Files
intercept/utils/wifi/constants.py
T
James Smith 96172ca593 style: apply ruff-format to entire codebase
First-time run of ruff-format via pre-commit hook normalises quote
style, trailing commas, and whitespace across 188 Python files.
No logic changes.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-05 14:48:11 +01:00

569 lines
14 KiB
Python

"""
WiFi-specific constants for the unified scanner.
"""
from __future__ import annotations
# =============================================================================
# SCANNER SETTINGS
# =============================================================================
# Default quick scan timeout in seconds
DEFAULT_QUICK_SCAN_TIMEOUT = 15
# Default deep scan channel dwell time (seconds)
DEFAULT_CHANNEL_DWELL_TIME = 2
# Maximum RSSI samples to keep per network
MAX_RSSI_SAMPLES = 300
# Network expiration time (seconds since last seen)
NETWORK_STALE_TIMEOUT = 300 # 5 minutes
# Client expiration time (seconds since last seen)
CLIENT_STALE_TIMEOUT = 180 # 3 minutes
# Probe request retention time (seconds)
PROBE_REQUEST_RETENTION = 600 # 10 minutes
# =============================================================================
# WIFI BANDS
# =============================================================================
BAND_2_4_GHZ = "2.4GHz"
BAND_5_GHZ = "5GHz"
BAND_6_GHZ = "6GHz"
BAND_UNKNOWN = "unknown"
# =============================================================================
# WIFI BAND CHANNEL MAPPINGS
# =============================================================================
# 2.4 GHz channels (1-14)
CHANNELS_2_4_GHZ = list(range(1, 15))
# 5 GHz channels (UNII-1, UNII-2A, UNII-2C, UNII-3)
CHANNELS_5_GHZ = [
# UNII-1 (5150-5250 MHz)
36,
40,
44,
48,
# UNII-2A (5250-5350 MHz) - DFS
52,
56,
60,
64,
# UNII-2C (5470-5725 MHz) - DFS
100,
104,
108,
112,
116,
120,
124,
128,
132,
136,
140,
144,
# UNII-3 (5725-5850 MHz)
149,
153,
157,
161,
165,
]
# 6 GHz channels (Wi-Fi 6E)
CHANNELS_6_GHZ = [
1,
5,
9,
13,
17,
21,
25,
29,
33,
37,
41,
45,
49,
53,
57,
61,
65,
69,
73,
77,
81,
85,
89,
93,
97,
101,
105,
109,
113,
117,
121,
125,
129,
133,
137,
141,
145,
149,
153,
157,
161,
165,
169,
173,
177,
181,
185,
189,
193,
197,
201,
205,
209,
213,
217,
221,
225,
229,
233,
]
# Non-overlapping channels for recommendations
NON_OVERLAPPING_2_4_GHZ = [1, 6, 11]
NON_OVERLAPPING_5_GHZ = [36, 40, 44, 48, 149, 153, 157, 161, 165] # Non-DFS
# Channel to frequency mappings (MHz)
CHANNEL_FREQUENCIES = {
# 2.4 GHz
1: 2412,
2: 2417,
3: 2422,
4: 2427,
5: 2432,
6: 2437,
7: 2442,
8: 2447,
9: 2452,
10: 2457,
11: 2462,
12: 2467,
13: 2472,
14: 2484,
# 5 GHz
36: 5180,
40: 5200,
44: 5220,
48: 5240,
52: 5260,
56: 5280,
60: 5300,
64: 5320,
100: 5500,
104: 5520,
108: 5540,
112: 5560,
116: 5580,
120: 5600,
124: 5620,
128: 5640,
132: 5660,
136: 5680,
140: 5700,
144: 5720,
149: 5745,
153: 5765,
157: 5785,
161: 5805,
165: 5825,
}
# Frequency to channel reverse mapping
FREQUENCY_CHANNELS = {v: k for k, v in CHANNEL_FREQUENCIES.items()}
def get_band_from_channel(channel: int) -> str:
"""Get WiFi band from channel number."""
if 1 <= channel <= 14:
return BAND_2_4_GHZ
elif channel in CHANNELS_5_GHZ:
return BAND_5_GHZ
elif channel in CHANNELS_6_GHZ:
return BAND_6_GHZ
return BAND_UNKNOWN
def get_band_from_frequency(frequency_mhz: int) -> str:
"""Get WiFi band from frequency in MHz."""
if 2400 <= frequency_mhz <= 2500:
return BAND_2_4_GHZ
elif 5150 <= frequency_mhz <= 5850:
return BAND_5_GHZ
elif 5925 <= frequency_mhz <= 7125:
return BAND_6_GHZ
return BAND_UNKNOWN
def get_channel_from_frequency(frequency_mhz: int) -> int | None:
"""Get channel number from frequency in MHz."""
return FREQUENCY_CHANNELS.get(frequency_mhz)
# =============================================================================
# SECURITY TYPES
# =============================================================================
SECURITY_OPEN = "Open"
SECURITY_WEP = "WEP"
SECURITY_WPA = "WPA"
SECURITY_WPA2 = "WPA2"
SECURITY_WPA3 = "WPA3"
SECURITY_WPA_WPA2 = "WPA/WPA2"
SECURITY_WPA2_WPA3 = "WPA2/WPA3"
SECURITY_ENTERPRISE = "Enterprise"
SECURITY_UNKNOWN = "Unknown"
# Security type priority (higher = more secure)
SECURITY_PRIORITY = {
SECURITY_OPEN: 0,
SECURITY_WEP: 1,
SECURITY_WPA: 2,
SECURITY_WPA_WPA2: 3,
SECURITY_WPA2: 4,
SECURITY_WPA2_WPA3: 5,
SECURITY_WPA3: 6,
SECURITY_ENTERPRISE: 7,
SECURITY_UNKNOWN: -1,
}
# =============================================================================
# CIPHER TYPES
# =============================================================================
CIPHER_NONE = "None"
CIPHER_WEP = "WEP"
CIPHER_TKIP = "TKIP"
CIPHER_CCMP = "CCMP"
CIPHER_GCMP = "GCMP"
CIPHER_UNKNOWN = "Unknown"
# =============================================================================
# AUTHENTICATION TYPES
# =============================================================================
AUTH_OPEN = "Open"
AUTH_PSK = "PSK"
AUTH_SAE = "SAE"
AUTH_EAP = "EAP"
AUTH_OWE = "OWE"
AUTH_UNKNOWN = "Unknown"
# =============================================================================
# CHANNEL WIDTH
# =============================================================================
WIDTH_20_MHZ = "20MHz"
WIDTH_40_MHZ = "40MHz"
WIDTH_80_MHZ = "80MHz"
WIDTH_160_MHZ = "160MHz"
WIDTH_320_MHZ = "320MHz"
WIDTH_UNKNOWN = "Unknown"
# =============================================================================
# SIGNAL STRENGTH BANDS (for proximity radar)
# =============================================================================
SIGNAL_STRONG = "strong" # >= -50 dBm
SIGNAL_MEDIUM = "medium" # -50 to -70 dBm
SIGNAL_WEAK = "weak" # -70 to -85 dBm
SIGNAL_VERY_WEAK = "very_weak" # < -85 dBm
SIGNAL_UNKNOWN = "unknown"
# RSSI thresholds for signal bands
RSSI_STRONG = -50
RSSI_MEDIUM = -70
RSSI_WEAK = -85
def get_signal_band(rssi: int | None) -> str:
"""Get signal band from RSSI value."""
if rssi is None:
return SIGNAL_UNKNOWN
if rssi >= RSSI_STRONG:
return SIGNAL_STRONG
elif rssi >= RSSI_MEDIUM:
return SIGNAL_MEDIUM
elif rssi >= RSSI_WEAK:
return SIGNAL_WEAK
return SIGNAL_VERY_WEAK
# =============================================================================
# PROXIMITY BANDS (consistent with Bluetooth)
# =============================================================================
PROXIMITY_IMMEDIATE = "immediate" # < 3m
PROXIMITY_NEAR = "near" # 3-10m
PROXIMITY_FAR = "far" # > 10m
PROXIMITY_UNKNOWN = "unknown"
# RSSI thresholds for proximity band classification
PROXIMITY_RSSI_IMMEDIATE = -55 # >= -55 dBm -> immediate
PROXIMITY_RSSI_NEAR = -70 # >= -70 dBm -> near
def get_proximity_band(rssi: int | None) -> str:
"""Get proximity band from RSSI value."""
if rssi is None:
return PROXIMITY_UNKNOWN
if rssi >= PROXIMITY_RSSI_IMMEDIATE:
return PROXIMITY_IMMEDIATE
elif rssi >= PROXIMITY_RSSI_NEAR:
return PROXIMITY_NEAR
return PROXIMITY_FAR
# =============================================================================
# DISTANCE ESTIMATION (WiFi-specific)
# =============================================================================
# Path-loss exponent for indoor WiFi (typically 2.5-4.0)
WIFI_PATH_LOSS_EXPONENT = 3.0
# Reference RSSI at 1 meter (typical WiFi AP)
WIFI_RSSI_AT_1M = -40
# EMA smoothing alpha for RSSI
WIFI_EMA_ALPHA = 0.3
# =============================================================================
# SCAN MODES
# =============================================================================
SCAN_MODE_QUICK = "quick" # Uses system tools (no monitor mode)
SCAN_MODE_DEEP = "deep" # Uses airodump-ng (monitor mode required)
# =============================================================================
# TOOL DETECTION
# =============================================================================
# Quick scan tools (by platform priority)
QUICK_SCAN_TOOLS_LINUX = ["nmcli", "iw", "iwlist"]
QUICK_SCAN_TOOLS_DARWIN = ["airport"]
# Deep scan tools
DEEP_SCAN_TOOLS = ["airodump-ng"]
# Monitor mode tools
MONITOR_MODE_TOOLS = ["airmon-ng", "iw"]
# Tool command timeouts (seconds)
TOOL_TIMEOUT_QUICK = 30.0
TOOL_TIMEOUT_DETECT = 5.0
# =============================================================================
# AIRODUMP-NG SETTINGS
# =============================================================================
AIRODUMP_OUTPUT_PREFIX = "airodump_wifi"
AIRODUMP_POLL_INTERVAL = 1.0 # seconds between CSV reads
# =============================================================================
# HEURISTIC FLAGS
# =============================================================================
HEURISTIC_HIDDEN = "hidden"
HEURISTIC_ROGUE_AP = "rogue_ap"
HEURISTIC_EVIL_TWIN = "evil_twin"
HEURISTIC_BEACON_FLOOD = "beacon_flood"
HEURISTIC_WEAK_SECURITY = "weak_security"
HEURISTIC_DEAUTH_DETECTED = "deauth_detected"
HEURISTIC_NEW = "new"
HEURISTIC_PERSISTENT = "persistent"
HEURISTIC_STRONG_STABLE = "strong_stable"
# Thresholds
BEACON_FLOOD_THRESHOLD = 50 # Same BSSID seen > 50 times/minute
PERSISTENT_MIN_SEEN = 10
PERSISTENT_WINDOW_SECONDS = 300
STRONG_RSSI_THRESHOLD = -50
STABLE_VARIANCE_THRESHOLD = 5.0
# =============================================================================
# COMMON VENDOR OUI PREFIXES (first 3 bytes of MAC)
# =============================================================================
VENDOR_OUIS = {
"00:00:5E": "IANA",
"00:03:93": "Apple",
"00:0A:95": "Apple",
"00:0D:93": "Apple",
"00:11:24": "Apple",
"00:14:51": "Apple",
"00:16:CB": "Apple",
"00:17:F2": "Apple",
"00:19:E3": "Apple",
"00:1B:63": "Apple",
"00:1C:B3": "Apple",
"00:1D:4F": "Apple",
"00:1E:52": "Apple",
"00:1E:C2": "Apple",
"00:1F:5B": "Apple",
"00:1F:F3": "Apple",
"00:21:E9": "Apple",
"00:22:41": "Apple",
"00:23:12": "Apple",
"00:23:32": "Apple",
"00:23:6C": "Apple",
"00:23:DF": "Apple",
"00:24:36": "Apple",
"00:25:00": "Apple",
"00:25:4B": "Apple",
"00:25:BC": "Apple",
"00:26:08": "Apple",
"00:26:4A": "Apple",
"00:26:B0": "Apple",
"00:26:BB": "Apple",
"00:50:F2": "Microsoft",
"00:15:5D": "Microsoft",
"00:17:FA": "Microsoft",
"00:1D:D8": "Microsoft",
"00:50:56": "VMware",
"00:0C:29": "VMware",
"00:05:69": "VMware",
"08:00:27": "VirtualBox",
"00:1C:42": "Parallels",
"00:16:3E": "Xen",
"DC:A6:32": "Raspberry Pi",
"B8:27:EB": "Raspberry Pi",
"E4:5F:01": "Raspberry Pi",
"28:CD:C1": "Raspberry Pi",
"00:1A:11": "Google",
"00:1A:22": "Google",
"3C:5A:B4": "Google",
"54:60:09": "Google",
"94:EB:2C": "Google",
"F4:F5:D8": "Google",
"00:17:C4": "Netgear",
"00:1B:2F": "Netgear",
"00:1E:2A": "Netgear",
"00:22:3F": "Netgear",
"00:24:B2": "Netgear",
"00:26:F2": "Netgear",
"00:18:F8": "Cisco",
"00:1A:A1": "Cisco",
"00:1B:0C": "Cisco",
"00:1B:D4": "Cisco",
"00:1C:0E": "Cisco",
"00:1C:57": "Cisco",
"00:40:96": "Cisco",
"00:50:54": "Cisco",
"00:60:5C": "Cisco",
"E8:65:D4": "Ubiquiti",
"FC:EC:DA": "Ubiquiti",
"00:27:22": "Ubiquiti",
"04:18:D6": "Ubiquiti",
"18:E8:29": "Ubiquiti",
"24:A4:3C": "Ubiquiti",
"44:D9:E7": "Ubiquiti",
"68:72:51": "Ubiquiti",
"74:83:C2": "Ubiquiti",
"78:8A:20": "Ubiquiti",
"B4:FB:E4": "Ubiquiti",
"F0:9F:C2": "Ubiquiti",
"00:0C:F1": "Intel",
"00:13:02": "Intel",
"00:13:20": "Intel",
"00:13:CE": "Intel",
"00:13:E8": "Intel",
"00:15:00": "Intel",
"00:15:17": "Intel",
"00:16:6F": "Intel",
"00:16:76": "Intel",
"00:16:EA": "Intel",
"00:16:EB": "Intel",
"00:18:DE": "Intel",
"00:19:D1": "Intel",
"00:19:D2": "Intel",
"00:1B:21": "Intel",
"00:1B:77": "Intel",
"00:1C:BF": "Intel",
"00:1D:E0": "Intel",
"00:1D:E1": "Intel",
"00:1E:64": "Intel",
"00:1E:65": "Intel",
"00:1E:67": "Intel",
"00:1F:3B": "Intel",
"00:1F:3C": "Intel",
"00:20:E0": "TP-Link",
"00:23:CD": "TP-Link",
"00:25:86": "TP-Link",
"00:27:19": "TP-Link",
"14:CC:20": "TP-Link",
"14:CF:92": "TP-Link",
"18:A6:F7": "TP-Link",
"1C:3B:F3": "TP-Link",
"30:B5:C2": "TP-Link",
"50:C7:BF": "TP-Link",
"54:C8:0F": "TP-Link",
"60:E3:27": "TP-Link",
"64:56:01": "TP-Link",
"64:66:B3": "TP-Link",
"64:70:02": "TP-Link",
}
def get_vendor_from_mac(mac: str) -> str | None:
"""Get vendor name from MAC address OUI."""
if not mac:
return None
# Normalize MAC format
mac_upper = mac.upper().replace("-", ":")
oui = mac_upper[:8]
vendor = VENDOR_OUIS.get(oui)
if vendor:
return vendor
# Fallback to expanded OUI database if available
try:
from data.oui import get_manufacturer
manufacturer = get_manufacturer(mac_upper)
if manufacturer and manufacturer != "Unknown":
return manufacturer
except Exception:
return None
return None
# =============================================================================
# HIDDEN SSID CORRELATION
# =============================================================================
# Time window for correlating probe requests with hidden AP associations
HIDDEN_CORRELATION_WINDOW_SECONDS = 60
# Minimum confidence for hidden SSID revelation
HIDDEN_MIN_CORRELATION_CONFIDENCE = 0.7
# =============================================================================
# CHANNEL ANALYSIS
# =============================================================================
# Weights for channel utilization scoring
CHANNEL_WEIGHT_AP_COUNT = 0.6
CHANNEL_WEIGHT_CLIENT_COUNT = 0.4
# RSSI adjustment factor (stronger signals = more interference)
CHANNEL_RSSI_INTERFERENCE_FACTOR = 0.1