From bb4ccc63559ff98ff05759d009f8b92a0ee3220c Mon Sep 17 00:00:00 2001 From: Smittix Date: Sat, 7 Feb 2026 14:45:24 +0000 Subject: [PATCH] Auto-bring WiFi interface up before TSCM scan When the WiFi interface is down (e.g. USB adapter not activated), scanning fails with "Network is down" errors. Now the scanner proactively checks interface state via /sys/class/net and brings it up using ip link (or ifconfig fallback) before attempting scans, with a retry loop if the initial scan still fails. Co-Authored-By: Claude Opus 4.6 --- utils/wifi/scanner.py | 91 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 91 insertions(+) diff --git a/utils/wifi/scanner.py b/utils/wifi/scanner.py index 94588e7..d9e6032 100644 --- a/utils/wifi/scanner.py +++ b/utils/wifi/scanner.py @@ -301,6 +301,73 @@ class UnifiedWiFiScanner: return False + def _ensure_interface_up(self, interface: str) -> bool: + """ + Ensure a WiFi interface is up before scanning. + + Attempts to bring the interface up using 'ip link set up', + falling back to 'ifconfig up'. + + Args: + interface: Network interface name. + + Returns: + True if the interface was brought up (or was already up), + False if we failed to bring it up. + """ + # Check current state via /sys/class/net + operstate_path = f"/sys/class/net/{interface}/operstate" + try: + with open(operstate_path) as f: + state = f.read().strip() + if state == "up": + return True + logger.info(f"Interface {interface} is '{state}', attempting to bring up") + except FileNotFoundError: + # Interface might not exist or /sys not available (non-Linux) + return True + except Exception: + pass + + # Try ip link set up + if shutil.which('ip'): + try: + result = subprocess.run( + ['ip', 'link', 'set', interface, 'up'], + capture_output=True, + text=True, + timeout=5, + ) + if result.returncode == 0: + logger.info(f"Brought interface {interface} up via ip link") + time.sleep(1) # Brief settle time + return True + else: + logger.warning(f"ip link set {interface} up failed: {result.stderr.strip()}") + except Exception as e: + logger.warning(f"Failed to run ip link: {e}") + + # Fallback to ifconfig + if shutil.which('ifconfig'): + try: + result = subprocess.run( + ['ifconfig', interface, 'up'], + capture_output=True, + text=True, + timeout=5, + ) + if result.returncode == 0: + logger.info(f"Brought interface {interface} up via ifconfig") + time.sleep(1) + return True + else: + logger.warning(f"ifconfig {interface} up failed: {result.stderr.strip()}") + except Exception as e: + logger.warning(f"Failed to run ifconfig: {e}") + + logger.error(f"Could not bring interface {interface} up") + return False + # ========================================================================= # Quick Scan # ========================================================================= @@ -362,6 +429,9 @@ class UnifiedWiFiScanner: result.is_complete = True return result else: # Linux - try tools in order with fallback + # Ensure interface is up before scanning + self._ensure_interface_up(iface) + tools_to_try = [] if self._capabilities.has_nmcli: tools_to_try.append(('nmcli', self._scan_with_nmcli)) @@ -375,6 +445,7 @@ class UnifiedWiFiScanner: result.is_complete = True return result + interface_was_down = False for tool_name, scan_func in tools_to_try: try: logger.info(f"Attempting quick scan with {tool_name} on {iface}") @@ -386,8 +457,28 @@ class UnifiedWiFiScanner: error_msg = f"{tool_name}: {str(e)}" errors_encountered.append(error_msg) logger.warning(f"Quick scan with {tool_name} failed: {e}") + if 'is down' in str(e): + interface_was_down = True continue # Try next tool + # If all tools failed because interface was down, try bringing it up and retry + if not tool_used and interface_was_down: + logger.info(f"Interface {iface} appears down, attempting to bring up and retry scan") + if self._ensure_interface_up(iface): + errors_encountered.clear() + for tool_name, scan_func in tools_to_try: + try: + logger.info(f"Retrying scan with {tool_name} on {iface} after bringing interface up") + observations = scan_func(iface, timeout) + tool_used = tool_name + logger.info(f"Retry scan with {tool_name} found {len(observations)} networks") + break + except Exception as e: + error_msg = f"{tool_name}: {str(e)}" + errors_encountered.append(error_msg) + logger.warning(f"Retry scan with {tool_name} failed: {e}") + continue + if not tool_used: # All tools failed result.error = "All scan tools failed. " + "; ".join(errors_encountered)