""" 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] return VENDOR_OUIS.get(oui) # ============================================================================= # 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