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>
This commit is contained in:
parent
8ba4bff040
commit
c73e694b9a
|
|
@ -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
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue