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;
|
||||
} 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",
|
||||
|
|
|
|||
|
|
@ -324,14 +324,14 @@ static void monitor_stats_task(void *arg) {
|
|||
|
||||
if (should_flush && current_offset > 0 && s_batch_mutex && sd_card_is_ready()) {
|
||||
size_t flush_size = 0;
|
||||
|
||||
|
||||
if (xSemaphoreTake(s_batch_mutex, portMAX_DELAY) == pdTRUE) {
|
||||
memcpy(s_flush_buf, s_batch_buf, current_offset);
|
||||
flush_size = current_offset;
|
||||
s_batch_offset = 0;
|
||||
xSemaphoreGive(s_batch_mutex);
|
||||
}
|
||||
|
||||
|
||||
if (flush_size > 0) {
|
||||
esp_err_t write_err = sd_card_write_file(FIWI_TELEMETRY_FILE, s_flush_buf, flush_size, true);
|
||||
if (write_err == ESP_OK) {
|
||||
|
|
@ -627,7 +627,7 @@ void wifi_ctl_set_channel(int channel) {
|
|||
*/
|
||||
static uint32_t wifi_channel_to_frequency(uint8_t channel) {
|
||||
uint32_t freq = 0;
|
||||
|
||||
|
||||
if (channel >= 1 && channel <= 14) {
|
||||
/* 2.4 GHz band: 2407 + (channel * 5) MHz */
|
||||
freq = 2407 + (channel * 5);
|
||||
|
|
@ -644,7 +644,7 @@ static uint32_t wifi_channel_to_frequency(uint8_t channel) {
|
|||
/* 5 GHz UNII-4: 5000 + (channel * 5) MHz */
|
||||
freq = 5000 + (channel * 5);
|
||||
}
|
||||
|
||||
|
||||
return freq;
|
||||
}
|
||||
|
||||
|
|
@ -657,11 +657,11 @@ void wifi_ctl_status(void) {
|
|||
if (s_current_mode == WIFI_CTL_MODE_MONITOR) {
|
||||
uint8_t channel = s_monitor_channel_active;
|
||||
uint32_t freq_mhz = wifi_channel_to_frequency(channel);
|
||||
|
||||
|
||||
/* Display channel info: channel N (FREQ MHz), width: 20 MHz, center1: FREQ MHz */
|
||||
/* Note: Monitor mode currently uses single channel (20 MHz width) */
|
||||
if (freq_mhz > 0) {
|
||||
printf(" Channel: %d (%" PRIu32 " MHz), width: 20 MHz, center1: %" PRIu32 " MHz\n",
|
||||
printf(" Channel: %d (%" PRIu32 " MHz), width: 20 MHz, center1: %" PRIu32 " MHz\n",
|
||||
channel, freq_mhz, freq_mhz);
|
||||
} else {
|
||||
printf(" Channel: %d\n", channel);
|
||||
|
|
@ -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;
|
||||
|
|
@ -713,7 +792,7 @@ bool wifi_ctl_param_is_unsaved(void) {
|
|||
void wifi_ctl_param_save(const char *dummy) {
|
||||
(void)dummy;
|
||||
/* If monitor mode is running, save the active channel; otherwise save staging */
|
||||
uint8_t channel_to_save = (s_current_mode == WIFI_CTL_MODE_MONITOR) ?
|
||||
uint8_t channel_to_save = (s_current_mode == WIFI_CTL_MODE_MONITOR) ?
|
||||
s_monitor_channel_active : s_monitor_channel_staging;
|
||||
if (wifi_cfg_set_monitor_channel(channel_to_save)) {
|
||||
ESP_LOGI(TAG, "Monitor channel (%d) saved to NVS", channel_to_save);
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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,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) {
|
||||
s_mismatch_log_counter++;
|
||||
if (s_monitor_debug && (s_mismatch_log_counter % log_every_n_mismatches) == 0) {
|
||||
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);
|
||||
/* 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);
|
||||
|
||||
// NEW: Log the Source MAC (Addr2)
|
||||
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[3], frame_info.addr2[4], frame_info.addr2[5]);
|
||||
// NEW: Log the Source MAC (Addr2)
|
||||
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[3], frame_info.addr2[4], frame_info.addr2[5]);
|
||||
|
||||
ESP_LOGW("MONITOR", " Expected: %lu us, Actual NAV: %u us (+%ld us)",
|
||||
expected_duration, frame_info.duration_id,
|
||||
frame_info.duration_id - expected_duration);
|
||||
ESP_LOGW("MONITOR", " Retry: %s, RSSI: %d dBm",
|
||||
frame_info.retry ? "YES" : "no", frame_info.rssi);
|
||||
ESP_LOGW("MONITOR", " Expected: %lu us, Actual NAV: %u us (+%ld us)",
|
||||
expected_duration, frame_info.duration_id,
|
||||
frame_info.duration_id - expected_duration);
|
||||
ESP_LOGW("MONITOR", " Retry: %s, RSSI: %d dBm",
|
||||
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 &&
|
||||
phy_rate_mbps < threshold_phy_rate_fallback_mbps) {
|
||||
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
|
||||
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[3], frame_info.addr2[4], frame_info.addr2[5]);
|
||||
// NEW: Log the Attacker MAC
|
||||
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[3], frame_info.addr2[4], frame_info.addr2[5]);
|
||||
|
||||
ESP_LOGW("MONITOR", " Type: %s, Size: %u bytes, Rate: %u Mbps",
|
||||
wifi_frame_type_str(frame_info.type, frame_info.subtype),
|
||||
frame_info.frame_len, phy_rate_mbps);
|
||||
ESP_LOGW("MONITOR", " NAV: %u us (expected %lu us), Retry: YES",
|
||||
frame_info.duration_id, expected_duration);
|
||||
ESP_LOGW("MONITOR", " Type: %s, Size: %u bytes, Rate: %u Mbps",
|
||||
wifi_frame_type_str(frame_info.type, frame_info.subtype),
|
||||
frame_info.frame_len, phy_rate_mbps);
|
||||
ESP_LOGW("MONITOR", " NAV: %u us (expected %lu us), Retry: YES",
|
||||
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:
|
||||
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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in New Issue