Let's get clients on the channel!

This commit is contained in:
Yaro Kasear 2025-04-11 12:54:09 -05:00
parent 81432e8e63
commit 8809059402

View file

@ -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()