Compare commits
2 Commits
bb6bd568ce
...
c73e694b9a
| Author | SHA1 | Date |
|---|---|---|
|
|
c73e694b9a | |
|
|
8ba4bff040 |
|
|
@ -0,0 +1,40 @@
|
||||||
|
# Installing Scapy on Raspberry Pi 5
|
||||||
|
|
||||||
|
## Option 1: Install via apt (Recommended)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo apt-get install python3-scapy
|
||||||
|
```
|
||||||
|
|
||||||
|
This is the safest method and integrates with the system package manager.
|
||||||
|
|
||||||
|
## Option 2: Use --break-system-packages (For system tools)
|
||||||
|
|
||||||
|
Since this is a system-level monitoring tool that runs as root, you can use:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo pip3 install --break-system-packages scapy
|
||||||
|
```
|
||||||
|
|
||||||
|
## Option 3: Virtual Environment (Not recommended for root tools)
|
||||||
|
|
||||||
|
If you prefer a virtual environment (though less convenient for root tools):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python3 -m venv ~/scapy-env
|
||||||
|
source ~/scapy-env/bin/activate
|
||||||
|
pip install scapy
|
||||||
|
```
|
||||||
|
|
||||||
|
Then run the script with:
|
||||||
|
```bash
|
||||||
|
sudo ~/scapy-env/bin/python3 rpi_capture_ra_ta_python.py 11
|
||||||
|
```
|
||||||
|
|
||||||
|
## Quick Install Command
|
||||||
|
|
||||||
|
For this use case (system monitoring tool), Option 2 is acceptable:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo pip3 install --break-system-packages scapy
|
||||||
|
```
|
||||||
|
|
@ -0,0 +1,216 @@
|
||||||
|
# Raspberry Pi 5 WiFi Monitor Mode Guide
|
||||||
|
|
||||||
|
## Quick Setup
|
||||||
|
|
||||||
|
1. **Copy the setup script to your Raspberry Pi:**
|
||||||
|
```bash
|
||||||
|
scp rpi_monitor_setup.sh pi@your-pi-ip:~/
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **SSH into your Raspberry Pi and run:**
|
||||||
|
```bash
|
||||||
|
sudo ./rpi_monitor_setup.sh [channel]
|
||||||
|
```
|
||||||
|
|
||||||
|
Example for channel 11:
|
||||||
|
```bash
|
||||||
|
sudo ./rpi_monitor_setup.sh 11
|
||||||
|
```
|
||||||
|
|
||||||
|
## Manual Setup (Alternative)
|
||||||
|
|
||||||
|
If you prefer to run commands manually:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. Check available interfaces
|
||||||
|
iw dev
|
||||||
|
|
||||||
|
# 2. Bring down the interface
|
||||||
|
sudo ip link set wlan0 down
|
||||||
|
|
||||||
|
# 3. Set to monitor mode
|
||||||
|
sudo iw dev wlan0 set type monitor
|
||||||
|
|
||||||
|
# 4. Bring up the interface
|
||||||
|
sudo ip link set wlan0 up
|
||||||
|
|
||||||
|
# 5. Set channel (e.g., channel 11)
|
||||||
|
sudo iw dev wlan0 set channel 11
|
||||||
|
|
||||||
|
# 6. Verify monitor mode
|
||||||
|
iw dev wlan0 info
|
||||||
|
```
|
||||||
|
|
||||||
|
## Capturing Packets
|
||||||
|
|
||||||
|
Once monitor mode is active, you can capture packets:
|
||||||
|
|
||||||
|
### Using tcpdump (simple)
|
||||||
|
```bash
|
||||||
|
# View packets in real-time
|
||||||
|
sudo tcpdump -i wlan0 -n
|
||||||
|
|
||||||
|
# Save to file
|
||||||
|
sudo tcpdump -i wlan0 -w capture.pcap
|
||||||
|
|
||||||
|
# Filter by MAC address (e.g., your Pi's MAC)
|
||||||
|
sudo tcpdump -i wlan0 -n ether host 80:84:89:93:c4:b6
|
||||||
|
|
||||||
|
# Filter by channel (if using multiple interfaces)
|
||||||
|
sudo tcpdump -i wlan0 -n -c 100 # Capture 100 packets
|
||||||
|
```
|
||||||
|
|
||||||
|
### Using airodump-ng (advanced, requires aircrack-ng)
|
||||||
|
```bash
|
||||||
|
# Install aircrack-ng if needed
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get install aircrack-ng
|
||||||
|
|
||||||
|
# Capture on specific channel
|
||||||
|
sudo airodump-ng wlan0 -c 11
|
||||||
|
|
||||||
|
# Save to file
|
||||||
|
sudo airodump-ng wlan0 -c 11 -w capture
|
||||||
|
```
|
||||||
|
|
||||||
|
### Using Wireshark (GUI)
|
||||||
|
```bash
|
||||||
|
# Install wireshark if needed
|
||||||
|
sudo apt-get install wireshark
|
||||||
|
|
||||||
|
# Run wireshark (may need to add user to wireshark group)
|
||||||
|
sudo wireshark -i wlan0
|
||||||
|
```
|
||||||
|
|
||||||
|
## Capturing RA/TA Addresses
|
||||||
|
|
||||||
|
### Quick Capture Script (Recommended)
|
||||||
|
|
||||||
|
Use the provided Python script for best results:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Install scapy if needed
|
||||||
|
sudo apt-get install python3-pip
|
||||||
|
sudo pip3 install scapy
|
||||||
|
|
||||||
|
# Capture on channel 11 (shows all frames with RA/TA)
|
||||||
|
sudo python3 rpi_capture_ra_ta_python.py 11
|
||||||
|
|
||||||
|
# Capture and filter by specific MAC address
|
||||||
|
sudo python3 rpi_capture_ra_ta_python.py 11 80:84:89:93:c4:b6
|
||||||
|
```
|
||||||
|
|
||||||
|
The script will:
|
||||||
|
- Automatically set monitor mode
|
||||||
|
- Parse 802.11 frames correctly
|
||||||
|
- Display RA (Receiver Address) and TA (Transmitter Address)
|
||||||
|
- Show frame type, RSSI, length, and QoS info
|
||||||
|
- Provide statistics when stopped (Ctrl+C)
|
||||||
|
|
||||||
|
### Alternative: Bash Script
|
||||||
|
|
||||||
|
For a simpler bash-based solution:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Capture on channel 11
|
||||||
|
sudo ./rpi_capture_ra_ta.sh 11
|
||||||
|
|
||||||
|
# Capture and filter by MAC
|
||||||
|
sudo ./rpi_capture_ra_ta.sh 11 80:84:89:93:c4:b6
|
||||||
|
```
|
||||||
|
|
||||||
|
## Monitoring Specific Traffic
|
||||||
|
|
||||||
|
### Filter by MAC address (TA/RA)
|
||||||
|
```bash
|
||||||
|
# Capture frames from specific transmitter (TA)
|
||||||
|
sudo tcpdump -i wlan0 -n ether src 80:84:89:93:c4:b6
|
||||||
|
|
||||||
|
# Capture frames to specific receiver (RA)
|
||||||
|
sudo tcpdump -i wlan0 -n ether dst e0:46:ee:07:df:e1
|
||||||
|
|
||||||
|
# Capture frames involving either address
|
||||||
|
sudo tcpdump -i wlan0 -n "ether host 80:84:89:93:c4:b6 or ether host e0:46:ee:07:df:e1"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Filter by frame type
|
||||||
|
```bash
|
||||||
|
# Data frames only
|
||||||
|
sudo tcpdump -i wlan0 -n "type wlan type data"
|
||||||
|
|
||||||
|
# Management frames (beacons, probes, etc.)
|
||||||
|
sudo tcpdump -i wlan0 -n "type wlan type mgt"
|
||||||
|
|
||||||
|
# Control frames (RTS, CTS, ACK)
|
||||||
|
sudo tcpdump -i wlan0 -n "type wlan type ctl"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Restoring Normal WiFi
|
||||||
|
|
||||||
|
To restore normal WiFi operation:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Bring down interface
|
||||||
|
sudo ip link set wlan0 down
|
||||||
|
|
||||||
|
# Set back to managed mode
|
||||||
|
sudo iw dev wlan0 set type managed
|
||||||
|
|
||||||
|
# Bring up interface
|
||||||
|
sudo ip link set wlan0 up
|
||||||
|
|
||||||
|
# Reconnect to your network (use NetworkManager, wpa_supplicant, etc.)
|
||||||
|
sudo nmcli device wifi connect "YourSSID" password "YourPassword"
|
||||||
|
# OR
|
||||||
|
sudo wpa_supplicant -i wlan0 -c /etc/wpa_supplicant/wpa_supplicant.conf &
|
||||||
|
sudo dhclient wlan0
|
||||||
|
```
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Interface not found
|
||||||
|
```bash
|
||||||
|
# List all network interfaces
|
||||||
|
ip link show
|
||||||
|
|
||||||
|
# Check WiFi interfaces specifically
|
||||||
|
iw dev
|
||||||
|
```
|
||||||
|
|
||||||
|
### Permission denied
|
||||||
|
- Make sure you're using `sudo` for all monitor mode commands
|
||||||
|
- Some distributions require adding your user to specific groups
|
||||||
|
|
||||||
|
### Can't set monitor mode
|
||||||
|
- Some WiFi adapters don't support monitor mode
|
||||||
|
- Check adapter capabilities: `iw phy | grep -A 10 "Supported interface modes"`
|
||||||
|
- Raspberry Pi 5 built-in WiFi should support monitor mode
|
||||||
|
|
||||||
|
### Channel not changing
|
||||||
|
- Make sure the interface is up: `sudo ip link set wlan0 up`
|
||||||
|
- Try bringing it down first, then setting channel, then bringing it up
|
||||||
|
|
||||||
|
## Useful Commands
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Check current interface status
|
||||||
|
iw dev wlan0 info
|
||||||
|
|
||||||
|
# Scan for networks (won't work in monitor mode, but useful before switching)
|
||||||
|
iw dev wlan0 scan
|
||||||
|
|
||||||
|
# Check signal strength and link info (before switching to monitor mode)
|
||||||
|
iw dev wlan0 link
|
||||||
|
|
||||||
|
# Monitor channel activity
|
||||||
|
watch -n 1 "iw dev wlan0 info | grep channel"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Comparing with ESP32 Monitor
|
||||||
|
|
||||||
|
When comparing captures between your ESP32 and Raspberry Pi:
|
||||||
|
|
||||||
|
1. **Ensure same channel**: Both devices must monitor the same channel
|
||||||
|
2. **Time sync**: Consider using NTP for accurate timestamp comparison
|
||||||
|
3. **MAC filtering**: Use tcpdump filters to match your ESP32's filter settings
|
||||||
|
4. **Frame types**: Both should capture the same frame types (data, management, control)
|
||||||
|
|
@ -54,6 +54,11 @@ static struct {
|
||||||
struct arg_end *end;
|
struct arg_end *end;
|
||||||
} debug_args;
|
} debug_args;
|
||||||
|
|
||||||
|
static struct {
|
||||||
|
struct arg_str *mac;
|
||||||
|
struct arg_end *end;
|
||||||
|
} filter_args;
|
||||||
|
|
||||||
static void print_monitor_usage(void) {
|
static void print_monitor_usage(void) {
|
||||||
printf("Usage: monitor <subcommand> [args]\n");
|
printf("Usage: monitor <subcommand> [args]\n");
|
||||||
printf("Subcommands:\n");
|
printf("Subcommands:\n");
|
||||||
|
|
@ -62,6 +67,7 @@ static void print_monitor_usage(void) {
|
||||||
printf(" status Show current status\n");
|
printf(" status Show current status\n");
|
||||||
printf(" channel <n> Switch channel (while running)\n");
|
printf(" channel <n> Switch channel (while running)\n");
|
||||||
printf(" debug [on|off] Enable/disable debug logging (default: show status)\n");
|
printf(" debug [on|off] Enable/disable debug logging (default: show status)\n");
|
||||||
|
printf(" filter [mac] Set MAC address filter for debug (e.g., 80:84:89:93:c4:b6), or 'clear' to disable\n");
|
||||||
printf(" save Save current config to NVS\n");
|
printf(" save Save current config to NVS\n");
|
||||||
printf(" reload Reload config from NVS\n");
|
printf(" reload Reload config from NVS\n");
|
||||||
printf(" clear Clear NVS config\n");
|
printf(" clear Clear NVS config\n");
|
||||||
|
|
@ -139,6 +145,73 @@ static int do_monitor_debug(int argc, char **argv) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int do_monitor_filter(int argc, char **argv) {
|
||||||
|
filter_args.mac = arg_str0(NULL, NULL, "<mac|clear>", "MAC address (XX:XX:XX:XX:XX:XX) or 'clear' to disable filter");
|
||||||
|
filter_args.end = arg_end(1);
|
||||||
|
|
||||||
|
int nerrors = arg_parse(argc, argv, (void **)&filter_args);
|
||||||
|
if (nerrors > 0) {
|
||||||
|
arg_print_errors(stderr, filter_args.end, argv[0]);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filter_args.mac->count > 0) {
|
||||||
|
const char *mac_str = filter_args.mac->sval[0];
|
||||||
|
|
||||||
|
if (strcmp(mac_str, "clear") == 0) {
|
||||||
|
wifi_ctl_set_monitor_debug_filter(NULL);
|
||||||
|
printf("Debug filter cleared\n");
|
||||||
|
} else {
|
||||||
|
/* Parse MAC address string (format: XX:XX:XX:XX:XX:XX) */
|
||||||
|
uint8_t mac[6];
|
||||||
|
int values[6];
|
||||||
|
int count = sscanf(mac_str, "%x:%x:%x:%x:%x:%x",
|
||||||
|
&values[0], &values[1], &values[2],
|
||||||
|
&values[3], &values[4], &values[5]);
|
||||||
|
|
||||||
|
if (count == 6) {
|
||||||
|
/* Validate values are in range */
|
||||||
|
bool valid = true;
|
||||||
|
for (int i = 0; i < 6; i++) {
|
||||||
|
if (values[i] < 0 || values[i] > 255) {
|
||||||
|
valid = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
mac[i] = (uint8_t)values[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (valid) {
|
||||||
|
if (wifi_ctl_set_monitor_debug_filter(mac) == ESP_OK) {
|
||||||
|
printf("Debug filter set to: %02x:%02x:%02x:%02x:%02x:%02x\n",
|
||||||
|
mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
|
||||||
|
} else {
|
||||||
|
printf("Failed to set debug filter\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
printf("Invalid MAC address: values must be 0-255\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
printf("Invalid MAC address format. Use XX:XX:XX:XX:XX:XX or 'clear'\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
/* No argument: show current filter */
|
||||||
|
uint8_t mac[6];
|
||||||
|
bool enabled = wifi_ctl_get_monitor_debug_filter(mac);
|
||||||
|
if (enabled) {
|
||||||
|
printf("Debug filter: %02x:%02x:%02x:%02x:%02x:%02x\n",
|
||||||
|
mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
|
||||||
|
} else {
|
||||||
|
printf("Debug filter: disabled\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
static int cmd_monitor(int argc, char **argv) {
|
static int cmd_monitor(int argc, char **argv) {
|
||||||
if (argc < 2) {
|
if (argc < 2) {
|
||||||
print_monitor_usage();
|
print_monitor_usage();
|
||||||
|
|
@ -154,6 +227,7 @@ static int cmd_monitor(int argc, char **argv) {
|
||||||
|
|
||||||
if (strcmp(argv[1], "channel") == 0) return do_monitor_channel(argc - 1, &argv[1]);
|
if (strcmp(argv[1], "channel") == 0) return do_monitor_channel(argc - 1, &argv[1]);
|
||||||
if (strcmp(argv[1], "debug") == 0) return do_monitor_debug(argc - 1, &argv[1]);
|
if (strcmp(argv[1], "debug") == 0) return do_monitor_debug(argc - 1, &argv[1]);
|
||||||
|
if (strcmp(argv[1], "filter") == 0) return do_monitor_filter(argc - 1, &argv[1]);
|
||||||
|
|
||||||
if (strcmp(argv[1], "help") == 0 || strcmp(argv[1], "--help") == 0) {
|
if (strcmp(argv[1], "help") == 0 || strcmp(argv[1], "--help") == 0) {
|
||||||
print_monitor_usage();
|
print_monitor_usage();
|
||||||
|
|
@ -175,6 +249,9 @@ void register_monitor_cmd(void) {
|
||||||
debug_args.enable = arg_str0(NULL, NULL, "<on|off>", "Enable or disable debug logging");
|
debug_args.enable = arg_str0(NULL, NULL, "<on|off>", "Enable or disable debug logging");
|
||||||
debug_args.end = arg_end(1);
|
debug_args.end = arg_end(1);
|
||||||
|
|
||||||
|
filter_args.mac = arg_str0(NULL, NULL, "<mac|clear>", "MAC address filter or 'clear'");
|
||||||
|
filter_args.end = arg_end(1);
|
||||||
|
|
||||||
const esp_console_cmd_t cmd = {
|
const esp_console_cmd_t cmd = {
|
||||||
.command = "monitor",
|
.command = "monitor",
|
||||||
.help = "Monitor Mode: start, stop, channel, status",
|
.help = "Monitor Mode: start, stop, channel, status",
|
||||||
|
|
|
||||||
|
|
@ -673,6 +673,85 @@ void wifi_ctl_status(void) {
|
||||||
}
|
}
|
||||||
printf(" Frames: %lu, %.1f fps\n", (unsigned long)s_monitor_frame_count, frame_rate_fps);
|
printf(" Frames: %lu, %.1f fps\n", (unsigned long)s_monitor_frame_count, frame_rate_fps);
|
||||||
|
|
||||||
|
/* Show frame type breakdown */
|
||||||
|
wifi_collapse_stats_t monitor_stats;
|
||||||
|
if (wifi_monitor_get_stats(&monitor_stats) == ESP_OK) {
|
||||||
|
printf(" Frame Types: Data=%lu, Mgmt=%lu, Ctrl=%lu (RTS=%lu, CTS=%lu, ACK=%lu)\n",
|
||||||
|
(unsigned long)monitor_stats.data_frames,
|
||||||
|
(unsigned long)monitor_stats.mgmt_frames,
|
||||||
|
(unsigned long)(monitor_stats.rts_frames + monitor_stats.cts_frames + monitor_stats.ack_frames),
|
||||||
|
(unsigned long)monitor_stats.rts_frames,
|
||||||
|
(unsigned long)monitor_stats.cts_frames,
|
||||||
|
(unsigned long)monitor_stats.ack_frames);
|
||||||
|
|
||||||
|
/* Show histograms */
|
||||||
|
// AMPDU histogram
|
||||||
|
uint32_t total_ampdu = 0;
|
||||||
|
for (int i = 0; i < 8; i++) {
|
||||||
|
total_ampdu += monitor_stats.ampdu_hist[i];
|
||||||
|
}
|
||||||
|
if (total_ampdu > 0) {
|
||||||
|
printf(" AMPDU Aggregation: ");
|
||||||
|
const char *ampdu_labels[] = {"1", "2-4", "5-8", "9-16", "17-32", "33-48", "49-64", "65+"};
|
||||||
|
for (int i = 0; i < 8; i++) {
|
||||||
|
if (monitor_stats.ampdu_hist[i] > 0) {
|
||||||
|
printf("%s=%lu ", ampdu_labels[i], (unsigned long)monitor_stats.ampdu_hist[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
printf("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
// MCS histogram (show non-zero entries)
|
||||||
|
uint32_t total_mcs = 0;
|
||||||
|
for (int i = 0; i < 32; i++) {
|
||||||
|
total_mcs += monitor_stats.mcs_hist[i];
|
||||||
|
}
|
||||||
|
if (total_mcs > 0) {
|
||||||
|
printf(" MCS Distribution: ");
|
||||||
|
int printed = 0;
|
||||||
|
for (int i = 0; i < 32; i++) {
|
||||||
|
if (monitor_stats.mcs_hist[i] > 0) {
|
||||||
|
if (printed > 0) printf(", ");
|
||||||
|
printf("MCS%d=%lu", i, (unsigned long)monitor_stats.mcs_hist[i]);
|
||||||
|
printed++;
|
||||||
|
if (printed >= 10) { // Limit output length
|
||||||
|
printf(" ...");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
printf("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Spatial streams histogram
|
||||||
|
uint32_t total_ss = 0;
|
||||||
|
for (int i = 0; i < 8; i++) {
|
||||||
|
total_ss += monitor_stats.ss_hist[i];
|
||||||
|
}
|
||||||
|
if (total_ss > 0) {
|
||||||
|
printf(" Spatial Streams: ");
|
||||||
|
for (int i = 0; i < 8; i++) {
|
||||||
|
if (monitor_stats.ss_hist[i] > 0) {
|
||||||
|
printf("%dSS=%lu ", i + 1, (unsigned long)monitor_stats.ss_hist[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
printf("\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Show debug and filter status */
|
||||||
|
bool debug_enabled = wifi_ctl_get_monitor_debug();
|
||||||
|
uint8_t filter_mac[6];
|
||||||
|
bool filter_enabled = wifi_ctl_get_monitor_debug_filter(filter_mac);
|
||||||
|
printf(" Debug: %s\n", debug_enabled ? "enabled" : "disabled");
|
||||||
|
if (filter_enabled) {
|
||||||
|
printf(" Filter: %02x:%02x:%02x:%02x:%02x:%02x\n",
|
||||||
|
filter_mac[0], filter_mac[1], filter_mac[2],
|
||||||
|
filter_mac[3], filter_mac[4], filter_mac[5]);
|
||||||
|
} else {
|
||||||
|
printf(" Filter: disabled (showing all frames)\n");
|
||||||
|
}
|
||||||
|
|
||||||
/* Show telemetry rate statistics */
|
/* Show telemetry rate statistics */
|
||||||
uint64_t gen_bytes = 0, write_bytes = 0;
|
uint64_t gen_bytes = 0, write_bytes = 0;
|
||||||
double gen_rate = 0.0, write_rate = 0.0;
|
double gen_rate = 0.0, write_rate = 0.0;
|
||||||
|
|
@ -824,6 +903,14 @@ bool wifi_ctl_get_monitor_debug(void) {
|
||||||
return s_monitor_debug;
|
return s_monitor_debug;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
esp_err_t wifi_ctl_set_monitor_debug_filter(const uint8_t *mac) {
|
||||||
|
return wifi_monitor_set_debug_filter(mac);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool wifi_ctl_get_monitor_debug_filter(uint8_t *mac_out) {
|
||||||
|
return wifi_monitor_get_debug_filter(mac_out);
|
||||||
|
}
|
||||||
|
|
||||||
// --- Deprecated ---
|
// --- Deprecated ---
|
||||||
static void auto_monitor_task_func(void *arg) {
|
static void auto_monitor_task_func(void *arg) {
|
||||||
uint8_t channel = (uint8_t)(uintptr_t)arg;
|
uint8_t channel = (uint8_t)(uintptr_t)arg;
|
||||||
|
|
|
||||||
|
|
@ -82,6 +82,10 @@ esp_err_t wifi_ctl_get_sd_write_stats(uint64_t *total_bytes, double *rate_bps);
|
||||||
void wifi_ctl_set_monitor_debug(bool enable);
|
void wifi_ctl_set_monitor_debug(bool enable);
|
||||||
bool wifi_ctl_get_monitor_debug(void);
|
bool wifi_ctl_get_monitor_debug(void);
|
||||||
|
|
||||||
|
// Debug MAC filter control
|
||||||
|
esp_err_t wifi_ctl_set_monitor_debug_filter(const uint8_t *mac);
|
||||||
|
bool wifi_ctl_get_monitor_debug_filter(uint8_t *mac_out);
|
||||||
|
|
||||||
// Deprecated / Compatibility
|
// Deprecated / Compatibility
|
||||||
void wifi_ctl_auto_monitor_start(uint8_t channel);
|
void wifi_ctl_auto_monitor_start(uint8_t channel);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
idf_component_register(
|
idf_component_register(
|
||||||
SRCS "wifi_monitor.c"
|
SRCS "wifi_monitor.c"
|
||||||
INCLUDE_DIRS "."
|
INCLUDE_DIRS "."
|
||||||
REQUIRES esp_wifi nvs_flash
|
REQUIRES esp_wifi nvs_flash esp_timer
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,7 @@
|
||||||
#include "wifi_monitor.h"
|
#include "wifi_monitor.h"
|
||||||
#include "esp_log.h"
|
#include "esp_log.h"
|
||||||
#include "esp_wifi.h"
|
#include "esp_wifi.h"
|
||||||
|
#include "esp_timer.h"
|
||||||
#include "string.h"
|
#include "string.h"
|
||||||
|
|
||||||
static const char *TAG = "WiFi_Monitor";
|
static const char *TAG = "WiFi_Monitor";
|
||||||
|
|
@ -57,9 +58,12 @@ uint32_t threshold_duration_multiplier = 2; // NAV > expected * this = mism
|
||||||
uint32_t log_every_n_mismatches = 1; // Log every Nth mismatch (1 = all, 10 = every 10th)
|
uint32_t log_every_n_mismatches = 1; // Log every Nth mismatch (1 = all, 10 = every 10th)
|
||||||
static uint32_t s_mismatch_log_counter = 0;
|
static uint32_t s_mismatch_log_counter = 0;
|
||||||
static bool s_monitor_debug = false; // Debug mode: enable serial logging
|
static bool s_monitor_debug = false; // Debug mode: enable serial logging
|
||||||
|
static uint8_t s_monitor_debug_filter_mac[6] = {0}; // MAC address filter (all zeros = no filter)
|
||||||
|
static bool s_monitor_debug_filter_enabled = false; // Whether MAC filtering is enabled
|
||||||
|
|
||||||
// Forward declarations
|
// Forward declarations
|
||||||
static void wifi_promiscuous_rx_cb(void *buf, wifi_promiscuous_pkt_type_t type);
|
static void wifi_promiscuous_rx_cb(void *buf, wifi_promiscuous_pkt_type_t type);
|
||||||
|
static bool wifi_monitor_debug_filter_match(const uint8_t *mac);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Parse HT/VHT/HE headers to extract PHY parameters
|
* @brief Parse HT/VHT/HE headers to extract PHY parameters
|
||||||
|
|
@ -103,10 +107,11 @@ static void wifi_parse_phy_headers(const uint8_t *payload, uint16_t len, uint16_
|
||||||
uint8_t ctrl_id = ht_ctrl & 0x0F;
|
uint8_t ctrl_id = ht_ctrl & 0x0F;
|
||||||
|
|
||||||
if (ctrl_id == 0) {
|
if (ctrl_id == 0) {
|
||||||
// HT Control (802.11n) - MCS info might be elsewhere
|
// HT Control (802.11n) - MCS info is typically in PLCP header (HT-SIG)
|
||||||
|
// which ESP-IDF provides in rx_ctrl->rate, not in HT Control field
|
||||||
frame_info->sig_mode = 1; // HT
|
frame_info->sig_mode = 1; // HT
|
||||||
// Note: For HT, spatial streams are typically in HT Capabilities element
|
// Note: MCS will be extracted from rx_ctrl->rate in the callback
|
||||||
// or can be inferred from MCS index (MCS 0-7 = 1 SS, 8-15 = 2 SS, etc.)
|
// For HT, spatial streams can be inferred from MCS index (MCS 0-7 = 1 SS, 8-15 = 2 SS, etc.)
|
||||||
} else if (ctrl_id >= 1 && ctrl_id <= 3) {
|
} else if (ctrl_id >= 1 && ctrl_id <= 3) {
|
||||||
// VHT Control (802.11ac) - bits layout varies by variant
|
// VHT Control (802.11ac) - bits layout varies by variant
|
||||||
frame_info->sig_mode = 3; // VHT
|
frame_info->sig_mode = 3; // VHT
|
||||||
|
|
@ -147,6 +152,101 @@ static void wifi_parse_phy_headers(const uint8_t *payload, uint16_t len, uint16_
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Parse A-MPDU aggregation count from frame payload
|
||||||
|
* @param payload Raw frame payload
|
||||||
|
* @param len Total frame length
|
||||||
|
* @param mac_hdr_len Length of MAC header (24 or 30 bytes)
|
||||||
|
* @return Number of aggregated MPDUs (1 if not aggregated)
|
||||||
|
*/
|
||||||
|
static uint8_t wifi_parse_ampdu_count(const uint8_t *payload, uint16_t len, uint16_t mac_hdr_len) {
|
||||||
|
uint8_t count = 1; // Default: not aggregated
|
||||||
|
uint16_t offset = mac_hdr_len;
|
||||||
|
|
||||||
|
// Check if this is a QoS data frame (required for A-MPDU)
|
||||||
|
uint8_t frame_type = (payload[0] >> 2) & 0x03;
|
||||||
|
uint8_t frame_subtype = (payload[0] >> 4) & 0x0F;
|
||||||
|
|
||||||
|
if (frame_type != FRAME_TYPE_DATA) {
|
||||||
|
return count; // Not a data frame
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if it's a QoS subtype
|
||||||
|
bool is_qos = (frame_subtype == DATA_QOS_DATA ||
|
||||||
|
frame_subtype == DATA_QOS_DATA_CF_ACK ||
|
||||||
|
frame_subtype == DATA_QOS_DATA_CF_POLL ||
|
||||||
|
frame_subtype == DATA_QOS_DATA_CF_ACK_POLL ||
|
||||||
|
frame_subtype == DATA_QOS_NULL ||
|
||||||
|
frame_subtype == DATA_QOS_CF_POLL ||
|
||||||
|
frame_subtype == DATA_QOS_CF_ACK_POLL);
|
||||||
|
|
||||||
|
if (!is_qos || len < offset + 4) {
|
||||||
|
return count; // Not QoS or too short
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip QoS Control field (4 bytes)
|
||||||
|
offset += 4;
|
||||||
|
|
||||||
|
// Check for HT Control field (4 bytes) - present if Order bit set
|
||||||
|
// ORDER bit is 0x8000 (bit 15), which is in payload[1], bit 7
|
||||||
|
bool has_ht_ctrl = (payload[1] & 0x80) != 0;
|
||||||
|
if (has_ht_ctrl && len >= offset + 4) {
|
||||||
|
offset += 4; // Skip HT Control field
|
||||||
|
}
|
||||||
|
|
||||||
|
// A-MPDU delimiter format (IEEE 802.11-2016):
|
||||||
|
// Each delimiter is 4 bytes: [Reserved(4) | MPDU Length(12) | CRC(8) | Delimiter Signature(8)]
|
||||||
|
// Delimiter signature is 0x4E (ASCII 'N')
|
||||||
|
// We count delimiters until we find the end delimiter (length = 0) or run out of data
|
||||||
|
|
||||||
|
uint16_t remaining = len - offset;
|
||||||
|
if (remaining < 4) {
|
||||||
|
return count; // Not enough data for even one delimiter
|
||||||
|
}
|
||||||
|
|
||||||
|
// Count A-MPDU subframes by parsing delimiters
|
||||||
|
uint16_t pos = offset;
|
||||||
|
uint16_t delimiter_count = 0; // Use uint16_t to support up to 256 delimiters
|
||||||
|
|
||||||
|
while (pos + 4 <= len && delimiter_count < 256) { // Support up to 256 for HE (though typically 64)
|
||||||
|
// Read delimiter (little-endian)
|
||||||
|
uint16_t delimiter = payload[pos] | (payload[pos + 1] << 8);
|
||||||
|
uint8_t sig = payload[pos + 3];
|
||||||
|
|
||||||
|
// Check delimiter signature (should be 0x4E)
|
||||||
|
if (sig != 0x4E) {
|
||||||
|
break; // Not a valid delimiter
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract MPDU length (bits 4-15)
|
||||||
|
uint16_t mpdu_len = (delimiter >> 4) & 0x0FFF;
|
||||||
|
|
||||||
|
if (mpdu_len == 0) {
|
||||||
|
break; // End delimiter
|
||||||
|
}
|
||||||
|
|
||||||
|
delimiter_count++;
|
||||||
|
|
||||||
|
// Move to next delimiter (skip this MPDU)
|
||||||
|
pos += 4 + mpdu_len; // Delimiter (4) + MPDU length
|
||||||
|
|
||||||
|
// Align to 4-byte boundary
|
||||||
|
pos = (pos + 3) & ~3;
|
||||||
|
|
||||||
|
if (pos >= len) {
|
||||||
|
break; // Reached end of frame
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we found multiple delimiters, this is an A-MPDU
|
||||||
|
// Cap count at 255 (max value for uint8_t return type)
|
||||||
|
if (delimiter_count > 1) {
|
||||||
|
count = (delimiter_count > 255) ? 255 : (uint8_t)delimiter_count;
|
||||||
|
}
|
||||||
|
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Parse 802.11 MAC header
|
* @brief Parse 802.11 MAC header
|
||||||
*/
|
*/
|
||||||
|
|
@ -205,6 +305,9 @@ esp_err_t wifi_parse_frame(const uint8_t *payload, uint16_t len, wifi_frame_info
|
||||||
// Parse HT/VHT/HE headers to extract PHY parameters
|
// Parse HT/VHT/HE headers to extract PHY parameters
|
||||||
wifi_parse_phy_headers(payload, len, mac_hdr_len, frame_info);
|
wifi_parse_phy_headers(payload, len, mac_hdr_len, frame_info);
|
||||||
|
|
||||||
|
// Parse A-MPDU aggregation count
|
||||||
|
frame_info->ampdu_count = wifi_parse_ampdu_count(payload, len, mac_hdr_len);
|
||||||
|
|
||||||
frame_info->frame_len = len;
|
frame_info->frame_len = len;
|
||||||
|
|
||||||
return ESP_OK;
|
return ESP_OK;
|
||||||
|
|
@ -236,11 +339,29 @@ static void wifi_promiscuous_rx_cb(void *buf, wifi_promiscuous_pkt_type_t type)
|
||||||
frame_info.rate = rx_ctrl->rate; // This is the rate index
|
frame_info.rate = rx_ctrl->rate; // This is the rate index
|
||||||
|
|
||||||
// If MCS wasn't parsed from headers but we have HT/VHT/HE mode, try to extract from rate
|
// If MCS wasn't parsed from headers but we have HT/VHT/HE mode, try to extract from rate
|
||||||
|
// ESP-IDF encoding: rate >= 128 encodes MCS for HT/VHT/HE frames
|
||||||
|
// HT: MCS 0-31, VHT: MCS 0-9, HE: MCS 0-11
|
||||||
if (frame_info.sig_mode > 0 && frame_info.mcs == 0 && rx_ctrl->rate >= 128) {
|
if (frame_info.sig_mode > 0 && frame_info.mcs == 0 && rx_ctrl->rate >= 128) {
|
||||||
// For HT/VHT/HE, rate >= 128 might encode MCS
|
uint8_t extracted_mcs = rx_ctrl->rate - 128;
|
||||||
// ESP-IDF encoding: rate 128+ might be MCS index
|
|
||||||
frame_info.mcs = rx_ctrl->rate - 128;
|
// Cap MCS based on signal mode
|
||||||
if (frame_info.mcs > 11) frame_info.mcs = 11; // Cap at max MCS
|
if (frame_info.sig_mode == 1) {
|
||||||
|
// HT (802.11n): MCS 0-31
|
||||||
|
if (extracted_mcs > 31) extracted_mcs = 31;
|
||||||
|
frame_info.mcs = extracted_mcs;
|
||||||
|
} else if (frame_info.sig_mode == 3) {
|
||||||
|
// VHT (802.11ac): MCS 0-9
|
||||||
|
if (extracted_mcs > 9) extracted_mcs = 9;
|
||||||
|
frame_info.mcs = extracted_mcs;
|
||||||
|
} else if (frame_info.sig_mode == 4) {
|
||||||
|
// HE (802.11ax): MCS 0-11
|
||||||
|
if (extracted_mcs > 11) extracted_mcs = 11;
|
||||||
|
frame_info.mcs = extracted_mcs;
|
||||||
|
} else {
|
||||||
|
// Unknown mode, use extracted value but cap at 31 (max HT)
|
||||||
|
if (extracted_mcs > 31) extracted_mcs = 31;
|
||||||
|
frame_info.mcs = extracted_mcs;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculate PHY rate using parsed MCS/spatial streams if available
|
// Calculate PHY rate using parsed MCS/spatial streams if available
|
||||||
|
|
@ -317,20 +438,23 @@ static void wifi_promiscuous_rx_cb(void *buf, wifi_promiscuous_pkt_type_t type)
|
||||||
if (frame_info.duration_id > threshold_duration_mismatch_us) {
|
if (frame_info.duration_id > threshold_duration_mismatch_us) {
|
||||||
s_mismatch_log_counter++;
|
s_mismatch_log_counter++;
|
||||||
if (s_monitor_debug && (s_mismatch_log_counter % log_every_n_mismatches) == 0) {
|
if (s_monitor_debug && (s_mismatch_log_counter % log_every_n_mismatches) == 0) {
|
||||||
ESP_LOGW("MONITOR", "Duration mismatch: %s frame, %u bytes @ %u Mbps",
|
/* Check MAC filter before logging */
|
||||||
wifi_frame_type_str(frame_info.type, frame_info.subtype),
|
if (wifi_monitor_debug_filter_match(frame_info.addr2)) {
|
||||||
frame_info.frame_len, phy_rate_mbps);
|
ESP_LOGW("MONITOR", "Duration mismatch: %s frame, %u bytes @ %u Mbps",
|
||||||
|
wifi_frame_type_str(frame_info.type, frame_info.subtype),
|
||||||
|
frame_info.frame_len, phy_rate_mbps);
|
||||||
|
|
||||||
// NEW: Log the Source MAC (Addr2)
|
// NEW: Log the Source MAC (Addr2)
|
||||||
ESP_LOGW("MONITOR", " Source MAC: %02x:%02x:%02x:%02x:%02x:%02x",
|
ESP_LOGW("MONITOR", " Source MAC: %02x:%02x:%02x:%02x:%02x:%02x",
|
||||||
frame_info.addr2[0], frame_info.addr2[1], frame_info.addr2[2],
|
frame_info.addr2[0], frame_info.addr2[1], frame_info.addr2[2],
|
||||||
frame_info.addr2[3], frame_info.addr2[4], frame_info.addr2[5]);
|
frame_info.addr2[3], frame_info.addr2[4], frame_info.addr2[5]);
|
||||||
|
|
||||||
ESP_LOGW("MONITOR", " Expected: %lu us, Actual NAV: %u us (+%ld us)",
|
ESP_LOGW("MONITOR", " Expected: %lu us, Actual NAV: %u us (+%ld us)",
|
||||||
expected_duration, frame_info.duration_id,
|
expected_duration, frame_info.duration_id,
|
||||||
frame_info.duration_id - expected_duration);
|
frame_info.duration_id - expected_duration);
|
||||||
ESP_LOGW("MONITOR", " Retry: %s, RSSI: %d dBm",
|
ESP_LOGW("MONITOR", " Retry: %s, RSSI: %d dBm",
|
||||||
frame_info.retry ? "YES" : "no", frame_info.rssi);
|
frame_info.retry ? "YES" : "no", frame_info.rssi);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -341,18 +465,21 @@ static void wifi_promiscuous_rx_cb(void *buf, wifi_promiscuous_pkt_type_t type)
|
||||||
if (frame_info.retry && frame_info.duration_id > threshold_high_nav_us &&
|
if (frame_info.retry && frame_info.duration_id > threshold_high_nav_us &&
|
||||||
phy_rate_mbps < threshold_phy_rate_fallback_mbps) {
|
phy_rate_mbps < threshold_phy_rate_fallback_mbps) {
|
||||||
if (s_monitor_debug) {
|
if (s_monitor_debug) {
|
||||||
ESP_LOGW("MONITOR", "⚠⚠⚠ COLLISION DETECTED!");
|
/* Check MAC filter before logging */
|
||||||
|
if (wifi_monitor_debug_filter_match(frame_info.addr2)) {
|
||||||
|
ESP_LOGW("MONITOR", "⚠⚠⚠ COLLISION DETECTED!");
|
||||||
|
|
||||||
// NEW: Log the Attacker MAC
|
// NEW: Log the Attacker MAC
|
||||||
ESP_LOGW("MONITOR", " Attacker MAC: %02x:%02x:%02x:%02x:%02x:%02x",
|
ESP_LOGW("MONITOR", " Attacker MAC: %02x:%02x:%02x:%02x:%02x:%02x",
|
||||||
frame_info.addr2[0], frame_info.addr2[1], frame_info.addr2[2],
|
frame_info.addr2[0], frame_info.addr2[1], frame_info.addr2[2],
|
||||||
frame_info.addr2[3], frame_info.addr2[4], frame_info.addr2[5]);
|
frame_info.addr2[3], frame_info.addr2[4], frame_info.addr2[5]);
|
||||||
|
|
||||||
ESP_LOGW("MONITOR", " Type: %s, Size: %u bytes, Rate: %u Mbps",
|
ESP_LOGW("MONITOR", " Type: %s, Size: %u bytes, Rate: %u Mbps",
|
||||||
wifi_frame_type_str(frame_info.type, frame_info.subtype),
|
wifi_frame_type_str(frame_info.type, frame_info.subtype),
|
||||||
frame_info.frame_len, phy_rate_mbps);
|
frame_info.frame_len, phy_rate_mbps);
|
||||||
ESP_LOGW("MONITOR", " NAV: %u us (expected %lu us), Retry: YES",
|
ESP_LOGW("MONITOR", " NAV: %u us (expected %lu us), Retry: YES",
|
||||||
frame_info.duration_id, expected_duration);
|
frame_info.duration_id, expected_duration);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -375,6 +502,94 @@ static void wifi_promiscuous_rx_cb(void *buf, wifi_promiscuous_pkt_type_t type)
|
||||||
|
|
||||||
case FRAME_TYPE_DATA:
|
case FRAME_TYPE_DATA:
|
||||||
stats.data_frames++;
|
stats.data_frames++;
|
||||||
|
|
||||||
|
/* Periodic diagnostic summary (even when debug is off) */
|
||||||
|
static uint32_t data_frame_diag_counter = 0;
|
||||||
|
static uint64_t last_diag_log_ms = 0;
|
||||||
|
data_frame_diag_counter++;
|
||||||
|
uint64_t now_ms = esp_timer_get_time() / 1000;
|
||||||
|
if (now_ms - last_diag_log_ms > 10000) { /* Log every 10 seconds */
|
||||||
|
ESP_LOGI("MONITOR", "Data frames: %lu total, last TA=%02x:%02x:%02x:%02x:%02x:%02x, "
|
||||||
|
"debug=%s, filter=%s",
|
||||||
|
(unsigned long)stats.data_frames,
|
||||||
|
frame_info.addr2[0], frame_info.addr2[1], frame_info.addr2[2],
|
||||||
|
frame_info.addr2[3], frame_info.addr2[4], frame_info.addr2[5],
|
||||||
|
s_monitor_debug ? "on" : "off",
|
||||||
|
s_monitor_debug_filter_enabled ? "on" : "off");
|
||||||
|
data_frame_diag_counter = 0;
|
||||||
|
last_diag_log_ms = now_ms;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Update histograms for data frames */
|
||||||
|
// AMPDU histogram: [1, 2-4, 5-8, 9-16, 17-32, 33-48, 49-64, 65+]
|
||||||
|
uint8_t ampdu_count = frame_info.ampdu_count;
|
||||||
|
if (ampdu_count == 1) {
|
||||||
|
stats.ampdu_hist[0]++;
|
||||||
|
} else if (ampdu_count >= 2 && ampdu_count <= 4) {
|
||||||
|
stats.ampdu_hist[1]++;
|
||||||
|
} else if (ampdu_count >= 5 && ampdu_count <= 8) {
|
||||||
|
stats.ampdu_hist[2]++;
|
||||||
|
} else if (ampdu_count >= 9 && ampdu_count <= 16) {
|
||||||
|
stats.ampdu_hist[3]++;
|
||||||
|
} else if (ampdu_count >= 17 && ampdu_count <= 32) {
|
||||||
|
stats.ampdu_hist[4]++;
|
||||||
|
} else if (ampdu_count >= 33 && ampdu_count <= 48) {
|
||||||
|
stats.ampdu_hist[5]++;
|
||||||
|
} else if (ampdu_count >= 49 && ampdu_count <= 64) {
|
||||||
|
stats.ampdu_hist[6]++;
|
||||||
|
} else if (ampdu_count >= 65) {
|
||||||
|
stats.ampdu_hist[7]++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// MCS histogram: [0-31]
|
||||||
|
if (frame_info.mcs < 32) {
|
||||||
|
stats.mcs_hist[frame_info.mcs]++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Spatial streams histogram: [1-8]
|
||||||
|
uint8_t ss = frame_info.spatial_streams;
|
||||||
|
if (ss >= 1 && ss <= 8) {
|
||||||
|
stats.ss_hist[ss - 1]++; // Convert to 0-indexed
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Debug logging for data frames (especially QoS data used by iperf) */
|
||||||
|
if (s_monitor_debug) {
|
||||||
|
bool filter_match = wifi_monitor_debug_filter_match(frame_info.addr2);
|
||||||
|
uint16_t phy_rate_mbps = frame_info.phy_rate_kbps / 1000;
|
||||||
|
const char *frame_type_name = wifi_frame_type_str(frame_info.type, frame_info.subtype);
|
||||||
|
|
||||||
|
if (filter_match) {
|
||||||
|
/* Log all data frames (QoS and non-QoS) when filter matches or filter is disabled */
|
||||||
|
ESP_LOGI("MONITOR", "DATA: %s, TA=%02x:%02x:%02x:%02x:%02x:%02x, RA=%02x:%02x:%02x:%02x:%02x:%02x, "
|
||||||
|
"Size=%u bytes, Rate=%u Mbps, MCS=%u, SS=%u, BW=%u MHz, RSSI=%d dBm, Retry=%s",
|
||||||
|
frame_type_name,
|
||||||
|
frame_info.addr2[0], frame_info.addr2[1], frame_info.addr2[2],
|
||||||
|
frame_info.addr2[3], frame_info.addr2[4], frame_info.addr2[5],
|
||||||
|
frame_info.addr1[0], frame_info.addr1[1], frame_info.addr1[2],
|
||||||
|
frame_info.addr1[3], frame_info.addr1[4], frame_info.addr1[5],
|
||||||
|
frame_info.frame_len, phy_rate_mbps, frame_info.mcs,
|
||||||
|
frame_info.spatial_streams,
|
||||||
|
(frame_info.bandwidth == 0) ? 20 : (frame_info.bandwidth == 1) ? 40 : 80,
|
||||||
|
frame_info.rssi, frame_info.retry ? "YES" : "no");
|
||||||
|
} else {
|
||||||
|
/* Diagnostic: log when data frames are filtered out (throttled, but more visible) */
|
||||||
|
static uint32_t filtered_data_count = 0;
|
||||||
|
static uint64_t last_filtered_log_ms = 0;
|
||||||
|
static uint8_t last_filtered_ta[6] = {0};
|
||||||
|
filtered_data_count++;
|
||||||
|
uint64_t now_ms = esp_timer_get_time() / 1000;
|
||||||
|
memcpy(last_filtered_ta, frame_info.addr2, 6);
|
||||||
|
if (now_ms - last_filtered_log_ms > 5000) { /* Log every 5 seconds */
|
||||||
|
ESP_LOGW("MONITOR", "Filtered out %lu data frames (last TA=%02x:%02x:%02x:%02x:%02x:%02x, filter=%s)",
|
||||||
|
(unsigned long)filtered_data_count,
|
||||||
|
last_filtered_ta[0], last_filtered_ta[1], last_filtered_ta[2],
|
||||||
|
last_filtered_ta[3], last_filtered_ta[4], last_filtered_ta[5],
|
||||||
|
s_monitor_debug_filter_enabled ? "enabled" : "disabled");
|
||||||
|
filtered_data_count = 0;
|
||||||
|
last_filtered_log_ms = now_ms;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -608,3 +823,51 @@ void wifi_monitor_set_debug(bool enable) {
|
||||||
bool wifi_monitor_get_debug(void) {
|
bool wifi_monitor_get_debug(void) {
|
||||||
return s_monitor_debug;
|
return s_monitor_debug;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Check if a MAC address matches the debug filter
|
||||||
|
* @param mac MAC address to check (6 bytes)
|
||||||
|
* @return true if filter is disabled or MAC matches filter, false otherwise
|
||||||
|
*/
|
||||||
|
static bool wifi_monitor_debug_filter_match(const uint8_t *mac) {
|
||||||
|
if (!s_monitor_debug_filter_enabled || mac == NULL) {
|
||||||
|
return true; /* No filter or invalid MAC - allow all */
|
||||||
|
}
|
||||||
|
/* Compare MAC addresses byte by byte */
|
||||||
|
for (int i = 0; i < 6; i++) {
|
||||||
|
if (mac[i] != s_monitor_debug_filter_mac[i]) {
|
||||||
|
return false; /* MAC doesn't match filter */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true; /* MAC matches filter */
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Set MAC address filter for debug logging
|
||||||
|
* @param mac MAC address to filter on (6 bytes), or NULL to disable filter
|
||||||
|
* @return ESP_OK on success, ESP_ERR_INVALID_ARG if mac is invalid
|
||||||
|
*/
|
||||||
|
esp_err_t wifi_monitor_set_debug_filter(const uint8_t *mac) {
|
||||||
|
if (mac == NULL) {
|
||||||
|
/* Disable filter */
|
||||||
|
s_monitor_debug_filter_enabled = false;
|
||||||
|
memset(s_monitor_debug_filter_mac, 0, 6);
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
/* Enable filter and copy MAC address */
|
||||||
|
memcpy(s_monitor_debug_filter_mac, mac, 6);
|
||||||
|
s_monitor_debug_filter_enabled = true;
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get current MAC address filter for debug logging
|
||||||
|
* @param mac_out Buffer to store MAC address (6 bytes), or NULL to just check if filter is enabled
|
||||||
|
* @return true if filter is enabled, false otherwise
|
||||||
|
*/
|
||||||
|
bool wifi_monitor_get_debug_filter(uint8_t *mac_out) {
|
||||||
|
if (mac_out != NULL && s_monitor_debug_filter_enabled) {
|
||||||
|
memcpy(mac_out, s_monitor_debug_filter_mac, 6);
|
||||||
|
}
|
||||||
|
return s_monitor_debug_filter_enabled;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -176,6 +176,9 @@ typedef struct {
|
||||||
uint8_t bandwidth; // 0=20MHz, 1=40MHz, 2=80MHz, 3=160MHz
|
uint8_t bandwidth; // 0=20MHz, 1=40MHz, 2=80MHz, 3=160MHz
|
||||||
uint32_t phy_rate_kbps; // Calculated PHY rate in Kbps (uint32_t to handle >65 Mbps)
|
uint32_t phy_rate_kbps; // Calculated PHY rate in Kbps (uint32_t to handle >65 Mbps)
|
||||||
|
|
||||||
|
// Aggregation info
|
||||||
|
uint8_t ampdu_count; // Number of aggregated MPDUs in A-MPDU (1 = not aggregated)
|
||||||
|
|
||||||
// Frame size
|
// Frame size
|
||||||
uint16_t frame_len;
|
uint16_t frame_len;
|
||||||
} wifi_frame_info_t;
|
} wifi_frame_info_t;
|
||||||
|
|
@ -205,6 +208,11 @@ typedef struct {
|
||||||
uint16_t avg_phy_rate_mbps; // Average PHY rate
|
uint16_t avg_phy_rate_mbps; // Average PHY rate
|
||||||
uint16_t min_phy_rate_mbps; // Minimum PHY rate seen
|
uint16_t min_phy_rate_mbps; // Minimum PHY rate seen
|
||||||
uint16_t max_phy_rate_mbps; // Maximum PHY rate seen
|
uint16_t max_phy_rate_mbps; // Maximum PHY rate seen
|
||||||
|
|
||||||
|
// Histograms
|
||||||
|
uint32_t ampdu_hist[8]; // AMPDU aggregation: [1, 2-4, 5-8, 9-16, 17-32, 33-48, 49-64, 65+]
|
||||||
|
uint32_t mcs_hist[32]; // MCS index: [0-31]
|
||||||
|
uint32_t ss_hist[8]; // Spatial streams: [1-8]
|
||||||
} wifi_collapse_stats_t;
|
} wifi_collapse_stats_t;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -300,6 +308,20 @@ void wifi_monitor_set_debug(bool enable);
|
||||||
*/
|
*/
|
||||||
bool wifi_monitor_get_debug(void);
|
bool wifi_monitor_get_debug(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Set MAC address filter for debug logging
|
||||||
|
* @param mac MAC address to filter on (6 bytes), or NULL to disable filter
|
||||||
|
* @return ESP_OK on success, ESP_ERR_INVALID_ARG if mac is invalid
|
||||||
|
*/
|
||||||
|
esp_err_t wifi_monitor_set_debug_filter(const uint8_t *mac);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get current MAC address filter for debug logging
|
||||||
|
* @param mac_out Buffer to store MAC address (6 bytes), or NULL to just check if filter is enabled
|
||||||
|
* @return true if filter is enabled, false otherwise
|
||||||
|
*/
|
||||||
|
bool wifi_monitor_get_debug_filter(uint8_t *mac_out);
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,101 @@
|
||||||
|
#!/bin/bash
|
||||||
|
# Raspberry Pi 5 WiFi Monitor Mode - Capture RA/TA Addresses
|
||||||
|
# This script sets up monitor mode and captures 802.11 frames showing RA and TA
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
WIFI_INTERFACE="wlan0"
|
||||||
|
CHANNEL="${1:-11}" # Default to channel 11, or pass as argument
|
||||||
|
FILTER_MAC="${2:-}" # Optional: filter by MAC address (e.g., 80:84:89:93:c4:b6)
|
||||||
|
|
||||||
|
echo "=== Raspberry Pi 5 WiFi Monitor - RA/TA Capture ==="
|
||||||
|
echo "Interface: $WIFI_INTERFACE"
|
||||||
|
echo "Channel: $CHANNEL"
|
||||||
|
if [ -n "$FILTER_MAC" ]; then
|
||||||
|
echo "Filter: $FILTER_MAC"
|
||||||
|
fi
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Check if running as root
|
||||||
|
if [ "$EUID" -ne 0 ]; then
|
||||||
|
echo "Please run as root (use sudo)"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check if interface exists
|
||||||
|
if ! ip link show "$WIFI_INTERFACE" &>/dev/null; then
|
||||||
|
echo "Error: Interface $WIFI_INTERFACE not found"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check current mode
|
||||||
|
CURRENT_MODE=$(iw dev "$WIFI_INTERFACE" info 2>/dev/null | grep "type" | awk '{print $2}' || echo "unknown")
|
||||||
|
|
||||||
|
if [ "$CURRENT_MODE" != "monitor" ]; then
|
||||||
|
echo "Setting $WIFI_INTERFACE to monitor mode..."
|
||||||
|
ip link set "$WIFI_INTERFACE" down
|
||||||
|
iw dev "$WIFI_INTERFACE" set type monitor
|
||||||
|
ip link set "$WIFI_INTERFACE" up
|
||||||
|
iw dev "$WIFI_INTERFACE" set channel "$CHANNEL"
|
||||||
|
echo "Monitor mode activated on channel $CHANNEL"
|
||||||
|
else
|
||||||
|
echo "Already in monitor mode, setting channel to $CHANNEL..."
|
||||||
|
iw dev "$WIFI_INTERFACE" set channel "$CHANNEL"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "=== Starting Capture (showing RA/TA addresses) ==="
|
||||||
|
echo "Press Ctrl+C to stop"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Build tcpdump filter
|
||||||
|
TCPDUMP_FILTER=""
|
||||||
|
if [ -n "$FILTER_MAC" ]; then
|
||||||
|
# Remove colons from MAC for tcpdump
|
||||||
|
MAC_CLEAN=$(echo "$FILTER_MAC" | tr -d ':')
|
||||||
|
TCPDUMP_FILTER="ether host $FILTER_MAC"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Use tcpdump with verbose output to show MAC addresses
|
||||||
|
# -e shows link-level headers (includes MAC addresses)
|
||||||
|
# -n prevents DNS resolution
|
||||||
|
# -v increases verbosity
|
||||||
|
if [ -n "$TCPDUMP_FILTER" ]; then
|
||||||
|
tcpdump -i "$WIFI_INTERFACE" -e -n -v "$TCPDUMP_FILTER" 2>&1 | \
|
||||||
|
grep -E "(ether|RA|TA|SA|DA|BSSID)" | \
|
||||||
|
awk '
|
||||||
|
{
|
||||||
|
# Extract MAC addresses from tcpdump output
|
||||||
|
# tcpdump shows: ether src/dst MAC
|
||||||
|
if (match($0, /ether (src|dst) ([0-9a-f:]{17})/, arr)) {
|
||||||
|
direction = arr[1]
|
||||||
|
mac = arr[2]
|
||||||
|
print "[" direction "] " mac
|
||||||
|
}
|
||||||
|
# Also show full frame info
|
||||||
|
print $0
|
||||||
|
}'
|
||||||
|
else
|
||||||
|
# Show all frames with MAC address extraction
|
||||||
|
tcpdump -i "$WIFI_INTERFACE" -e -n -v 2>&1 | \
|
||||||
|
while IFS= read -r line; do
|
||||||
|
# Extract and highlight MAC addresses
|
||||||
|
if echo "$line" | grep -q "ether"; then
|
||||||
|
# Extract source MAC (TA in 802.11)
|
||||||
|
if echo "$line" | grep -q "ether src"; then
|
||||||
|
TA=$(echo "$line" | grep -oP 'ether src \K[0-9a-f:]{17}' || echo "")
|
||||||
|
if [ -n "$TA" ]; then
|
||||||
|
echo "TA (Transmitter): $TA"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
# Extract destination MAC (RA in 802.11)
|
||||||
|
if echo "$line" | grep -q "ether dst"; then
|
||||||
|
RA=$(echo "$line" | grep -oP 'ether dst \K[0-9a-f:]{17}' || echo "")
|
||||||
|
if [ -n "$RA" ]; then
|
||||||
|
echo "RA (Receiver): $RA"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
echo "$line"
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
@ -0,0 +1,307 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Raspberry Pi 5 WiFi Monitor - Capture RA/TA Addresses
|
||||||
|
Uses scapy to parse 802.11 frames and display RA/TA addresses clearly
|
||||||
|
|
||||||
|
Requirements:
|
||||||
|
sudo apt-get install python3-pip
|
||||||
|
sudo pip3 install scapy
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
sudo python3 rpi_capture_ra_ta_python.py [channel] [filter_mac]
|
||||||
|
|
||||||
|
Example:
|
||||||
|
sudo python3 rpi_capture_ra_ta_python.py 11
|
||||||
|
sudo python3 rpi_capture_ra_ta_python.py 11 80:84:89:93:c4:b6
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
import signal
|
||||||
|
from datetime import datetime
|
||||||
|
from scapy.all import *
|
||||||
|
from scapy.layers.dot11 import Dot11, Dot11QoS
|
||||||
|
|
||||||
|
# Configuration
|
||||||
|
WIFI_INTERFACE = "wlan0"
|
||||||
|
CHANNEL = int(sys.argv[1]) if len(sys.argv) > 1 else 11
|
||||||
|
FILTER_MAC = sys.argv[2] if len(sys.argv) > 2 else None
|
||||||
|
|
||||||
|
def setup_monitor_mode():
|
||||||
|
"""Set WiFi interface to monitor mode"""
|
||||||
|
print(f"=== Setting up monitor mode on {WIFI_INTERFACE} ===")
|
||||||
|
|
||||||
|
# Check current mode
|
||||||
|
try:
|
||||||
|
result = subprocess.run(
|
||||||
|
["iw", "dev", WIFI_INTERFACE, "info"],
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
check=True
|
||||||
|
)
|
||||||
|
if "type monitor" in result.stdout:
|
||||||
|
print(f"Already in monitor mode")
|
||||||
|
else:
|
||||||
|
print(f"Setting {WIFI_INTERFACE} to monitor mode...")
|
||||||
|
subprocess.run(["ip", "link", "set", WIFI_INTERFACE, "down"], check=True)
|
||||||
|
subprocess.run(["iw", "dev", WIFI_INTERFACE, "set", "type", "monitor"], check=True)
|
||||||
|
subprocess.run(["ip", "link", "set", WIFI_INTERFACE, "up"], check=True)
|
||||||
|
print("Monitor mode activated")
|
||||||
|
except subprocess.CalledProcessError as e:
|
||||||
|
print(f"Error setting monitor mode: {e}")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# Set channel
|
||||||
|
try:
|
||||||
|
subprocess.run(["iw", "dev", WIFI_INTERFACE, "set", "channel", str(CHANNEL)], check=True)
|
||||||
|
print(f"Channel set to {CHANNEL}")
|
||||||
|
except subprocess.CalledProcessError as e:
|
||||||
|
print(f"Error setting channel: {e}")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
def get_frame_type_name(dot11):
|
||||||
|
"""Get human-readable frame type name"""
|
||||||
|
type_names = {
|
||||||
|
0: { # Management
|
||||||
|
0: "Association Request",
|
||||||
|
1: "Association Response",
|
||||||
|
2: "Reassociation Request",
|
||||||
|
3: "Reassociation Response",
|
||||||
|
4: "Probe Request",
|
||||||
|
5: "Probe Response",
|
||||||
|
8: "Beacon",
|
||||||
|
10: "Disassociation",
|
||||||
|
11: "Authentication",
|
||||||
|
12: "Deauthentication"
|
||||||
|
},
|
||||||
|
1: { # Control
|
||||||
|
10: "RTS",
|
||||||
|
11: "CTS",
|
||||||
|
12: "ACK",
|
||||||
|
13: "CF-End",
|
||||||
|
14: "CF-End+CF-Ack"
|
||||||
|
},
|
||||||
|
2: { # Data
|
||||||
|
0: "Data",
|
||||||
|
1: "Data+CF-Ack",
|
||||||
|
2: "Data+CF-Poll",
|
||||||
|
3: "Data+CF-Ack+CF-Poll",
|
||||||
|
4: "Null",
|
||||||
|
8: "QoS Data",
|
||||||
|
9: "QoS Data+CF-Ack",
|
||||||
|
10: "QoS Data+CF-Poll",
|
||||||
|
11: "QoS Data+CF-Ack+CF-Poll",
|
||||||
|
12: "QoS Null"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return type_names.get(dot11.type, {}).get(dot11.subtype,
|
||||||
|
f"Type{dot11.type}/Subtype{dot11.subtype}")
|
||||||
|
|
||||||
|
def parse_80211_frame(pkt):
|
||||||
|
"""Parse 802.11 frame and extract RA/TA addresses"""
|
||||||
|
if not pkt.haslayer(Dot11):
|
||||||
|
return None
|
||||||
|
|
||||||
|
dot11 = pkt[Dot11]
|
||||||
|
|
||||||
|
# 802.11 addressing:
|
||||||
|
# For Data frames (To DS=1, From DS=1):
|
||||||
|
# addr1 = RA (Receiver Address) = Next hop destination
|
||||||
|
# addr2 = TA (Transmitter Address) = Transmitting station
|
||||||
|
# addr3 = DA (Destination Address) = Final destination
|
||||||
|
# addr4 = SA (Source Address) = Original source
|
||||||
|
|
||||||
|
# For Data frames (To DS=0, From DS=1): AP to STA
|
||||||
|
# addr1 = RA = DA (Destination STA)
|
||||||
|
# addr2 = TA = SA (Source AP)
|
||||||
|
# addr3 = BSSID
|
||||||
|
|
||||||
|
# For Data frames (To DS=1, From DS=0): STA to AP
|
||||||
|
# addr1 = RA = BSSID (AP)
|
||||||
|
# addr2 = TA = SA (Source STA)
|
||||||
|
# addr3 = DA (Destination)
|
||||||
|
|
||||||
|
frame_type = dot11.type
|
||||||
|
frame_subtype = dot11.subtype
|
||||||
|
|
||||||
|
# Get addresses
|
||||||
|
addr1 = dot11.addr1 if dot11.addr1 else "N/A"
|
||||||
|
addr2 = dot11.addr2 if dot11.addr2 else "N/A"
|
||||||
|
addr3 = dot11.addr3 if dot11.addr3 else "N/A"
|
||||||
|
addr4 = dot11.addr4 if hasattr(dot11, 'addr4') and dot11.addr4 else None
|
||||||
|
|
||||||
|
# Extract To DS and From DS flags
|
||||||
|
to_ds = dot11.FCfield & 0x1
|
||||||
|
from_ds = (dot11.FCfield >> 1) & 0x1
|
||||||
|
|
||||||
|
# Determine RA and TA based on frame type
|
||||||
|
if frame_type == 2: # Data frame
|
||||||
|
# For data frames:
|
||||||
|
# addr1 is always RA (receiver)
|
||||||
|
# addr2 is always TA (transmitter)
|
||||||
|
ra = addr1
|
||||||
|
ta = addr2
|
||||||
|
|
||||||
|
# Additional context based on To DS / From DS
|
||||||
|
if to_ds and from_ds:
|
||||||
|
# WDS frame: addr3=DA, addr4=SA
|
||||||
|
context = f"WDS: DA={addr3}, SA={addr4 if addr4 else 'N/A'}"
|
||||||
|
elif to_ds and not from_ds:
|
||||||
|
# STA to AP: addr3=DA
|
||||||
|
context = f"STA→AP: DA={addr3}"
|
||||||
|
elif not to_ds and from_ds:
|
||||||
|
# AP to STA: addr3=BSSID
|
||||||
|
context = f"AP→STA: BSSID={addr3}"
|
||||||
|
else:
|
||||||
|
# Ad-hoc: addr3=BSSID
|
||||||
|
context = f"Ad-hoc: BSSID={addr3}"
|
||||||
|
else:
|
||||||
|
# For management/control frames, addr1=DA, addr2=SA
|
||||||
|
ra = addr1 # Receiver/Destination
|
||||||
|
ta = addr2 # Transmitter/Source
|
||||||
|
context = f"BSSID={addr3}" if addr3 != "N/A" else ""
|
||||||
|
|
||||||
|
# Get RSSI if available
|
||||||
|
rssi = "N/A"
|
||||||
|
if hasattr(pkt, "dBm_AntSignal"):
|
||||||
|
rssi = f"{pkt.dBm_AntSignal} dBm"
|
||||||
|
elif hasattr(pkt, "notdecoded"):
|
||||||
|
# Try to extract from radiotap header if present
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Check for QoS data
|
||||||
|
is_qos = pkt.haslayer(Dot11QoS)
|
||||||
|
qos_info = ""
|
||||||
|
if is_qos:
|
||||||
|
qos = pkt[Dot11QoS]
|
||||||
|
tid = qos.TID if hasattr(qos, 'TID') else "N/A"
|
||||||
|
qos_info = f", TID={tid}"
|
||||||
|
|
||||||
|
return {
|
||||||
|
"type": frame_type,
|
||||||
|
"subtype": frame_subtype,
|
||||||
|
"name": get_frame_type_name(dot11),
|
||||||
|
"ra": ra,
|
||||||
|
"ta": ta,
|
||||||
|
"bssid": addr3,
|
||||||
|
"context": context,
|
||||||
|
"rssi": rssi,
|
||||||
|
"len": len(pkt),
|
||||||
|
"qos": is_qos,
|
||||||
|
"qos_info": qos_info,
|
||||||
|
"retry": bool(dot11.FCfield & 0x8) if hasattr(dot11, 'FCfield') else False
|
||||||
|
}
|
||||||
|
|
||||||
|
# Statistics
|
||||||
|
stats = {
|
||||||
|
"total": 0,
|
||||||
|
"by_ta": {},
|
||||||
|
"by_ra": {},
|
||||||
|
"by_type": {}
|
||||||
|
}
|
||||||
|
|
||||||
|
def packet_handler(pkt):
|
||||||
|
"""Handle captured packets"""
|
||||||
|
frame_info = parse_80211_frame(pkt)
|
||||||
|
if not frame_info:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Apply MAC filter if specified
|
||||||
|
if FILTER_MAC:
|
||||||
|
filter_mac_clean = FILTER_MAC.lower().replace(":", "").replace("-", "")
|
||||||
|
ta_clean = frame_info["ta"].replace(":", "").replace("-", "").lower() if frame_info["ta"] != "N/A" else ""
|
||||||
|
ra_clean = frame_info["ra"].replace(":", "").replace("-", "").lower() if frame_info["ra"] != "N/A" else ""
|
||||||
|
|
||||||
|
if filter_mac_clean.lower() not in ta_clean and filter_mac_clean.lower() not in ra_clean:
|
||||||
|
return # Skip this frame
|
||||||
|
|
||||||
|
# Update statistics
|
||||||
|
stats["total"] += 1
|
||||||
|
if frame_info["ta"] != "N/A":
|
||||||
|
stats["by_ta"][frame_info["ta"]] = stats["by_ta"].get(frame_info["ta"], 0) + 1
|
||||||
|
if frame_info["ra"] != "N/A":
|
||||||
|
stats["by_ra"][frame_info["ra"]] = stats["by_ra"].get(frame_info["ra"], 0) + 1
|
||||||
|
frame_type_key = f"{frame_info['name']}"
|
||||||
|
stats["by_type"][frame_type_key] = stats["by_type"].get(frame_type_key, 0) + 1
|
||||||
|
|
||||||
|
# Print frame information
|
||||||
|
timestamp = datetime.now().strftime("%H:%M:%S.%f")[:-3]
|
||||||
|
retry_str = " [RETRY]" if frame_info["retry"] else ""
|
||||||
|
|
||||||
|
print(f"\n[{timestamp}] {frame_info['name']}{retry_str}")
|
||||||
|
print(f" RA (Receiver): {frame_info['ra']}")
|
||||||
|
print(f" TA (Transmitter): {frame_info['ta']}")
|
||||||
|
if frame_info['bssid'] != "N/A":
|
||||||
|
print(f" BSSID: {frame_info['bssid']}")
|
||||||
|
if frame_info['context']:
|
||||||
|
print(f" Context: {frame_info['context']}")
|
||||||
|
print(f" RSSI: {frame_info['rssi']}")
|
||||||
|
print(f" Length: {frame_info['len']} bytes{frame_info['qos_info']}")
|
||||||
|
|
||||||
|
def signal_handler(sig, frame):
|
||||||
|
"""Handle Ctrl+C gracefully"""
|
||||||
|
print("\n\nStopping capture...")
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""Main function"""
|
||||||
|
print(f"=== Raspberry Pi 5 WiFi Monitor - RA/TA Capture ===")
|
||||||
|
print(f"Interface: {WIFI_INTERFACE}")
|
||||||
|
print(f"Channel: {CHANNEL}")
|
||||||
|
if FILTER_MAC:
|
||||||
|
print(f"Filter: {FILTER_MAC}")
|
||||||
|
print("")
|
||||||
|
|
||||||
|
# Check if running as root
|
||||||
|
if os.geteuid() != 0:
|
||||||
|
print("Error: This script must be run as root (use sudo)")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# Setup monitor mode
|
||||||
|
setup_monitor_mode()
|
||||||
|
|
||||||
|
# Register signal handler
|
||||||
|
signal.signal(signal.SIGINT, signal_handler)
|
||||||
|
|
||||||
|
print("\n=== Starting Capture (showing RA/TA addresses) ===")
|
||||||
|
print("Press Ctrl+C to stop\n")
|
||||||
|
|
||||||
|
# Build filter
|
||||||
|
bpf_filter = None
|
||||||
|
if FILTER_MAC:
|
||||||
|
bpf_filter = f"ether host {FILTER_MAC}"
|
||||||
|
|
||||||
|
# Start capturing
|
||||||
|
try:
|
||||||
|
sniff(
|
||||||
|
iface=WIFI_INTERFACE,
|
||||||
|
prn=packet_handler,
|
||||||
|
filter=bpf_filter,
|
||||||
|
store=False
|
||||||
|
)
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
print("\n\n" + "="*60)
|
||||||
|
print("Capture stopped by user")
|
||||||
|
print("="*60)
|
||||||
|
print(f"\nStatistics:")
|
||||||
|
print(f" Total frames captured: {stats['total']}")
|
||||||
|
print(f"\n Top 5 TAs (Transmitters):")
|
||||||
|
sorted_tas = sorted(stats['by_ta'].items(), key=lambda x: x[1], reverse=True)[:5]
|
||||||
|
for ta, count in sorted_tas:
|
||||||
|
print(f" {ta}: {count} frames")
|
||||||
|
print(f"\n Top 5 RAs (Receivers):")
|
||||||
|
sorted_ras = sorted(stats['by_ra'].items(), key=lambda x: x[1], reverse=True)[:5]
|
||||||
|
for ra, count in sorted_ras:
|
||||||
|
print(f" {ra}: {count} frames")
|
||||||
|
print(f"\n Frame types:")
|
||||||
|
for ftype, count in sorted(stats['by_type'].items(), key=lambda x: x[1], reverse=True):
|
||||||
|
print(f" {ftype}: {count}")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"\nError during capture: {e}")
|
||||||
|
import traceback
|
||||||
|
traceback.print_exc()
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
|
|
@ -0,0 +1,73 @@
|
||||||
|
#!/bin/bash
|
||||||
|
# Raspberry Pi 5 WiFi Monitor Mode Setup Script
|
||||||
|
# Run this script with sudo on your Raspberry Pi
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# Configuration
|
||||||
|
WIFI_INTERFACE="wlan0" # Change if your interface is different
|
||||||
|
MONITOR_INTERFACE="mon0" # Monitor mode interface name
|
||||||
|
CHANNEL="${1:-11}" # Default to channel 11, or pass as argument: ./rpi_monitor_setup.sh 36
|
||||||
|
|
||||||
|
echo "=== Raspberry Pi 5 WiFi Monitor Mode Setup ==="
|
||||||
|
echo "Interface: $WIFI_INTERFACE"
|
||||||
|
echo "Monitor interface: $MONITOR_INTERFACE"
|
||||||
|
echo "Channel: $CHANNEL"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Check if running as root
|
||||||
|
if [ "$EUID" -ne 0 ]; then
|
||||||
|
echo "Please run as root (use sudo)"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check if interface exists
|
||||||
|
if ! ip link show "$WIFI_INTERFACE" &>/dev/null; then
|
||||||
|
echo "Error: Interface $WIFI_INTERFACE not found"
|
||||||
|
echo "Available interfaces:"
|
||||||
|
ip link show | grep -E "^[0-9]+:" | awk '{print $2}' | sed 's/://'
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check if monitor interface already exists
|
||||||
|
if ip link show "$MONITOR_INTERFACE" &>/dev/null; then
|
||||||
|
echo "Monitor interface $MONITOR_INTERFACE already exists. Removing..."
|
||||||
|
iw dev "$MONITOR_INTERFACE" del
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Bring down the interface
|
||||||
|
echo "Bringing down $WIFI_INTERFACE..."
|
||||||
|
ip link set "$WIFI_INTERFACE" down
|
||||||
|
|
||||||
|
# Set monitor mode
|
||||||
|
echo "Setting $WIFI_INTERFACE to monitor mode..."
|
||||||
|
iw dev "$WIFI_INTERFACE" set type monitor
|
||||||
|
|
||||||
|
# Bring up the monitor interface
|
||||||
|
echo "Bringing up monitor interface..."
|
||||||
|
ip link set "$WIFI_INTERFACE" up
|
||||||
|
|
||||||
|
# Set channel
|
||||||
|
echo "Setting channel to $CHANNEL..."
|
||||||
|
iw dev "$WIFI_INTERFACE" set channel "$CHANNEL"
|
||||||
|
|
||||||
|
# Verify monitor mode
|
||||||
|
echo ""
|
||||||
|
echo "=== Verification ==="
|
||||||
|
iw dev "$WIFI_INTERFACE" info
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "=== Monitor mode is now active! ==="
|
||||||
|
echo "Interface: $WIFI_INTERFACE"
|
||||||
|
echo "Channel: $CHANNEL"
|
||||||
|
echo ""
|
||||||
|
echo "To capture packets, you can use:"
|
||||||
|
echo " sudo tcpdump -i $WIFI_INTERFACE -n"
|
||||||
|
echo " sudo tcpdump -i $WIFI_INTERFACE -w capture.pcap"
|
||||||
|
echo " sudo wireshark -i $WIFI_INTERFACE"
|
||||||
|
echo ""
|
||||||
|
echo "To stop monitor mode and restore normal WiFi:"
|
||||||
|
echo " sudo ip link set $WIFI_INTERFACE down"
|
||||||
|
echo " sudo iw dev $WIFI_INTERFACE set type managed"
|
||||||
|
echo " sudo ip link set $WIFI_INTERFACE up"
|
||||||
|
echo " # Then reconnect to your network"
|
||||||
Loading…
Reference in New Issue