Compare commits

...

2 Commits

Author SHA1 Message Date
Robert McMahon c73e694b9a Add MAC filter, histograms, and enhanced debug logging to ESP32 monitor
- Add MAC address filter for debug logging (monitor filter command)
- Add histograms for AMPDU aggregation, MCS distribution, and spatial streams
- Display RA (Receiver Address) in data frame debug logs alongside TA
- Add periodic diagnostic summary (every 10s) showing data frame stats
- Add filtered frame diagnostic logging when filter is active
- Fix MCS extraction to properly cap values based on HT/VHT/HE standards
- Add esp_timer dependency to wifi_monitor component
- Display frame type breakdown and histograms in monitor status
- Show debug and filter status in monitor status output

These changes help debug WiFi traffic by filtering on specific MAC addresses
and providing detailed statistics about frame characteristics.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-10 16:38:21 -08:00
Robert McMahon 8ba4bff040 Add Raspberry Pi 5 monitor mode scripts for RA/TA capture
- Add rpi_monitor_setup.sh: Basic monitor mode setup script
- Add rpi_capture_ra_ta.sh: Bash script for capturing RA/TA addresses
- Add rpi_capture_ra_ta_python.py: Python script using scapy for detailed 802.11 frame parsing
- Add RPI_MONITOR_GUIDE.md: Comprehensive guide for monitor mode setup and usage
- Add RPI_INSTALL_SCAPY.md: Installation instructions for scapy on Raspberry Pi

These scripts help debug WiFi traffic by capturing and displaying Receiver Address (RA) and Transmitter Address (TA) from 802.11 frames, useful for comparing with ESP32 monitor mode captures.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-10 16:37:48 -08:00
11 changed files with 1227 additions and 37 deletions

40
RPI_INSTALL_SCAPY.md Normal file
View File

@ -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
```

216
RPI_MONITOR_GUIDE.md Normal file
View File

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

View File

@ -54,6 +54,11 @@ static struct {
struct arg_end *end;
} debug_args;
static struct {
struct arg_str *mac;
struct arg_end *end;
} filter_args;
static void print_monitor_usage(void) {
printf("Usage: monitor <subcommand> [args]\n");
printf("Subcommands:\n");
@ -62,6 +67,7 @@ static void print_monitor_usage(void) {
printf(" status Show current status\n");
printf(" channel <n> Switch channel (while running)\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(" reload Reload config from NVS\n");
printf(" clear Clear NVS config\n");
@ -139,6 +145,73 @@ static int do_monitor_debug(int argc, char **argv) {
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) {
if (argc < 2) {
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], "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) {
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.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 = {
.command = "monitor",
.help = "Monitor Mode: start, stop, channel, status",

View File

@ -673,6 +673,85 @@ void wifi_ctl_status(void) {
}
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 */
uint64_t gen_bytes = 0, write_bytes = 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;
}
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 ---
static void auto_monitor_task_func(void *arg) {
uint8_t channel = (uint8_t)(uintptr_t)arg;

View File

@ -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);
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
void wifi_ctl_auto_monitor_start(uint8_t channel);

View File

@ -1,5 +1,5 @@
idf_component_register(
SRCS "wifi_monitor.c"
INCLUDE_DIRS "."
REQUIRES esp_wifi nvs_flash
REQUIRES esp_wifi nvs_flash esp_timer
)

View File

@ -34,6 +34,7 @@
#include "wifi_monitor.h"
#include "esp_log.h"
#include "esp_wifi.h"
#include "esp_timer.h"
#include "string.h"
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)
static uint32_t s_mismatch_log_counter = 0;
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
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
@ -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;
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
// Note: For HT, spatial streams are typically in HT Capabilities element
// or can be inferred from MCS index (MCS 0-7 = 1 SS, 8-15 = 2 SS, etc.)
// Note: MCS will be extracted from rx_ctrl->rate in the callback
// 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) {
// VHT Control (802.11ac) - bits layout varies by variant
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
*/
@ -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
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;
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
// 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) {
// For HT/VHT/HE, rate >= 128 might encode MCS
// ESP-IDF encoding: rate 128+ might be MCS index
frame_info.mcs = rx_ctrl->rate - 128;
if (frame_info.mcs > 11) frame_info.mcs = 11; // Cap at max MCS
uint8_t extracted_mcs = rx_ctrl->rate - 128;
// Cap MCS based on signal mode
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
@ -317,6 +438,8 @@ static void wifi_promiscuous_rx_cb(void *buf, wifi_promiscuous_pkt_type_t type)
if (frame_info.duration_id > threshold_duration_mismatch_us) {
s_mismatch_log_counter++;
if (s_monitor_debug && (s_mismatch_log_counter % log_every_n_mismatches) == 0) {
/* Check MAC filter before logging */
if (wifi_monitor_debug_filter_match(frame_info.addr2)) {
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);
@ -334,6 +457,7 @@ static void wifi_promiscuous_rx_cb(void *buf, wifi_promiscuous_pkt_type_t type)
}
}
}
}
// ---------------------------------------------------------
// COLLISION CHECK (Attacker Identification Added)
@ -341,6 +465,8 @@ 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 &&
phy_rate_mbps < threshold_phy_rate_fallback_mbps) {
if (s_monitor_debug) {
/* Check MAC filter before logging */
if (wifi_monitor_debug_filter_match(frame_info.addr2)) {
ESP_LOGW("MONITOR", "⚠⚠⚠ COLLISION DETECTED!");
// NEW: Log the Attacker MAC
@ -356,6 +482,7 @@ static void wifi_promiscuous_rx_cb(void *buf, wifi_promiscuous_pkt_type_t type)
}
}
}
}
// Count frame types
switch (frame_info.type) {
@ -375,6 +502,94 @@ static void wifi_promiscuous_rx_cb(void *buf, wifi_promiscuous_pkt_type_t type)
case FRAME_TYPE_DATA:
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;
}
@ -608,3 +823,51 @@ void wifi_monitor_set_debug(bool enable) {
bool wifi_monitor_get_debug(void) {
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;
}

View File

@ -176,6 +176,9 @@ typedef struct {
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)
// Aggregation info
uint8_t ampdu_count; // Number of aggregated MPDUs in A-MPDU (1 = not aggregated)
// Frame size
uint16_t frame_len;
} wifi_frame_info_t;
@ -205,6 +208,11 @@ typedef struct {
uint16_t avg_phy_rate_mbps; // Average PHY rate
uint16_t min_phy_rate_mbps; // Minimum 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;
/**
@ -300,6 +308,20 @@ void wifi_monitor_set_debug(bool enable);
*/
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
}
#endif

101
rpi_capture_ra_ta.sh Executable file
View File

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

307
rpi_capture_ra_ta_python.py Executable file
View File

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

73
rpi_monitor_setup.sh Executable file
View File

@ -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"