Add deauth/disassoc detection — firmware capture, coordinator ingestion, Alerts tab with burst detection
This commit is contained in:
+92
-21
@@ -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 10–15
|
||||
// ── 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
|
||||
|
||||
Reference in New Issue
Block a user