mirror of
https://github.com/smittix/intercept.git
synced 2026-04-24 06:40:00 -07:00
Add error handling for WiFi and Bluetooth scanning
- Add stderr capture and display for airodump-ng errors in WiFi scanning - Add early failure detection with helpful error messages for WiFi scan - Add timeout warning when no scan data received after 5 seconds - Add error handling for Bluetooth scanning with stderr capture - Add showError() function to display errors in red in the output panel - Add error type handlers to WiFi and Bluetooth SSE event streams - Hide RTL-SDR device section when in WiFi or Bluetooth modes - Improve error messages with install instructions for missing tools 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
154
intercept.py
154
intercept.py
@@ -1389,8 +1389,8 @@ HTML_TEMPLATE = '''
|
|||||||
<button class="mode-tab" onclick="switchMode('bluetooth')">BT</button>
|
<button class="mode-tab" onclick="switchMode('bluetooth')">BT</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="section">
|
<div class="section" id="rtlDeviceSection">
|
||||||
<h3>Device</h3>
|
<h3>RTL-SDR Device</h3>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<select id="deviceSelect">
|
<select id="deviceSelect">
|
||||||
{% if devices %}
|
{% if devices %}
|
||||||
@@ -1933,6 +1933,9 @@ HTML_TEMPLATE = '''
|
|||||||
document.getElementById('wifiVisuals').style.display = mode === 'wifi' ? 'grid' : 'none';
|
document.getElementById('wifiVisuals').style.display = mode === 'wifi' ? 'grid' : 'none';
|
||||||
document.getElementById('btVisuals').style.display = mode === 'bluetooth' ? 'grid' : 'none';
|
document.getElementById('btVisuals').style.display = mode === 'bluetooth' ? 'grid' : 'none';
|
||||||
|
|
||||||
|
// Show RTL-SDR device section only for modes that use it (pager and sensor/433MHz)
|
||||||
|
document.getElementById('rtlDeviceSection').style.display = (mode === 'pager' || mode === 'sensor') ? 'block' : 'none';
|
||||||
|
|
||||||
// Load interfaces when switching modes
|
// Load interfaces when switching modes
|
||||||
if (mode === 'wifi') {
|
if (mode === 'wifi') {
|
||||||
refreshWifiInterfaces();
|
refreshWifiInterfaces();
|
||||||
@@ -2624,6 +2627,22 @@ HTML_TEMPLATE = '''
|
|||||||
output.insertBefore(infoEl, output.firstChild);
|
output.insertBefore(infoEl, output.firstChild);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function showError(text) {
|
||||||
|
const output = document.getElementById('output');
|
||||||
|
|
||||||
|
// Clear placeholder only (has the 'placeholder' class)
|
||||||
|
const placeholder = output.querySelector('.placeholder');
|
||||||
|
if (placeholder) {
|
||||||
|
placeholder.remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
const errorEl = document.createElement('div');
|
||||||
|
errorEl.className = 'error-msg';
|
||||||
|
errorEl.style.cssText = 'padding: 12px 15px; margin-bottom: 8px; background: #1a0a0a; border: 1px solid #2a1a1a; border-left: 2px solid #ff3366; font-family: "JetBrains Mono", monospace; font-size: 11px; color: #ff6688; word-break: break-all;';
|
||||||
|
errorEl.textContent = '⚠ ' + text;
|
||||||
|
output.insertBefore(errorEl, output.firstChild);
|
||||||
|
}
|
||||||
|
|
||||||
function clearMessages() {
|
function clearMessages() {
|
||||||
document.getElementById('output').innerHTML = `
|
document.getElementById('output').innerHTML = `
|
||||||
<div class="placeholder" style="color: #888; text-align: center; padding: 50px;">
|
<div class="placeholder" style="color: #888; text-align: center; padding: 50px;">
|
||||||
@@ -3137,6 +3156,8 @@ HTML_TEMPLATE = '''
|
|||||||
handleWifiClient(data);
|
handleWifiClient(data);
|
||||||
} else if (data.type === 'info' || data.type === 'raw') {
|
} else if (data.type === 'info' || data.type === 'raw') {
|
||||||
showInfo(data.text);
|
showInfo(data.text);
|
||||||
|
} else if (data.type === 'error') {
|
||||||
|
showError(data.text);
|
||||||
} else if (data.type === 'status') {
|
} else if (data.type === 'status') {
|
||||||
if (data.text === 'stopped') {
|
if (data.text === 'stopped') {
|
||||||
setWifiRunning(false);
|
setWifiRunning(false);
|
||||||
@@ -3740,6 +3761,8 @@ HTML_TEMPLATE = '''
|
|||||||
handleBtDevice(data);
|
handleBtDevice(data);
|
||||||
} else if (data.type === 'info' || data.type === 'raw') {
|
} else if (data.type === 'info' || data.type === 'raw') {
|
||||||
showInfo(data.text);
|
showInfo(data.text);
|
||||||
|
} else if (data.type === 'error') {
|
||||||
|
showError(data.text);
|
||||||
} else if (data.type === 'status') {
|
} else if (data.type === 'status') {
|
||||||
if (data.text === 'stopped') {
|
if (data.text === 'stopped') {
|
||||||
setBtRunning(false);
|
setBtRunning(false);
|
||||||
@@ -4859,7 +4882,7 @@ def toggle_monitor_mode():
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
return jsonify({'status': 'error', 'message': str(e)})
|
return jsonify({'status': 'error', 'message': str(e)})
|
||||||
else:
|
else:
|
||||||
return jsonify({'status': 'error', 'message': 'No monitor mode tools available (need airmon-ng or iw)'})
|
return jsonify({'status': 'error', 'message': 'No monitor mode tools available. Install aircrack-ng (brew install aircrack-ng) or iw.'})
|
||||||
|
|
||||||
else: # stop
|
else: # stop
|
||||||
if check_tool('airmon-ng'):
|
if check_tool('airmon-ng'):
|
||||||
@@ -4951,17 +4974,42 @@ def stream_airodump_output(process, csv_path):
|
|||||||
"""Stream airodump-ng output to queue."""
|
"""Stream airodump-ng output to queue."""
|
||||||
global wifi_process, wifi_networks, wifi_clients
|
global wifi_process, wifi_networks, wifi_clients
|
||||||
import time
|
import time
|
||||||
|
import select
|
||||||
|
|
||||||
try:
|
try:
|
||||||
wifi_queue.put({'type': 'status', 'text': 'started'})
|
wifi_queue.put({'type': 'status', 'text': 'started'})
|
||||||
last_parse = 0
|
last_parse = 0
|
||||||
|
start_time = time.time()
|
||||||
|
csv_found = False
|
||||||
|
|
||||||
while process.poll() is None:
|
while process.poll() is None:
|
||||||
|
# Check for stderr output (non-blocking)
|
||||||
|
try:
|
||||||
|
import fcntl
|
||||||
|
# Make stderr non-blocking
|
||||||
|
fd = process.stderr.fileno()
|
||||||
|
fl = fcntl.fcntl(fd, fcntl.F_GETFL)
|
||||||
|
fcntl.fcntl(fd, fcntl.F_SETFL, fl | os.O_NONBLOCK)
|
||||||
|
|
||||||
|
stderr_data = process.stderr.read()
|
||||||
|
if stderr_data:
|
||||||
|
stderr_text = stderr_data.decode('utf-8', errors='replace').strip()
|
||||||
|
if stderr_text:
|
||||||
|
# Filter out progress updates, report actual errors
|
||||||
|
for line in stderr_text.split('\n'):
|
||||||
|
line = line.strip()
|
||||||
|
if line and not line.startswith('CH') and not line.startswith('Elapsed'):
|
||||||
|
wifi_queue.put({'type': 'error', 'text': f'airodump-ng: {line}'})
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
# Parse CSV file periodically
|
# Parse CSV file periodically
|
||||||
current_time = time.time()
|
current_time = time.time()
|
||||||
if current_time - last_parse >= 2: # Parse every 2 seconds
|
if current_time - last_parse >= 2: # Parse every 2 seconds
|
||||||
if os.path.exists(csv_path + '-01.csv'):
|
csv_file = csv_path + '-01.csv'
|
||||||
networks, clients = parse_airodump_csv(csv_path + '-01.csv')
|
if os.path.exists(csv_file):
|
||||||
|
csv_found = True
|
||||||
|
networks, clients = parse_airodump_csv(csv_file)
|
||||||
|
|
||||||
# Detect new networks
|
# Detect new networks
|
||||||
for bssid, net in networks.items():
|
for bssid, net in networks.items():
|
||||||
@@ -4991,9 +5039,28 @@ def stream_airodump_output(process, csv_path):
|
|||||||
wifi_networks = networks
|
wifi_networks = networks
|
||||||
wifi_clients = clients
|
wifi_clients = clients
|
||||||
last_parse = current_time
|
last_parse = current_time
|
||||||
|
elif current_time - start_time > 5 and not csv_found:
|
||||||
|
# No CSV after 5 seconds - likely a problem
|
||||||
|
wifi_queue.put({'type': 'error', 'text': 'No scan data after 5 seconds. Check if monitor mode is properly enabled.'})
|
||||||
|
start_time = current_time + 30 # Don't spam this message
|
||||||
|
|
||||||
time.sleep(0.5)
|
time.sleep(0.5)
|
||||||
|
|
||||||
|
# Process exited - capture any remaining stderr
|
||||||
|
try:
|
||||||
|
remaining_stderr = process.stderr.read()
|
||||||
|
if remaining_stderr:
|
||||||
|
stderr_text = remaining_stderr.decode('utf-8', errors='replace').strip()
|
||||||
|
if stderr_text:
|
||||||
|
wifi_queue.put({'type': 'error', 'text': f'airodump-ng exited: {stderr_text}'})
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Check exit code
|
||||||
|
exit_code = process.returncode
|
||||||
|
if exit_code != 0 and exit_code is not None:
|
||||||
|
wifi_queue.put({'type': 'error', 'text': f'airodump-ng exited with code {exit_code}'})
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
wifi_queue.put({'type': 'error', 'text': str(e)})
|
wifi_queue.put({'type': 'error', 'text': str(e)})
|
||||||
finally:
|
finally:
|
||||||
@@ -5062,6 +5129,29 @@ def start_wifi_scan():
|
|||||||
stderr=subprocess.PIPE
|
stderr=subprocess.PIPE
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Wait briefly to check if process fails immediately
|
||||||
|
import time
|
||||||
|
time.sleep(0.5)
|
||||||
|
|
||||||
|
if wifi_process.poll() is not None:
|
||||||
|
# Process already exited - capture error
|
||||||
|
stderr_output = wifi_process.stderr.read().decode('utf-8', errors='replace').strip()
|
||||||
|
stdout_output = wifi_process.stdout.read().decode('utf-8', errors='replace').strip()
|
||||||
|
exit_code = wifi_process.returncode
|
||||||
|
wifi_process = None
|
||||||
|
|
||||||
|
error_msg = stderr_output or stdout_output or f'Process exited with code {exit_code}'
|
||||||
|
|
||||||
|
# Common error explanations
|
||||||
|
if 'No such device' in error_msg or 'No such interface' in error_msg:
|
||||||
|
error_msg = f'Interface "{interface}" not found. Make sure monitor mode is enabled.'
|
||||||
|
elif 'Operation not permitted' in error_msg:
|
||||||
|
error_msg = 'Permission denied. Try running with sudo.'
|
||||||
|
elif 'monitor mode' in error_msg.lower():
|
||||||
|
error_msg = f'Interface "{interface}" is not in monitor mode. Enable monitor mode first.'
|
||||||
|
|
||||||
|
return jsonify({'status': 'error', 'message': error_msg})
|
||||||
|
|
||||||
# Start parsing thread
|
# Start parsing thread
|
||||||
thread = threading.Thread(target=stream_airodump_output, args=(wifi_process, csv_path))
|
thread = threading.Thread(target=stream_airodump_output, args=(wifi_process, csv_path))
|
||||||
thread.daemon = True
|
thread.daemon = True
|
||||||
@@ -5072,7 +5162,7 @@ def start_wifi_scan():
|
|||||||
return jsonify({'status': 'started', 'interface': interface})
|
return jsonify({'status': 'started', 'interface': interface})
|
||||||
|
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
return jsonify({'status': 'error', 'message': 'airodump-ng not found. Install aircrack-ng suite.'})
|
return jsonify({'status': 'error', 'message': 'airodump-ng not found. Install aircrack-ng suite (brew install aircrack-ng).'})
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return jsonify({'status': 'error', 'message': str(e)})
|
return jsonify({'status': 'error', 'message': str(e)})
|
||||||
|
|
||||||
@@ -5444,6 +5534,17 @@ def stream_bt_scan(process, scan_mode):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
bt_queue.put({'type': 'status', 'text': 'started'})
|
bt_queue.put({'type': 'status', 'text': 'started'})
|
||||||
|
start_time = time.time()
|
||||||
|
device_found = False
|
||||||
|
|
||||||
|
# Set up non-blocking stderr reading
|
||||||
|
try:
|
||||||
|
import fcntl
|
||||||
|
fd = process.stderr.fileno()
|
||||||
|
fl = fcntl.fcntl(fd, fcntl.F_GETFL)
|
||||||
|
fcntl.fcntl(fd, fcntl.F_SETFL, fl | os.O_NONBLOCK)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
if scan_mode == 'hcitool':
|
if scan_mode == 'hcitool':
|
||||||
# hcitool lescan output
|
# hcitool lescan output
|
||||||
@@ -5519,7 +5620,22 @@ def stream_bt_scan(process, scan_mode):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
bt_queue.put({'type': 'error', 'text': str(e)})
|
bt_queue.put({'type': 'error', 'text': str(e)})
|
||||||
finally:
|
finally:
|
||||||
|
# Capture any remaining stderr
|
||||||
|
try:
|
||||||
|
remaining_stderr = process.stderr.read()
|
||||||
|
if remaining_stderr:
|
||||||
|
stderr_text = remaining_stderr.decode('utf-8', errors='replace').strip()
|
||||||
|
if stderr_text:
|
||||||
|
bt_queue.put({'type': 'error', 'text': f'Bluetooth scan: {stderr_text}'})
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Check exit code
|
||||||
process.wait()
|
process.wait()
|
||||||
|
exit_code = process.returncode
|
||||||
|
if exit_code != 0 and exit_code is not None:
|
||||||
|
bt_queue.put({'type': 'error', 'text': f'Bluetooth scan exited with code {exit_code}'})
|
||||||
|
|
||||||
bt_queue.put({'type': 'status', 'text': 'stopped'})
|
bt_queue.put({'type': 'status', 'text': 'stopped'})
|
||||||
with bt_lock:
|
with bt_lock:
|
||||||
bt_process = None
|
bt_process = None
|
||||||
@@ -5596,6 +5712,29 @@ def start_bt_scan():
|
|||||||
else:
|
else:
|
||||||
return jsonify({'status': 'error', 'message': f'Unknown scan mode: {scan_mode}'})
|
return jsonify({'status': 'error', 'message': f'Unknown scan mode: {scan_mode}'})
|
||||||
|
|
||||||
|
# Wait briefly to check if process fails immediately
|
||||||
|
import time
|
||||||
|
time.sleep(0.5)
|
||||||
|
|
||||||
|
if bt_process.poll() is not None:
|
||||||
|
# Process already exited - capture error
|
||||||
|
stderr_output = bt_process.stderr.read().decode('utf-8', errors='replace').strip()
|
||||||
|
stdout_output = bt_process.stdout.read().decode('utf-8', errors='replace').strip()
|
||||||
|
exit_code = bt_process.returncode
|
||||||
|
bt_process = None
|
||||||
|
|
||||||
|
error_msg = stderr_output or stdout_output or f'Process exited with code {exit_code}'
|
||||||
|
|
||||||
|
# Common error explanations
|
||||||
|
if 'No such device' in error_msg or 'hci0' in error_msg.lower():
|
||||||
|
error_msg = f'Bluetooth interface "{interface}" not found or not available.'
|
||||||
|
elif 'Operation not permitted' in error_msg or 'Permission denied' in error_msg:
|
||||||
|
error_msg = 'Permission denied. Try running with sudo or add user to bluetooth group.'
|
||||||
|
elif 'busy' in error_msg.lower():
|
||||||
|
error_msg = f'Bluetooth interface "{interface}" is busy. Stop other Bluetooth operations first.'
|
||||||
|
|
||||||
|
return jsonify({'status': 'error', 'message': error_msg})
|
||||||
|
|
||||||
# Start streaming thread
|
# Start streaming thread
|
||||||
thread = threading.Thread(target=stream_bt_scan, args=(bt_process, scan_mode))
|
thread = threading.Thread(target=stream_bt_scan, args=(bt_process, scan_mode))
|
||||||
thread.daemon = True
|
thread.daemon = True
|
||||||
@@ -5605,7 +5744,8 @@ def start_bt_scan():
|
|||||||
return jsonify({'status': 'started', 'mode': scan_mode, 'interface': interface})
|
return jsonify({'status': 'started', 'mode': scan_mode, 'interface': interface})
|
||||||
|
|
||||||
except FileNotFoundError as e:
|
except FileNotFoundError as e:
|
||||||
return jsonify({'status': 'error', 'message': f'Tool not found: {e.filename}'})
|
tool_name = e.filename or scan_mode
|
||||||
|
return jsonify({'status': 'error', 'message': f'Tool "{tool_name}" not found. Install required Bluetooth tools.'})
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return jsonify({'status': 'error', 'message': str(e)})
|
return jsonify({'status': 'error', 'message': str(e)})
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user