diff --git a/kismet_enrich_from_pcap.py b/kismet_enrich_from_pcap.py index 1109f7a..760d407 100755 --- a/kismet_enrich_from_pcap.py +++ b/kismet_enrich_from_pcap.py @@ -4,6 +4,55 @@ import csv from datetime import datetime import pyshark +# United States regulatory domain channel lookup table + +CHANNEL_LOOKUP_TABLE = { + # 2.4 GHz (non-DFS, always allowed) + 1: {"freq": 2412, "dfs": False, "band": "2.4GHz"}, + 2: {"freq": 2417, "dfs": False, "band": "2.4GHz"}, + 3: {"freq": 2422, "dfs": False, "band": "2.4GHz"}, + 4: {"freq": 2427, "dfs": False, "band": "2.4GHz"}, + 5: {"freq": 2432, "dfs": False, "band": "2.4GHz"}, + 6: {"freq": 2437, "dfs": False, "band": "2.4GHz"}, + 7: {"freq": 2442, "dfs": False, "band": "2.4GHz"}, + 8: {"freq": 2447, "dfs": False, "band": "2.4GHz"}, + 9: {"freq": 2452, "dfs": False, "band": "2.4GHz"}, + 10: {"freq": 2457, "dfs": False, "band": "2.4GHz"}, + 11: {"freq": 2462, "dfs": False, "band": "2.4GHz"}, + + # 5 GHz UNII-1 (indoor only) + 36: {"freq": 5180, "dfs": False, "band": "UNII-1"}, + 40: {"freq": 5200, "dfs": False, "band": "UNII-1"}, + 44: {"freq": 5220, "dfs": False, "band": "UNII-1"}, + 48: {"freq": 5240, "dfs": False, "band": "UNII-1"}, + + # 5 GHz UNII-2 (DFS required) + 52: {"freq": 5260, "dfs": True, "band": "UNII-2"}, + 56: {"freq": 5280, "dfs": True, "band": "UNII-2"}, + 60: {"freq": 5300, "dfs": True, "band": "UNII-2"}, + 64: {"freq": 5320, "dfs": True, "band": "UNII-2"}, + + # 5 GHz UNII-2e (DFS required) + 100: {"freq": 5500, "dfs": True, "band": "UNII-2e"}, + 104: {"freq": 5520, "dfs": True, "band": "UNII-2e"}, + 108: {"freq": 5540, "dfs": True, "band": "UNII-2e"}, + 112: {"freq": 5560, "dfs": True, "band": "UNII-2e"}, + 116: {"freq": 5580, "dfs": True, "band": "UNII-2e"}, + 120: {"freq": 5600, "dfs": True, "band": "UNII-2e"}, + 124: {"freq": 5620, "dfs": True, "band": "UNII-2e"}, + 128: {"freq": 5640, "dfs": True, "band": "UNII-2e"}, + 132: {"freq": 5660, "dfs": True, "band": "UNII-2e"}, + 136: {"freq": 5680, "dfs": True, "band": "UNII-2e"}, + 140: {"freq": 5700, "dfs": True, "band": "UNII-2e"}, + + # 5 GHz UNII-3 (outdoor/indoor, no DFS) + 149: {"freq": 5745, "dfs": False, "band": "UNII-3"}, + 153: {"freq": 5765, "dfs": False, "band": "UNII-3"}, + 157: {"freq": 5785, "dfs": False, "band": "UNII-3"}, + 161: {"freq": 5805, "dfs": False, "band": "UNII-3"}, + 165: {"freq": 5825, "dfs": False, "band": "UNII-3"}, +} + def parse_args(): parser = argparse.ArgumentParser() parser.add_argument('--csv', required=True, help='Input speedtest CSV') @@ -45,29 +94,39 @@ def get_clients_on_ap(capture, ap_bssid): return len(clients) -def get_clients_on_channel(capture, ap_channel, ap_bssid): +def get_clients_on_channel(capture, ap_channel): + from_channel_freq = CHANNEL_LOOKUP_TABLE.get(ap_channel, {}).get('freq', None) + if not from_channel_freq: + print(f"[!] Invalid channel: {ap_channel}") + return 0 + clients = set() - ap_bssid = ap_bssid.lower() # Normalize for comparison - ap_channel = str(ap_channel) # Ensure channel is a string for comparison for packet in capture: try: - if not hasattr(packet, 'wlan'): + if 'radiotap' not in packet or 'wlan' not in packet: continue - channel = getattr(packet.wlan, 'channel', None) - sa = getattr(packet.wlan, 'sa', '').lower() - da = getattr(packet.wlan, 'da', '').lower() - bssid = getattr(packet.wlan, 'bssid', '').lower() + radio = packet.radiotap + wlan = packet.wlan - # Check if the packet is on the specified channel and not from the AP - if channel == ap_channel and (sa != ap_bssid and da != ap_bssid): - clients.add(sa) - clients.add(da) + # Printing the channel frequency for debugging + print(f"Channel Frequency: {getattr(radio, 'channel_freq', None)}") + + packet_freq = int(getattr(radio, 'channel_freq', -1)) + if packet_freq != from_channel_freq: + continue + + sa = getattr(wlan, 'sa', '').lower() + da = getattr(wlan, 'da', '').lower() + + for mac in (sa, da): + if mac and mac != 'ff:ff:ff:ff:ff:ff': + clients.add(mac.lower()) except AttributeError: continue - + return len(clients) def analyze_pcap(pcapng_path, start_ts, end_ts, ap_bssid, ap_channel): @@ -84,6 +143,8 @@ def analyze_pcap(pcapng_path, start_ts, end_ts, ap_bssid, ap_channel): # Get clients on the specified channel + clients_on_channel = get_clients_on_channel(cap, ap_channel) + # Placeholder: Logic will be added for: # - APsOnChannel # - CongestionScore @@ -93,7 +154,7 @@ def analyze_pcap(pcapng_path, start_ts, end_ts, ap_bssid, ap_channel): cap.close() - return clients_on_ap, 0, 0, None, None, None, 0 + return clients_on_ap, clients_on_channel, 0, None, None, None, 0 def main(): args = parse_args()