mirror of
https://github.com/smittix/intercept.git
synced 2026-06-08 14:11:54 -07:00
Improve WiFi quick scan error handling and Linux tool fallback
- Add fallback mechanism to try multiple tools (nmcli -> iw -> iwlist) - Improve error messages for iw/iwlist with root privilege detection - Enhance nmcli scanner to try without interface if specific scan fails - Better error reporting in frontend showing actual backend errors - Add logging throughout scan process for debugging This fixes quick scan immediately failing on Linux systems by trying multiple tools and providing meaningful error messages. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
+22
-8
@@ -302,18 +302,32 @@ const WiFiMode = (function() {
|
||||
const result = await response.json();
|
||||
console.log('[WiFiMode] Quick scan complete:', result);
|
||||
|
||||
// Check if we got results
|
||||
if (!result.access_points || result.access_points.length === 0) {
|
||||
if (result.error) {
|
||||
throw new Error(result.error);
|
||||
}
|
||||
// No error but no results - might need different tool
|
||||
console.warn('[WiFiMode] Quick scan returned no networks. Try Deep Scan instead.');
|
||||
showError('Quick scan found no networks. System tools may not be available. Try Deep Scan with monitor mode.');
|
||||
// Check for error first
|
||||
if (result.error) {
|
||||
console.error('[WiFiMode] Quick scan error from server:', result.error);
|
||||
showError(result.error);
|
||||
setScanning(false);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if we got results
|
||||
if (!result.access_points || result.access_points.length === 0) {
|
||||
// No error but no results
|
||||
let msg = 'Quick scan found no networks in range.';
|
||||
if (result.warnings && result.warnings.length > 0) {
|
||||
msg += ' Warnings: ' + result.warnings.join('; ');
|
||||
}
|
||||
console.warn('[WiFiMode] ' + msg);
|
||||
showError(msg + ' Try Deep Scan with monitor mode.');
|
||||
setScanning(false);
|
||||
return;
|
||||
}
|
||||
|
||||
// Show any warnings even on success
|
||||
if (result.warnings && result.warnings.length > 0) {
|
||||
console.warn('[WiFiMode] Quick scan warnings:', result.warnings);
|
||||
}
|
||||
|
||||
// Process results
|
||||
processQuickScanResult(result);
|
||||
|
||||
|
||||
+115
-39
@@ -300,29 +300,54 @@ class UnifiedWiFiScanner:
|
||||
result.interface = iface
|
||||
|
||||
# Select and run parser based on platform/tools
|
||||
# Try multiple tools with fallback on Linux
|
||||
observations = []
|
||||
tool_used = None
|
||||
errors_encountered = []
|
||||
|
||||
try:
|
||||
if self._capabilities.platform == 'darwin':
|
||||
if self._capabilities.has_airport:
|
||||
observations = self._scan_with_airport(iface, timeout)
|
||||
tool_used = 'airport'
|
||||
else: # Linux
|
||||
else:
|
||||
result.error = "No WiFi scanning tool available on macOS (airport not found)"
|
||||
result.is_complete = True
|
||||
return result
|
||||
else: # Linux - try tools in order with fallback
|
||||
tools_to_try = []
|
||||
if self._capabilities.has_nmcli:
|
||||
observations = self._scan_with_nmcli(iface, timeout)
|
||||
tool_used = 'nmcli'
|
||||
elif self._capabilities.has_iw:
|
||||
observations = self._scan_with_iw(iface, timeout)
|
||||
tool_used = 'iw'
|
||||
elif self._capabilities.has_iwlist:
|
||||
observations = self._scan_with_iwlist(iface, timeout)
|
||||
tool_used = 'iwlist'
|
||||
tools_to_try.append(('nmcli', self._scan_with_nmcli))
|
||||
if self._capabilities.has_iw:
|
||||
tools_to_try.append(('iw', self._scan_with_iw))
|
||||
if self._capabilities.has_iwlist:
|
||||
tools_to_try.append(('iwlist', self._scan_with_iwlist))
|
||||
|
||||
if not tool_used:
|
||||
result.error = "No WiFi scanning tool available"
|
||||
result.is_complete = True
|
||||
return result
|
||||
if not tools_to_try:
|
||||
result.error = "No WiFi scanning tools available. Install NetworkManager (nmcli) or wireless-tools (iw/iwlist)."
|
||||
result.is_complete = True
|
||||
return result
|
||||
|
||||
for tool_name, scan_func in tools_to_try:
|
||||
try:
|
||||
logger.info(f"Attempting quick scan with {tool_name} on {iface}")
|
||||
observations = scan_func(iface, timeout)
|
||||
tool_used = tool_name
|
||||
logger.info(f"Quick scan with {tool_name} found {len(observations)} networks")
|
||||
break # Success, stop trying other tools
|
||||
except Exception as e:
|
||||
error_msg = f"{tool_name}: {str(e)}"
|
||||
errors_encountered.append(error_msg)
|
||||
logger.warning(f"Quick scan with {tool_name} failed: {e}")
|
||||
continue # Try next tool
|
||||
|
||||
if not tool_used:
|
||||
# All tools failed
|
||||
result.error = "All scan tools failed. " + "; ".join(errors_encountered)
|
||||
if not self._capabilities.is_root:
|
||||
result.error += " (Note: iw/iwlist require root privileges)"
|
||||
result.is_complete = True
|
||||
return result
|
||||
|
||||
# Process observations into access points
|
||||
for obs in observations:
|
||||
@@ -332,10 +357,16 @@ class UnifiedWiFiScanner:
|
||||
with self._lock:
|
||||
result.access_points = list(self._access_points.values())
|
||||
|
||||
# Add warnings for tools that failed before one succeeded
|
||||
for err in errors_encountered:
|
||||
result.warnings.append(err)
|
||||
|
||||
# Generate channel stats
|
||||
result.channel_stats = self._calculate_channel_stats()
|
||||
result.recommendations = self._generate_recommendations(result.channel_stats)
|
||||
|
||||
logger.info(f"Quick scan complete: {len(result.access_points)} networks found using {tool_used}")
|
||||
|
||||
except subprocess.TimeoutExpired:
|
||||
result.error = f"Scan timed out after {timeout}s"
|
||||
result.warnings.append(f"Tool '{tool_used}' timed out")
|
||||
@@ -383,14 +414,21 @@ class UnifiedWiFiScanner:
|
||||
from .parsers.nmcli import parse_nmcli_scan
|
||||
|
||||
try:
|
||||
# Trigger a rescan first
|
||||
subprocess.run(
|
||||
# Try to trigger a rescan first (might fail if interface not managed by NM)
|
||||
rescan_result = subprocess.run(
|
||||
['nmcli', 'device', 'wifi', 'rescan', 'ifname', interface],
|
||||
capture_output=True,
|
||||
timeout=timeout / 2,
|
||||
)
|
||||
if rescan_result.returncode != 0:
|
||||
# Try without interface specification
|
||||
subprocess.run(
|
||||
['nmcli', 'device', 'wifi', 'rescan'],
|
||||
capture_output=True,
|
||||
timeout=timeout / 2,
|
||||
)
|
||||
|
||||
# Get results
|
||||
# Get results - try with interface first, then without
|
||||
result = subprocess.run(
|
||||
['nmcli', '-t', '-f', 'BSSID,SSID,MODE,CHAN,FREQ,RATE,SIGNAL,SECURITY', 'device', 'wifi', 'list', 'ifname', interface],
|
||||
capture_output=True,
|
||||
@@ -398,10 +436,28 @@ class UnifiedWiFiScanner:
|
||||
timeout=timeout,
|
||||
)
|
||||
|
||||
# If interface-specific scan failed, try general scan
|
||||
if result.returncode != 0 or not result.stdout.strip():
|
||||
logger.debug(f"nmcli scan with interface {interface} failed, trying general scan")
|
||||
result = subprocess.run(
|
||||
['nmcli', '-t', '-f', 'BSSID,SSID,MODE,CHAN,FREQ,RATE,SIGNAL,SECURITY', 'device', 'wifi', 'list'],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=timeout,
|
||||
)
|
||||
|
||||
if result.returncode != 0:
|
||||
error_msg = result.stderr.strip() or f"nmcli returned code {result.returncode}"
|
||||
logger.warning(f"nmcli scan failed: {error_msg}")
|
||||
raise RuntimeError(f"nmcli scan failed: {error_msg}")
|
||||
# Check for common issues
|
||||
if 'not running' in error_msg.lower():
|
||||
raise RuntimeError("NetworkManager is not running")
|
||||
elif 'not found' in error_msg.lower() or 'no such' in error_msg.lower():
|
||||
raise RuntimeError(f"Interface {interface} not found or not managed by NetworkManager")
|
||||
else:
|
||||
raise RuntimeError(f"nmcli scan failed: {error_msg}")
|
||||
|
||||
if not result.stdout.strip():
|
||||
raise RuntimeError("nmcli returned no results (WiFi might be disabled or no networks in range)")
|
||||
|
||||
return parse_nmcli_scan(result.stdout)
|
||||
except subprocess.TimeoutExpired:
|
||||
@@ -413,36 +469,56 @@ class UnifiedWiFiScanner:
|
||||
"""Scan using iw."""
|
||||
from .parsers.iw import parse_iw_scan
|
||||
|
||||
result = subprocess.run(
|
||||
['iw', interface, 'scan'],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=timeout,
|
||||
)
|
||||
try:
|
||||
result = subprocess.run(
|
||||
['iw', interface, 'scan'],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=timeout,
|
||||
)
|
||||
|
||||
if result.returncode != 0:
|
||||
# May need root
|
||||
logger.warning(f"iw scan failed: {result.stderr}")
|
||||
return []
|
||||
if result.returncode != 0:
|
||||
error_msg = result.stderr.strip() or f"iw returned code {result.returncode}"
|
||||
# Check for common errors
|
||||
if 'Operation not permitted' in error_msg or 'Permission denied' in error_msg:
|
||||
raise RuntimeError(f"iw scan requires root privileges: {error_msg}")
|
||||
elif 'Network is down' in error_msg:
|
||||
raise RuntimeError(f"Interface {interface} is down: {error_msg}")
|
||||
else:
|
||||
raise RuntimeError(f"iw scan failed: {error_msg}")
|
||||
|
||||
return parse_iw_scan(result.stdout)
|
||||
return parse_iw_scan(result.stdout)
|
||||
except subprocess.TimeoutExpired:
|
||||
raise RuntimeError(f"iw scan timed out after {timeout}s")
|
||||
except FileNotFoundError:
|
||||
raise RuntimeError("iw not found (wireless-tools not installed)")
|
||||
|
||||
def _scan_with_iwlist(self, interface: str, timeout: float) -> list[WiFiObservation]:
|
||||
"""Scan using iwlist."""
|
||||
from .parsers.iwlist import parse_iwlist_scan
|
||||
|
||||
result = subprocess.run(
|
||||
['iwlist', interface, 'scan'],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=timeout,
|
||||
)
|
||||
try:
|
||||
result = subprocess.run(
|
||||
['iwlist', interface, 'scan'],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=timeout,
|
||||
)
|
||||
|
||||
if result.returncode != 0:
|
||||
logger.warning(f"iwlist scan failed: {result.stderr}")
|
||||
return []
|
||||
if result.returncode != 0:
|
||||
error_msg = result.stderr.strip() or f"iwlist returned code {result.returncode}"
|
||||
if 'Operation not permitted' in error_msg or 'Permission denied' in error_msg:
|
||||
raise RuntimeError(f"iwlist scan requires root privileges: {error_msg}")
|
||||
elif 'Network is down' in error_msg:
|
||||
raise RuntimeError(f"Interface {interface} is down: {error_msg}")
|
||||
else:
|
||||
raise RuntimeError(f"iwlist scan failed: {error_msg}")
|
||||
|
||||
return parse_iwlist_scan(result.stdout)
|
||||
return parse_iwlist_scan(result.stdout)
|
||||
except subprocess.TimeoutExpired:
|
||||
raise RuntimeError(f"iwlist scan timed out after {timeout}s")
|
||||
except FileNotFoundError:
|
||||
raise RuntimeError("iwlist not found (wireless-tools not installed)")
|
||||
|
||||
# =========================================================================
|
||||
# Deep Scan (airodump-ng)
|
||||
|
||||
Reference in New Issue
Block a user