mirror of
https://github.com/smittix/intercept.git
synced 2026-07-25 09:18:10 -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();
|
const result = await response.json();
|
||||||
console.log('[WiFiMode] Quick scan complete:', result);
|
console.log('[WiFiMode] Quick scan complete:', result);
|
||||||
|
|
||||||
// Check if we got results
|
// Check for error first
|
||||||
if (!result.access_points || result.access_points.length === 0) {
|
if (result.error) {
|
||||||
if (result.error) {
|
console.error('[WiFiMode] Quick scan error from server:', result.error);
|
||||||
throw new Error(result.error);
|
showError(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.');
|
|
||||||
setScanning(false);
|
setScanning(false);
|
||||||
return;
|
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
|
// Process results
|
||||||
processQuickScanResult(result);
|
processQuickScanResult(result);
|
||||||
|
|
||||||
|
|||||||
+115
-39
@@ -300,29 +300,54 @@ class UnifiedWiFiScanner:
|
|||||||
result.interface = iface
|
result.interface = iface
|
||||||
|
|
||||||
# Select and run parser based on platform/tools
|
# Select and run parser based on platform/tools
|
||||||
|
# Try multiple tools with fallback on Linux
|
||||||
observations = []
|
observations = []
|
||||||
tool_used = None
|
tool_used = None
|
||||||
|
errors_encountered = []
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if self._capabilities.platform == 'darwin':
|
if self._capabilities.platform == 'darwin':
|
||||||
if self._capabilities.has_airport:
|
if self._capabilities.has_airport:
|
||||||
observations = self._scan_with_airport(iface, timeout)
|
observations = self._scan_with_airport(iface, timeout)
|
||||||
tool_used = 'airport'
|
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:
|
if self._capabilities.has_nmcli:
|
||||||
observations = self._scan_with_nmcli(iface, timeout)
|
tools_to_try.append(('nmcli', self._scan_with_nmcli))
|
||||||
tool_used = 'nmcli'
|
if self._capabilities.has_iw:
|
||||||
elif self._capabilities.has_iw:
|
tools_to_try.append(('iw', self._scan_with_iw))
|
||||||
observations = self._scan_with_iw(iface, timeout)
|
if self._capabilities.has_iwlist:
|
||||||
tool_used = 'iw'
|
tools_to_try.append(('iwlist', self._scan_with_iwlist))
|
||||||
elif self._capabilities.has_iwlist:
|
|
||||||
observations = self._scan_with_iwlist(iface, timeout)
|
|
||||||
tool_used = 'iwlist'
|
|
||||||
|
|
||||||
if not tool_used:
|
if not tools_to_try:
|
||||||
result.error = "No WiFi scanning tool available"
|
result.error = "No WiFi scanning tools available. Install NetworkManager (nmcli) or wireless-tools (iw/iwlist)."
|
||||||
result.is_complete = True
|
result.is_complete = True
|
||||||
return result
|
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
|
# Process observations into access points
|
||||||
for obs in observations:
|
for obs in observations:
|
||||||
@@ -332,10 +357,16 @@ class UnifiedWiFiScanner:
|
|||||||
with self._lock:
|
with self._lock:
|
||||||
result.access_points = list(self._access_points.values())
|
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
|
# Generate channel stats
|
||||||
result.channel_stats = self._calculate_channel_stats()
|
result.channel_stats = self._calculate_channel_stats()
|
||||||
result.recommendations = self._generate_recommendations(result.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:
|
except subprocess.TimeoutExpired:
|
||||||
result.error = f"Scan timed out after {timeout}s"
|
result.error = f"Scan timed out after {timeout}s"
|
||||||
result.warnings.append(f"Tool '{tool_used}' timed out")
|
result.warnings.append(f"Tool '{tool_used}' timed out")
|
||||||
@@ -383,14 +414,21 @@ class UnifiedWiFiScanner:
|
|||||||
from .parsers.nmcli import parse_nmcli_scan
|
from .parsers.nmcli import parse_nmcli_scan
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Trigger a rescan first
|
# Try to trigger a rescan first (might fail if interface not managed by NM)
|
||||||
subprocess.run(
|
rescan_result = subprocess.run(
|
||||||
['nmcli', 'device', 'wifi', 'rescan', 'ifname', interface],
|
['nmcli', 'device', 'wifi', 'rescan', 'ifname', interface],
|
||||||
capture_output=True,
|
capture_output=True,
|
||||||
timeout=timeout / 2,
|
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(
|
result = subprocess.run(
|
||||||
['nmcli', '-t', '-f', 'BSSID,SSID,MODE,CHAN,FREQ,RATE,SIGNAL,SECURITY', 'device', 'wifi', 'list', 'ifname', interface],
|
['nmcli', '-t', '-f', 'BSSID,SSID,MODE,CHAN,FREQ,RATE,SIGNAL,SECURITY', 'device', 'wifi', 'list', 'ifname', interface],
|
||||||
capture_output=True,
|
capture_output=True,
|
||||||
@@ -398,10 +436,28 @@ class UnifiedWiFiScanner:
|
|||||||
timeout=timeout,
|
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:
|
if result.returncode != 0:
|
||||||
error_msg = result.stderr.strip() or f"nmcli returned code {result.returncode}"
|
error_msg = result.stderr.strip() or f"nmcli returned code {result.returncode}"
|
||||||
logger.warning(f"nmcli scan failed: {error_msg}")
|
# Check for common issues
|
||||||
raise RuntimeError(f"nmcli scan failed: {error_msg}")
|
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)
|
return parse_nmcli_scan(result.stdout)
|
||||||
except subprocess.TimeoutExpired:
|
except subprocess.TimeoutExpired:
|
||||||
@@ -413,36 +469,56 @@ class UnifiedWiFiScanner:
|
|||||||
"""Scan using iw."""
|
"""Scan using iw."""
|
||||||
from .parsers.iw import parse_iw_scan
|
from .parsers.iw import parse_iw_scan
|
||||||
|
|
||||||
result = subprocess.run(
|
try:
|
||||||
['iw', interface, 'scan'],
|
result = subprocess.run(
|
||||||
capture_output=True,
|
['iw', interface, 'scan'],
|
||||||
text=True,
|
capture_output=True,
|
||||||
timeout=timeout,
|
text=True,
|
||||||
)
|
timeout=timeout,
|
||||||
|
)
|
||||||
|
|
||||||
if result.returncode != 0:
|
if result.returncode != 0:
|
||||||
# May need root
|
error_msg = result.stderr.strip() or f"iw returned code {result.returncode}"
|
||||||
logger.warning(f"iw scan failed: {result.stderr}")
|
# Check for common errors
|
||||||
return []
|
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]:
|
def _scan_with_iwlist(self, interface: str, timeout: float) -> list[WiFiObservation]:
|
||||||
"""Scan using iwlist."""
|
"""Scan using iwlist."""
|
||||||
from .parsers.iwlist import parse_iwlist_scan
|
from .parsers.iwlist import parse_iwlist_scan
|
||||||
|
|
||||||
result = subprocess.run(
|
try:
|
||||||
['iwlist', interface, 'scan'],
|
result = subprocess.run(
|
||||||
capture_output=True,
|
['iwlist', interface, 'scan'],
|
||||||
text=True,
|
capture_output=True,
|
||||||
timeout=timeout,
|
text=True,
|
||||||
)
|
timeout=timeout,
|
||||||
|
)
|
||||||
|
|
||||||
if result.returncode != 0:
|
if result.returncode != 0:
|
||||||
logger.warning(f"iwlist scan failed: {result.stderr}")
|
error_msg = result.stderr.strip() or f"iwlist returned code {result.returncode}"
|
||||||
return []
|
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)
|
# Deep Scan (airodump-ng)
|
||||||
|
|||||||
Reference in New Issue
Block a user