From 5b66d687c44dfc209b103702f0cf977c0e6f4679 Mon Sep 17 00:00:00 2001 From: dadav <33197631+dadav@users.noreply.github.com> Date: Fri, 1 Nov 2019 18:00:07 +0100 Subject: [PATCH] Add multi bt-tether support --- pwnagotchi/defaults.yml | 26 ++- pwnagotchi/plugins/default/bt-tether.py | 218 ++++++++++++++---------- 2 files changed, 146 insertions(+), 98 deletions(-) diff --git a/pwnagotchi/defaults.yml b/pwnagotchi/defaults.yml index bb78d8a..5de1137 100644 --- a/pwnagotchi/defaults.yml +++ b/pwnagotchi/defaults.yml @@ -71,11 +71,27 @@ main: enabled: false bt-tether: enabled: false # if you want to use this, set ui.display.video.address to 0.0.0.0 - mac: ~ # mac of your bluetooth device - ip: '192.168.44.44' # ip from which your pwnagotchi should be reachable - netmask: 24 - interval: 1 # check every x minutes for device - share_internet: false + devices: + android-phone: + search_order: 1 # search for this first + mac: ~ # mac of your bluetooth device + ip: '192.168.44.44' # ip from which your pwnagotchi should be reachable + netmask: 24 + interval: 1 # check every minute for device + scantime: 10 # search for 10 seconds + max_tries: 10 # after 10 tries of "not found"; don't try anymore + share_internet: false + priority: 1 # low routing priority; ios (prio: 999) would win here + ios-phone: + search_order: 2 # search for this second + mac: ~ # mac of your bluetooth device + ip: '172.20.10.2' # ip from which your pwnagotchi should be reachable + netmask: 24 + interval: 5 # check every 5 minutes for device + scantime: 20 + max_tries: 0 # infinity + share_internet: false + priority: 999 # routing priority memtemp: # Display memory usage, cpu load and cpu temperature on screen enabled: false orientation: horizontal # horizontal/vertical diff --git a/pwnagotchi/plugins/default/bt-tether.py b/pwnagotchi/plugins/default/bt-tether.py index 7c89387..2115c72 100644 --- a/pwnagotchi/plugins/default/bt-tether.py +++ b/pwnagotchi/plugins/default/bt-tether.py @@ -149,24 +149,6 @@ class BTNap: return None - def is_connected(self): - """ - Check if already connected - """ - logging.debug("BT-TETHER: Checking if device is connected.") - - bt_dev = self.power(True) - - if not bt_dev: - logging.debug("BT-TETHER: No bluetooth device found.") - return None, False - - try: - dev_remote = BTNap.find_device(self._mac, bt_dev) - return dev_remote, bool(BTNap.prop_get(dev_remote, 'Connected')) - except BTError: - logging.debug("BT-TETHER: Device is not connected.") - return None, False def is_paired(self): """ @@ -388,8 +370,8 @@ class IfaceWrapper: return False @staticmethod - def set_route(addr): - process = subprocess.Popen(f"ip route replace default via {addr}", shell=True, stdin=None, + def set_route(gateway, device): + process = subprocess.Popen(f"ip route replace default via {gateway} dev {device}", shell=True, stdin=None, stdout=open("/dev/null", "w"), stderr=None, executable="/bin/bash") process.wait() @@ -399,6 +381,39 @@ class IfaceWrapper: return True +class Device: + def __init__(self, name, share_internet, mac, ip, netmask, interval, priority=10, scantime=15, search_order=0, max_tries=0, **kwargs): + self.name = name + self.status = StatusFile(f'/root/.bt-tether-{name}') + self.status.update() + self.tries = 0 + self.network = None + + self.max_tries = max_tries + self.search_order = search_order + self.share_internet = share_internet + self.ip = ip + self.netmask = netmask + self.interval = interval + self.mac = mac + self.scantime = scantime + self.priority = priority + + def connected(self): + """ + Checks if device is connected + """ + return self.network and BTNap.prop_get(self.network, 'Connected') + + def interface(self): + """ + Returns the interface name or None + """ + if not self.connected(): + return None + return BTNap.prop_get(self.network, 'Interface') + + class BTTether(plugins.Plugin): __author__ = '33197631+dadav@users.noreply.github.com' __version__ = '1.0.0' @@ -407,15 +422,33 @@ class BTTether(plugins.Plugin): def __init__(self): self.ready = False - self.interval = StatusFile('/root/.bt-tether') - self.network = None + self.options = dict() + self.devices = dict() def on_loaded(self): - for opt in ['share_internet', 'mac', 'ip', 'netmask', 'interval']: - if opt not in self.options or (opt in self.options and self.options[opt] is None): - logging.error("BT-TET: Please specify the %s in your config.yml.", opt) - return + if 'devices' in self.options: + for device, options in self.options['devices'].items(): + for device_opt in ['priority', 'scantime', 'search_order', 'max_tries', 'share_internet', 'mac', 'ip', 'netmask', 'interval']: + if device_opt not in options or (device_opt in options and options[device_opt] is None): + logging.error("BT-TET: Pleace specify the %s for device %s.", device_opt, device) + break + else: + self.devices[device] = Device(name=device, **options) + elif 'mac' in self.options: + # legacy + for opt in ['share_internet', 'mac', 'ip', 'netmask', 'interval']: + if opt not in self.options or (opt in self.options and self.options[opt] is None): + logging.error("BT-TET: Please specify the %s in your config.yml.", opt) + return + self.devices['default'] = Device(name='default', **self.options) + else: + logging.error("BT-TET: No configuration found") + return + + if not self.devices: + logging.error("BT-TET: No valid devices found") + return # ensure bluetooth is running bt_unit = SystemdUnitWrapper('bluetooth.service') if not bt_unit.is_active(): @@ -423,7 +456,6 @@ class BTTether(plugins.Plugin): logging.error("BT-TET: Can't start bluetooth.service") return - self.interval.update() self.ready = True def on_ui_setup(self, ui): @@ -434,88 +466,88 @@ class BTTether(plugins.Plugin): if not self.ready: return - if self.interval.newer_then_minutes(self.options['interval']): - return + devices_to_try = list() + connected_priorities = list() + any_device_connected = False # if this is true, last status on screen should be C - self.interval.update() + for _, device in self.devices.items(): + if device.connected(): + connected_priorities.append(device.priority) + any_device_connected = True + continue - bt = BTNap(self.options['mac']) + if not device.max_tries or (device.max_tries > device.tries): + if device.status.newer_then_minutes(device.interval): + devices_to_try.append(device) + device.status.update() + device.tries += 1 - logging.debug('BT-TETHER: Check if already connected and paired') - dev_remote, connected = bt.is_connected() + sorted_devices = sorted(devices_to_try, key=lambda x: x.search_order) - if connected: - logging.debug('BT-TETHER: Already connected.') - ui.set('bluetooth', 'C') - return + for device in sorted_devices: + bt = BTNap(device.mac) - try: - logging.info('BT-TETHER: Search device ...') - dev_remote = bt.wait_for_device() - if dev_remote is None: - logging.info('BT-TETHER: Could not find device.') + try: + logging.info('BT-TETHER: Search %d secs for %s ...', device.scantime, device.name) + dev_remote = bt.wait_for_device(timeout=device.scantime) + if dev_remote is None: + logging.info('BT-TETHER: Could not find %s, try again in %d minutes.', device.name, device.interval) + ui.set('bluetooth', 'NF') + continue + except Exception as bt_ex: + logging.error(bt_ex) ui.set('bluetooth', 'NF') - return - except Exception as bt_ex: - logging.error(bt_ex) - ui.set('bluetooth', 'NF') - return + continue - paired = bt.is_paired() - if not paired: - if BTNap.pair(dev_remote): - logging.info('BT-TETHER: Paired with device.') + paired = bt.is_paired() + if not paired: + if BTNap.pair(dev_remote): + logging.info('BT-TETHER: Paired with %s.', device.name) + else: + logging.info('BT-TETHER: Pairing with %s failed ...', device.name) + ui.set('bluetooth', 'PE') + continue else: - logging.info('BT-TETHER: Pairing failed ...') - ui.set('bluetooth', 'PE') - return - else: - logging.debug('BT-TETHER: Already paired.') + logging.debug('BT-TETHER: Already paired.') - btnap_iface = IfaceWrapper('bnep0') - logging.debug('BT-TETHER: Check interface') - if not btnap_iface.exists(): - # connected and paired but not napping - logging.debug('BT-TETHER: Try to connect to nap ...') - network, status = BTNap.nap(dev_remote) - self.network = network - if status: - logging.info('BT-TETHER: Napping!') + + logging.debug('BT-TETHER: Try to create nap connection with %s ...', device.name) + device.network, success = BTNap.nap(dev_remote) + + if success: + logging.info('BT-TETHER: Created interface (%s)', device.interface()) ui.set('bluetooth', 'C') - time.sleep(5) + any_device_connected = True + device.tries = 0 # reset tries else: - logging.info('BT-TETHER: Napping failed ...') + logging.info('BT-TETHER: Could not establish nap connection with %s', device.name) ui.set('bluetooth', 'NF') - return + continue - if btnap_iface.exists(): - logging.debug('BT-TETHER: Interface found') + interface = device.interface() + addr = f"{device.ip}/{device.netmask}" + gateway = ".".join(device.ip.split('.')[:-1] + ['1']) - # check ip - addr = f"{self.options['ip']}/{self.options['netmask']}" - - logging.debug('BT-TETHER: Try to set ADDR to interface') - if not btnap_iface.set_addr(addr): + wrapped_interface = IfaceWrapper(interface) + logging.debug('BT-TETHER: Add ip to %s', interface) + if not wrapped_interface.set_addr(addr): ui.set('bluetooth', 'AE') - logging.error("BT-TETHER: Could not set ip of bnep0 to %s", addr) - return + logging.error("BT-TETHER: Could not add ip to %s", interface) + continue - logging.debug('BT-TETHER: Set ADDR to interface') + if device.share_internet: + if not connected_priorities or device.priority > max(connected_priorities): + logging.debug('BT-TETHER: Set default route to %s via %s', gateway, interface) + IfaceWrapper.set_route(gateway, interface) + connected_priorities.append(device.priority) - # change route if sharking - if self.options['share_internet']: - logging.debug('BT-TETHER: Set routing and change resolv.conf') - IfaceWrapper.set_route( - ".".join(self.options['ip'].split('.')[:-1] + ['1'])) # im not proud about that - # fix resolv.conf; dns over https ftw! - with open('/etc/resolv.conf', 'r+') as resolv: - nameserver = resolv.read() - if 'nameserver 9.9.9.9' not in nameserver: - logging.info('BT-TETHER: Added nameserver') - resolv.seek(0) - resolv.write(nameserver + 'nameserver 9.9.9.9\n') + logging.debug('BT-TETHER: Change resolv.conf if necessary ...') + with open('/etc/resolv.conf', 'r+') as resolv: + nameserver = resolv.read() + if 'nameserver 9.9.9.9' not in nameserver: + logging.info('BT-TETHER: Added nameserver') + resolv.seek(0) + resolv.write(nameserver + 'nameserver 9.9.9.9\n') + if any_device_connected: ui.set('bluetooth', 'C') - else: - logging.error('BT-TETHER: bnep0 not found') - ui.set('bluetooth', 'BE')