Files
intercept/utils/wifi/parsers/iwlist.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

208 lines
6.4 KiB
Python

"""
Parser for Linux iwlist scan output.
Example output from 'iwlist wlan0 scan':
wlan0 Scan completed :
Cell 01 - Address: 00:11:22:33:44:55
Channel:6
Frequency:2.437 GHz (Channel 6)
Quality=70/70 Signal level=-40 dBm
Encryption key:on
ESSID:"MyWiFi"
Bit Rates:54 Mb/s
Mode:Master
Extra:tsf=0000000000000000
Extra: Last beacon: 100ms ago
IE: Unknown: 000A4D79576946695F4E6574
IE: IEEE 802.11i/WPA2 Version 1
Group Cipher : CCMP
Pairwise Ciphers (1) : CCMP
Authentication Suites (1) : PSK
"""
from __future__ import annotations
import logging
import re
from datetime import datetime
from ..constants import (
AUTH_EAP,
AUTH_OPEN,
AUTH_PSK,
AUTH_UNKNOWN,
CHANNEL_FREQUENCIES,
CIPHER_CCMP,
CIPHER_TKIP,
CIPHER_UNKNOWN,
CIPHER_WEP,
SECURITY_OPEN,
SECURITY_WEP,
SECURITY_WPA,
SECURITY_WPA2,
SECURITY_WPA_WPA2,
get_channel_from_frequency,
)
from ..models import WiFiObservation
logger = logging.getLogger(__name__)
def parse_iwlist_scan(output: str) -> list[WiFiObservation]:
"""
Parse iwlist scan output.
Args:
output: Raw output from 'iwlist <interface> scan'.
Returns:
List of WiFiObservation objects.
"""
observations = []
current_block = []
for line in output.split("\n"):
# New cell starts with "Cell XX - Address:"
if re.match(r"\s*Cell \d+ - Address:", line):
if current_block:
obs = _parse_iwlist_block(current_block)
if obs:
observations.append(obs)
current_block = [line]
elif current_block:
current_block.append(line)
# Parse last block
if current_block:
obs = _parse_iwlist_block(current_block)
if obs:
observations.append(obs)
return observations
def _parse_iwlist_block(lines: list[str]) -> WiFiObservation | None:
"""Parse a single Cell block from iwlist output."""
try:
# Extract BSSID from first line
first_line = lines[0]
bssid_match = re.search(r"Address:\s*([0-9A-Fa-f:]{17})", first_line)
if not bssid_match:
return None
bssid = bssid_match.group(1).upper()
# Parse remaining fields
ssid = None
frequency_mhz = None
channel = None
rssi = None
has_encryption = False
has_wpa = False
has_wpa2 = False
cipher = CIPHER_UNKNOWN
auth = AUTH_UNKNOWN
for line in lines[1:]:
line = line.strip()
# Channel
if line.startswith("Channel:"):
chan_match = re.search(r"Channel:(\d+)", line)
if chan_match:
channel = int(chan_match.group(1))
# Frequency
elif line.startswith("Frequency:"):
# Format: "Frequency:2.437 GHz (Channel 6)"
freq_match = re.search(r"Frequency:(\d+\.?\d*)\s*GHz", line)
if freq_match:
frequency_ghz = float(freq_match.group(1))
frequency_mhz = int(frequency_ghz * 1000)
# Also try to get channel from this line
chan_match = re.search(r"\(Channel (\d+)\)", line)
if chan_match and not channel:
channel = int(chan_match.group(1))
# Signal level
elif "Signal level" in line:
# Format: "Quality=70/70 Signal level=-40 dBm"
signal_match = re.search(r"Signal level[=:]?\s*(-?\d+)", line)
if signal_match:
rssi = int(signal_match.group(1))
# ESSID
elif line.startswith("ESSID:"):
ssid_match = re.search(r'ESSID:"([^"]*)"', line)
if ssid_match:
ssid = ssid_match.group(1)
if not ssid:
ssid = None
# Encryption
elif line.startswith("Encryption key:"):
has_encryption = "on" in line.lower()
# WPA/WPA2 IE
elif "WPA2" in line or "IEEE 802.11i" in line:
has_wpa2 = True
elif "WPA Version" in line:
has_wpa = True
# Cipher
elif "Group Cipher" in line or "Pairwise Ciphers" in line:
if "CCMP" in line:
cipher = CIPHER_CCMP
elif "TKIP" in line:
cipher = CIPHER_TKIP
# Auth
elif "Authentication Suites" in line:
if "PSK" in line:
auth = AUTH_PSK
elif "802.1x" in line.lower() or "EAP" in line:
auth = AUTH_EAP
# Derive channel from frequency if needed
if not channel and frequency_mhz:
channel = get_channel_from_frequency(frequency_mhz)
# Get frequency from channel if needed
if not frequency_mhz and channel:
frequency_mhz = CHANNEL_FREQUENCIES.get(channel)
# Determine security type
security = SECURITY_OPEN
if has_wpa2 and has_wpa:
security = SECURITY_WPA_WPA2
elif has_wpa2:
security = SECURITY_WPA2
elif has_wpa:
security = SECURITY_WPA
elif has_encryption:
security = SECURITY_WEP
cipher = CIPHER_WEP
if auth == AUTH_UNKNOWN:
if security == SECURITY_OPEN:
auth = AUTH_OPEN
elif security != SECURITY_WEP:
auth = AUTH_PSK
return WiFiObservation(
timestamp=datetime.now(),
bssid=bssid,
essid=ssid,
channel=channel,
frequency_mhz=frequency_mhz,
rssi=rssi,
security=security,
cipher=cipher,
auth=auth,
)
except Exception as e:
logger.debug(f"Failed to parse iwlist block: {e}")
return None