feat: Add probe request capture and mDNS service advertisement

- Capture WiFi probe requests (subtype 0x04) in promiscuous callback
- Parse SSID from tagged parameters, emit PROBE_DATA via UDP
- Per-MAC deduplication (10s cooldown) to limit probe flood
- Advertise _esp-csi._udp mDNS service for sensor discovery
This commit is contained in:
user
2026-02-04 19:07:16 +01:00
parent a87151cc9c
commit ca526ef667

View File

@@ -689,7 +689,7 @@ static void ota_task(void *arg)
vTaskDelete(NULL);
}
/* --- Promiscuous mode: deauth/disassoc detection --- */
/* --- Promiscuous mode: deauth/disassoc detection + probe request capture --- */
typedef struct {
uint16_t frame_ctrl;
@@ -700,6 +700,41 @@ typedef struct {
uint16_t seq_ctrl;
} __attribute__((packed)) wifi_ieee80211_mac_hdr_t;
/* Probe request deduplication: report each MAC at most once per 10 seconds */
#define PROBE_DEDUP_SIZE 32
#define PROBE_DEDUP_US 10000000LL
static struct {
uint8_t mac[6];
int64_t ts;
} s_probe_seen[PROBE_DEDUP_SIZE];
static bool probe_dedup_check(const uint8_t *mac)
{
int64_t now = esp_timer_get_time();
int oldest_idx = 0;
int64_t oldest_ts = INT64_MAX;
for (int i = 0; i < PROBE_DEDUP_SIZE; i++) {
if (memcmp(s_probe_seen[i].mac, mac, 6) == 0) {
if (now - s_probe_seen[i].ts < PROBE_DEDUP_US) {
return true; /* seen recently, skip */
}
s_probe_seen[i].ts = now;
return false; /* cooldown expired */
}
if (s_probe_seen[i].ts < oldest_ts) {
oldest_ts = s_probe_seen[i].ts;
oldest_idx = i;
}
}
/* New MAC — replace oldest entry */
memcpy(s_probe_seen[oldest_idx].mac, mac, 6);
s_probe_seen[oldest_idx].ts = now;
return false;
}
static void wifi_promiscuous_cb(void *buf, wifi_promiscuous_pkt_type_t type)
{
if (type != WIFI_PKT_MGMT) return;
@@ -708,41 +743,71 @@ static void wifi_promiscuous_cb(void *buf, wifi_promiscuous_pkt_type_t type)
const wifi_ieee80211_mac_hdr_t *hdr = (wifi_ieee80211_mac_hdr_t *)pkt->payload;
uint8_t subtype = (hdr->frame_ctrl >> 4) & 0x0F;
const char *type_str = NULL;
if (subtype == 0x0C) {
type_str = "deauth";
} else if (subtype == 0x0A) {
type_str = "disassoc";
} else {
/* Deauth (0x0C) / Disassoc (0x0A) */
if (subtype == 0x0C || subtype == 0x0A) {
const char *type_str = (subtype == 0x0C) ? "deauth" : "disassoc";
char alert[160];
int len = snprintf(alert, sizeof(alert),
"ALERT_DATA,%s,%s,"
"%02x:%02x:%02x:%02x:%02x:%02x,"
"%02x:%02x:%02x:%02x:%02x:%02x,"
"%d\n",
s_hostname, type_str,
hdr->addr2[0], hdr->addr2[1], hdr->addr2[2],
hdr->addr2[3], hdr->addr2[4], hdr->addr2[5],
hdr->addr1[0], hdr->addr1[1], hdr->addr1[2],
hdr->addr1[3], hdr->addr1[4], hdr->addr1[5],
pkt->rx_ctrl.rssi);
if (s_udp_socket >= 0) {
sendto(s_udp_socket, alert, len, 0,
(struct sockaddr *)&s_dest_addr, sizeof(s_dest_addr));
}
ESP_LOGW(TAG, "ALERT: %s from " MACSTR " -> " MACSTR " rssi=%d",
type_str,
hdr->addr2[0], hdr->addr2[1], hdr->addr2[2],
hdr->addr2[3], hdr->addr2[4], hdr->addr2[5],
hdr->addr1[0], hdr->addr1[1], hdr->addr1[2],
hdr->addr1[3], hdr->addr1[4], hdr->addr1[5],
pkt->rx_ctrl.rssi);
return;
}
char alert[160];
int len = snprintf(alert, sizeof(alert),
"ALERT_DATA,%s,%s,"
"%02x:%02x:%02x:%02x:%02x:%02x,"
"%02x:%02x:%02x:%02x:%02x:%02x,"
"%d\n",
s_hostname, type_str,
hdr->addr2[0], hdr->addr2[1], hdr->addr2[2],
hdr->addr2[3], hdr->addr2[4], hdr->addr2[5],
hdr->addr1[0], hdr->addr1[1], hdr->addr1[2],
hdr->addr1[3], hdr->addr1[4], hdr->addr1[5],
pkt->rx_ctrl.rssi);
/* Probe request (0x04) */
if (subtype == 0x04) {
/* Dedup: skip if this MAC was reported recently */
if (probe_dedup_check(hdr->addr2)) return;
if (s_udp_socket >= 0) {
sendto(s_udp_socket, alert, len, 0,
(struct sockaddr *)&s_dest_addr, sizeof(s_dest_addr));
/* Parse SSID from tagged parameters after MAC header */
const uint8_t *body = pkt->payload + sizeof(wifi_ieee80211_mac_hdr_t);
int body_len = pkt->rx_ctrl.sig_len - sizeof(wifi_ieee80211_mac_hdr_t);
char ssid[33] = "";
if (body_len >= 2 && body[0] == 0) { /* Tag 0 = SSID */
int ssid_len = body[1];
if (ssid_len > 0 && ssid_len <= 32 && ssid_len + 2 <= body_len) {
memcpy(ssid, &body[2], ssid_len);
ssid[ssid_len] = '\0';
}
}
char probe[192];
int len = snprintf(probe, sizeof(probe),
"PROBE_DATA,%s,%02x:%02x:%02x:%02x:%02x:%02x,%d,%s\n",
s_hostname,
hdr->addr2[0], hdr->addr2[1], hdr->addr2[2],
hdr->addr2[3], hdr->addr2[4], hdr->addr2[5],
pkt->rx_ctrl.rssi,
ssid);
if (s_udp_socket >= 0) {
sendto(s_udp_socket, probe, len, 0,
(struct sockaddr *)&s_dest_addr, sizeof(s_dest_addr));
}
}
ESP_LOGW(TAG, "ALERT: %s from " MACSTR " -> " MACSTR " rssi=%d",
type_str,
hdr->addr2[0], hdr->addr2[1], hdr->addr2[2],
hdr->addr2[3], hdr->addr2[4], hdr->addr2[5],
hdr->addr1[0], hdr->addr1[1], hdr->addr1[2],
hdr->addr1[3], hdr->addr1[4], hdr->addr1[5],
pkt->rx_ctrl.rssi);
}
static void wifi_promiscuous_init(void)
@@ -753,7 +818,7 @@ static void wifi_promiscuous_init(void)
ESP_ERROR_CHECK(esp_wifi_set_promiscuous_filter(&filt));
ESP_ERROR_CHECK(esp_wifi_set_promiscuous_rx_cb(wifi_promiscuous_cb));
ESP_ERROR_CHECK(esp_wifi_set_promiscuous(true));
ESP_LOGI(TAG, "Promiscuous mode: deauth/disassoc detection enabled");
ESP_LOGI(TAG, "Promiscuous mode: deauth/disassoc/probe detection enabled");
}
/* --- Command handler --- */
@@ -1125,11 +1190,12 @@ void app_main()
}
#endif
/* mDNS: announce as <hostname>.local */
/* mDNS: announce as <hostname>.local with _esp-csi._udp service */
ESP_ERROR_CHECK(mdns_init());
mdns_hostname_set(s_hostname);
mdns_instance_name_set("ESP32 CSI Sensor");
ESP_LOGI(TAG, "mDNS hostname: %s.local", s_hostname);
mdns_service_add(NULL, "_esp-csi", "_udp", s_target_port, NULL, 0);
ESP_LOGI(TAG, "mDNS hostname: %s.local (_esp-csi._udp:%d)", s_hostname, s_target_port);
/* Watchdog: 30s timeout, auto-reboot on hang */
esp_task_wdt_config_t wdt_cfg = {