Add deauth/disassoc detection — firmware capture, coordinator ingestion, Alerts tab with burst detection

This commit is contained in:
bot
2026-04-03 22:24:25 +03:00
parent cbef4143ee
commit fbf5b5c738
7 changed files with 426 additions and 23 deletions
+92 -21
View File
@@ -25,7 +25,17 @@ struct ProbeEvent {
int8_t rssi;
};
struct DeauthEvent {
uint8_t src[6];
uint8_t dst[6];
uint8_t bssid[6];
uint8_t subtype; // 0x0C = deauth, 0x0A = disassoc
uint16_t reason;
int8_t rssi;
};
static QueueHandle_t probeQueue;
static QueueHandle_t deauthQueue;
// Dedup cache — suppress repeated MAC+SSID pairs within PROBE_DEDUP_SECS
struct DedupEntry { uint8_t mac[6]; char ssid[33]; uint32_t last_ms; };
@@ -54,39 +64,60 @@ static bool isDuplicate(const uint8_t* mac, const char* ssid) {
}
// Promiscuous callback — runs in WiFi task context, not safe to call UDP here.
// Parse probe request frames and push to queue for the main loop to send.
// Parse probe request, deauth, and disassoc frames and push to queues.
static void promiscuous_rx_cb(void* buf, wifi_promiscuous_pkt_type_t type) {
if (type != WIFI_PKT_MGMT) return;
const wifi_promiscuous_pkt_t* pkt = (const wifi_promiscuous_pkt_t*)buf;
const uint8_t* d = pkt->payload;
const uint8_t* d = pkt->payload;
uint16_t len = pkt->rx_ctrl.sig_len;
if (len < 28) return;
if (d[0] != 0x40) return; // byte 0 = 0x40 → management, probe request subtype
if (len < 24) return;
uint8_t subtype = d[0];
const uint8_t* src = d + 10; // source address at bytes 1015
// ── Probe request (0x40) ─────────────────────────────────────────────────
if (subtype == 0x40) {
if (len < 28) return;
const uint8_t* src = d + 10;
// Parse SSID tag (tag 0) from frame body starting at byte 24
char ssid[33] = "";
if (d[24] == 0x00) {
uint8_t slen = d[25];
if (slen > 0 && slen <= 32 && (26 + slen) <= len) {
bool printable = true;
for (uint8_t i = 0; i < slen; i++) {
if (d[26 + i] < 32 || d[26 + i] > 126) { printable = false; break; }
char ssid[33] = "";
if (d[24] == 0x00) {
uint8_t slen = d[25];
if (slen > 0 && slen <= 32 && (26 + slen) <= len) {
bool printable = true;
for (uint8_t i = 0; i < slen; i++) {
if (d[26 + i] < 32 || d[26 + i] > 126) { printable = false; break; }
}
if (printable) { memcpy(ssid, d + 26, slen); ssid[slen] = '\0'; }
}
if (printable) { memcpy(ssid, d + 26, slen); ssid[slen] = '\0'; }
}
if (isDuplicate(src, ssid)) return;
ProbeEvent ev;
memcpy(ev.src_mac, src, 6);
memcpy(ev.ssid, ssid, 33);
ev.rssi = (int8_t)pkt->rx_ctrl.rssi;
xQueueSend(probeQueue, &ev, 0);
return;
}
if (isDuplicate(src, ssid)) return;
// ── Deauth (0xC0) and Disassoc (0xA0) ───────────────────────────────────
if (subtype == 0xC0 || subtype == 0xA0) {
// 802.11 frame header: addr1 (dst) @ 4, addr2 (src) @ 10, addr3 (bssid) @ 16
// Frame body starts at byte 24: 2-byte reason code
if (len < 26) return;
ProbeEvent ev;
memcpy(ev.src_mac, src, 6);
memcpy(ev.ssid, ssid, 33);
ev.rssi = (int8_t)pkt->rx_ctrl.rssi;
xQueueSend(probeQueue, &ev, 0); // 0 timeout: drop if queue full
DeauthEvent ev;
memcpy(ev.dst, d + 4, 6);
memcpy(ev.src, d + 10, 6);
memcpy(ev.bssid, d + 16, 6);
ev.subtype = subtype;
ev.reason = (uint16_t)d[24] | ((uint16_t)d[25] << 8);
ev.rssi = (int8_t)pkt->rx_ctrl.rssi;
xQueueSend(deauthQueue, &ev, 0);
return;
}
}
#endif // PROBE_SNIFF
@@ -109,7 +140,8 @@ void setup() {
#if PROBE_SNIFF
memset(dedupCache, 0, sizeof(dedupCache));
probeQueue = xQueueCreate(PROBE_QUEUE_SIZE, sizeof(ProbeEvent));
probeQueue = xQueueCreate(PROBE_QUEUE_SIZE, sizeof(ProbeEvent));
deauthQueue = xQueueCreate(DEAUTH_QUEUE_SIZE, sizeof(DeauthEvent));
esp_wifi_set_promiscuous_rx_cb(promiscuous_rx_cb);
esp_wifi_set_promiscuous(true);
Serial.println("[PROBE] Promiscuous mode enabled");
@@ -184,6 +216,7 @@ void scanAndSend() {
// Scan may have disabled promiscuous mode internally — re-enable it.
esp_wifi_set_promiscuous(true);
flushProbeQueue();
flushDeauthQueue();
#endif
}
@@ -306,4 +339,42 @@ void flushProbeQueue() {
}
}
void sendDeauthEvent(const DeauthEvent& ev) {
auto macStr = [](const uint8_t* m, char* out) {
snprintf(out, 18, "%02X:%02X:%02X:%02X:%02X:%02X",
m[0], m[1], m[2], m[3], m[4], m[5]);
};
char src[18], dst[18], bssid[18];
macStr(ev.src, src);
macStr(ev.dst, dst);
macStr(ev.bssid, bssid);
const char* stype = (ev.subtype == 0xC0) ? "deauth" : "disassoc";
char buf[384];
snprintf(buf, sizeof(buf),
"{\"node_id\":\"%s\",\"ts\":%lu,\"type\":\"%s\","
"\"src\":\"%s\",\"dst\":\"%s\",\"bssid\":\"%s\","
"\"reason\":%u,\"rssi\":%d}",
nodeId.c_str(), millis(), stype,
src, dst, bssid,
(unsigned)ev.reason, (int)ev.rssi
);
udp.beginPacket(COORDINATOR_IP, COORDINATOR_PORT);
udp.print(buf);
udp.endPacket();
Serial.printf(" [%s] %s → %s bssid=%s reason=%u %ddBm\n",
stype, src, dst, bssid, (unsigned)ev.reason, (int)ev.rssi);
}
void flushDeauthQueue() {
if (WiFi.status() != WL_CONNECTED) return;
DeauthEvent ev;
while (xQueueReceive(deauthQueue, &ev, 0) == pdTRUE) {
sendDeauthEvent(ev);
}
}
#endif // PROBE_SNIFF