bt-tether rework

- Move connexion to NetworkManager autoconnect for more reliability
- Check options formats for several values
- Add a dns option to be able to choose another DNS provider (ex: OpenNIC for no traceability)
- The "BT" show more information (Device Connected/disconnected, Connexion Up/Down)
- Status now show a message on error
- nmcli calls are non-blocking  
- Less logs when disconnected (ex: phone's bluetooth is off) 

Signed-off-by: Frédéric <fmatray@users.noreply.github.com>
This commit is contained in:
Frédéric
2025-02-07 22:34:52 +01:00
committed by GitHub
parent 4774a983f9
commit 7a0301b57f

View File

@ -1,93 +1,130 @@
import logging import logging
import subprocess import subprocess
import re
import pwnagotchi.plugins as plugins import pwnagotchi.plugins as plugins
import pwnagotchi.ui.fonts as fonts import pwnagotchi.ui.fonts as fonts
from pwnagotchi.ui.components import LabeledValue from pwnagotchi.ui.components import LabeledValue
from pwnagotchi.ui.view import BLACK from pwnagotchi.ui.view import BLACK
MAC_PTTRN = "^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$"
IP_PTTRN = "^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$"
class BTTether(plugins.Plugin): class BTTether(plugins.Plugin):
__author__ = 'Jayofelony' __author__ = "Jayofelony, modified my fmatray"
__version__ = '1.2' __version__ = "1.3"
__license__ = 'GPL3' __license__ = "GPL3"
__description__ = 'A new BT-Tether plugin' __description__ = "A new BT-Tether plugin"
def __init__(self): def __init__(self):
self.ready = False self.ready = False
self.options = dict() self.options = dict()
self.status = '-' self.phone_name = None
self.mac = None
@staticmethod
def nmcli(args, pattern=None):
try:
result = subprocess.run(["nmcli"] + args,
check=True, capture_output=True, text=True)
if pattern:
return result.stdout.find(pattern)
return result
except Exception as exp:
logging.error(f"[BT-Tether] Error with nmcli: {exp}")
raise exp
def on_loaded(self): def on_loaded(self):
logging.info("[BT-Tether] plugin loaded.") logging.info("[BT-Tether] plugin loaded.")
def on_config_changed(self, config): def on_config_changed(self, config):
if any(self.options[key] == '' for key in ['phone', 'phone-name', 'ip', 'mac']): if "phone-name" not in self.options:
self.ready = False logging.error("[BT-Tether] Phone name not provided")
ip = self.options['ip'] return
mac = self.options['mac'] if not ("mac" in self.options and re.match(MAC_PTTRN, self.options["mac"])):
phone_name = self.options['phone-name'] + ' Network' logging.error("[BT-Tether] Error with mac adresse")
if self.options['phone'].lower() == 'android': return
address = f'{ip}'
gateway = '192.168.44.1' if not ("phone" in self.options and self.options["phone"].lower() in ["android", "ios"]):
elif self.options['phone'].lower() == 'ios': logging.error("[BT-Tether] Phone type not supported")
address = f'{ip}' return
gateway = '172.20.10.1' if self.options["phone"].lower() == "android":
else: address = self.options.get("ip", "192.168.44.2")
logging.error("[BT-Tether] Phone type not supported.") gateway = "192.168.44.1"
elif self.options["phone"].lower() == "ios":
address = self.options.get("ip", "172.20.10.2")
gateway = "172.20.10.1"
if not re.match(IP_PTTRN, address):
logging.error(f"[BT-Tether] IP error: {address}")
return
self.phone_name = self.options["phone-name"] + " Network"
self.mac = self.options["mac"]
dns = self.options.get("dns", "8.8.8.8 1.1.1.1").replace(",", " ").replace(";", " ")
try:
# Configure connection. Metric is set to 200 to prefer connection over USB
self.nmcli(["connection", "modify", f"{self.phone_name}",
"connection.type", "bluetooth",
"bluetooth.type", "panu",
"bluetooth.bdaddr", f"{self.mac}",
"connection.autoconnect", "yes",
"connection.autoconnect-retries", "0",
"ipv4.method", "manual",
"ipv4.dns", f"{dns}",
"ipv4.addresses", f"{address}/24",
"ipv4.gateway", f"{gateway}",
"ipv4.route-metric", "200" ])
self.nmcli(["connection", "reload"])
self.ready = True
logging.info(f"[BT-Tether] Connection {self.phone_name} configured")
except Exception as e:
logging.error(f"[BT-Tether] Error while configuring: {e}")
return return
try: try:
subprocess.run([ self.nmcli(["connection", "up", f"{self.phone_name}"])
'nmcli', 'connection', 'modify', f'{phone_name}',
'connection.type', 'bluetooth',
'bluetooth.type', 'panu',
'bluetooth.bdaddr', f'{mac}',
'ipv4.method', 'manual',
'ipv4.dns', '8.8.8.8 1.1.1.1',
'ipv4.addresses', f'{address}/24',
'ipv4.gateway', f'{gateway}',
'ipv4.route-metric', '100'
], check=True)
subprocess.run(['nmcli', 'connection', 'reload'], check=True)
subprocess.run(['nmcli', 'connection', 'up', f'{phone_name}'], check=True)
except Exception as e: except Exception as e:
logging.error(f"[BT-Tether] Failed to connect to device: {e}") logging.error(f"[BT-Tether] Failed to connect to device: {e}")
logging.error(f"[BT-Tether] Failed to connect to device: have you enabled bluetooth tethering on your phone?") logging.error(
self.ready = True f"[BT-Tether] Failed to connect to device: have you enabled bluetooth tethering on your phone?"
)
def on_ready(self, agent):
if any(self.options[key] == '' for key in ['phone', 'phone-name', 'ip', 'mac']):
self.ready = False
self.ready = True
def on_ui_setup(self, ui): def on_ui_setup(self, ui):
with ui._lock: with ui._lock:
ui.add_element('bluetooth', LabeledValue(color=BLACK, label='BT', value='-', ui.add_element('bluetooth', LabeledValue(color=BLACK, label='BT', value='-',
position=(ui.width() / 2 - 10, 0), position=(ui.width() / 2 - 10, 0),
label_font=fonts.Bold, text_font=fonts.Medium)) label_font=fonts.Bold, text_font=fonts.Medium))
def on_ui_update(self, ui): def on_ui_update(self, ui):
if self.ready: if not self.ready:
phone_name = self.options['phone-name'] + ' Network'
if (subprocess.run(['bluetoothctl', 'info'], capture_output=True, text=True)).stdout.find('Connected: yes') != -1:
self.status = 'C'
else:
self.status = '-'
try:
subprocess.run(['nmcli', 'connection', 'up', f'{phone_name}'], check=True)
except Exception as e:
logging.debug(f"[BT-Tether] Failed to connect to device: {e}")
logging.error(f"[BT-Tether] Failed to connect to device: have you enabled bluetooth tethering on your phone?")
ui.set('bluetooth', self.status)
return return
with ui._lock:
status = ""
try:
# Checking connection
if self.nmcli(["-w", "0", "-g", "GENERAL.STATE", "connection", "show", self.phone_name],
"activated") != -1:
ui.set("bluetooth", "U")
return
else:
ui.set("bluetooth", "D")
status = "BT Conn. down"
# Checking device
if self.nmcli(["-w", "0", "-g", "GENERAL.STATE", "device", "show", self.mac],
"(connected)") != -1:
ui.set("bluetooth", "C")
status += "\nBT dev conn."
else:
ui.set("bluetooth", "-")
status += "\nBT dev disconn."
ui.set("status", status)
except Exception as e:
logging.error(f"[BT-Tether] Error on update: {e}")
def on_unload(self, ui): def on_unload(self, ui):
phone_name = self.options['phone-name'] + ' Network'
with ui._lock: with ui._lock:
ui.remove_element('bluetooth') ui.remove_element("bluetooth")
try: try:
if (subprocess.run(['bluetoothctl', 'info'], capture_output=True, text=True)).stdout.find('Connected: yes') != -1: self.nmcli(["connection", "down", f"{self.phone_name}"])
subprocess.run(['nmcli', 'connection', 'down', f'{phone_name}'], check=True)
logging.info(f"[BT-Tether] Disconnected from device with name: {phone_name}")
else:
logging.info(f"[BT-Tether] Device with name {phone_name} is not connected, not disconnecting")
except Exception as e: except Exception as e:
logging.error(f"[BT-Tether] Failed to disconnect from device: {e}") logging.error(f"[BT-Tether] Failed to disconnect from device: {e}")