firmware&dashboard changes

This commit is contained in:
bot
2026-04-05 13:06:25 +03:00
parent 2636ea1e11
commit e01bbd9e56
11 changed files with 419 additions and 84 deletions
+137 -14
View File
@@ -28,16 +28,33 @@ struct ProbeEvent {
};
struct DeauthEvent {
uint8_t src[6];
uint8_t dst[6];
uint8_t bssid[6];
uint8_t subtype; // 0x0C = deauth, 0x0A = disassoc
uint8_t src[6];
uint8_t dst[6];
uint8_t bssid[6];
uint8_t subtype; // 0xC0 = deauth, 0xA0 = disassoc
uint16_t reason;
int8_t rssi;
};
struct AssocEvent {
uint8_t src[6]; // client MAC
uint8_t bssid[6]; // AP BSSID
char ssid[33]; // SSID the client is associating to
uint8_t subtype; // 0x00 = assoc req, 0x20 = reassoc req
int8_t rssi;
};
static QueueHandle_t probeQueue;
static QueueHandle_t deauthQueue;
static QueueHandle_t assocQueue;
static volatile uint32_t probeDrops = 0;
static volatile uint32_t deauthDrops = 0;
static volatile uint32_t assocDrops = 0;
// Set in promiscuous callback when a close-range attack-signature deauth is seen.
// Checked in loop() to trigger an immediate flush rather than waiting for next hop cycle.
static volatile bool alertPending = false;
// Dedup cache — suppress repeated MAC+SSID pairs within PROBE_DEDUP_SECS
struct DedupEntry { uint8_t mac[6]; char ssid[33]; uint32_t last_ms; };
@@ -107,7 +124,7 @@ static void promiscuous_rx_cb(void* buf, wifi_promiscuous_pkt_type_t type) {
memcpy(ev.src_mac, src, 6);
memcpy(ev.ssid, ssid, 33);
ev.rssi = (int8_t)pkt->rx_ctrl.rssi;
xQueueSend(probeQueue, &ev, 0);
if (xQueueSend(probeQueue, &ev, 0) != pdTRUE) probeDrops++;
return;
}
@@ -124,7 +141,47 @@ static void promiscuous_rx_cb(void* buf, wifi_promiscuous_pkt_type_t type) {
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);
if (xQueueSend(deauthQueue, &ev, 0) != pdTRUE) deauthDrops++;
// Strong close-range attack signature — request an immediate flush
if (ev.reason == 2 && ev.rssi >= RSSI_ALERT_THRESHOLD) {
alertPending = true;
}
return;
}
// ── Association request (0x00) and Reassociation request (0x20) ─────────
// Association req body: capability(2) + listen_interval(2) + IEs
// Reassociation req body: capability(2) + listen_interval(2) + current_AP(6) + IEs
// SSID IE is always first IE: tag(1) + length(1) + ssid(n)
if (subtype == 0x00 || subtype == 0x20) {
uint16_t ie_offset = (subtype == 0x00) ? 28 : 34; // body start + fixed fields
if (len < ie_offset + 2) return;
AssocEvent ev;
memcpy(ev.src, d + 10, 6);
memcpy(ev.bssid, d + 16, 6);
ev.subtype = subtype;
ev.rssi = (int8_t)pkt->rx_ctrl.rssi;
ev.ssid[0] = '\0';
if (d[ie_offset] == 0x00) { // SSID IE tag
uint8_t slen = d[ie_offset + 1];
if (slen > 0 && slen <= 32 && (ie_offset + 2 + slen) <= len) {
bool printable = true;
for (uint8_t i = 0; i < slen; i++) {
if (d[ie_offset + 2 + i] < 32 || d[ie_offset + 2 + i] > 126) {
printable = false; break;
}
}
if (printable) {
memcpy(ev.ssid, d + ie_offset + 2, slen);
ev.ssid[slen] = '\0';
}
}
}
if (xQueueSend(assocQueue, &ev, 0) != pdTRUE) assocDrops++;
return;
}
}
@@ -149,8 +206,9 @@ 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));
assocQueue = xQueueCreate(ASSOC_QUEUE_SIZE, sizeof(AssocEvent));
esp_wifi_set_promiscuous_rx_cb(promiscuous_rx_cb);
esp_wifi_set_promiscuous(true);
Serial.println("[PROBE] Promiscuous mode enabled");
@@ -189,7 +247,18 @@ void loop() {
}
#if PROBE_SNIFF
advanceHop();
// Alert path — close-range attack deauth detected; flush immediately
// rather than waiting up to 4s for the next natural homeChannel pass.
if (alertPending && WiFi.status() == WL_CONNECTED) {
alertPending = false;
esp_wifi_set_channel(homeChannel, WIFI_SECOND_CHAN_NONE);
flushDeauthQueue();
flushProbeQueue();
flushAssocQueue();
lastHop = millis(); // avoid an immediate hop right after flushing
} else {
advanceHop();
}
#endif
delay(10);
@@ -261,6 +330,7 @@ void scanAndSend() {
esp_wifi_set_promiscuous(true);
flushProbeQueue();
flushDeauthQueue();
flushAssocQueue();
// Reset hop state so next cycle starts cleanly from channel 1.
hopIdx = 0;
@@ -293,7 +363,8 @@ const char* importance(int rssi) {
void sendBeaconEvent(int idx) {
String ssid = WiFi.SSID(idx);
ssid.replace("\"", "\\\""); // escape quotes for JSON
ssid.replace("\\", "\\\\"); // escape backslashes first, then quotes
ssid.replace("\"", "\\\"");
String bssid = WiFi.BSSIDstr(idx);
int rssi = WiFi.RSSI(idx);
@@ -325,22 +396,29 @@ void sendBeaconEvent(int idx) {
void sendHeartbeat() {
unsigned long now = millis();
char buf[256];
char buf[320];
snprintf(buf, sizeof(buf),
"{\"node_id\":\"%s\",\"ts\":%lu,\"type\":\"heartbeat\","
"\"uptime_ms\":%lu,\"free_heap\":%lu,\"wifi_rssi\":%d}",
"\"uptime_ms\":%lu,\"free_heap\":%lu,\"wifi_rssi\":%d,"
"\"probe_drops\":%lu,\"deauth_drops\":%lu,\"assoc_drops\":%lu}",
nodeId.c_str(), now,
now,
(unsigned long)ESP.getFreeHeap(),
WiFi.RSSI()
WiFi.RSSI(),
(unsigned long)probeDrops,
(unsigned long)deauthDrops,
(unsigned long)assocDrops
);
udp.beginPacket(COORDINATOR_IP, COORDINATOR_PORT);
udp.print(buf);
udp.endPacket();
Serial.printf("[HB] uptime=%lus heap=%luK ap=%ddBm\n",
Serial.printf("[HB] uptime=%lus heap=%luK ap=%ddBm drops(p/d/a)=%lu/%lu/%lu\n",
now / 1000,
(unsigned long)ESP.getFreeHeap() / 1024,
WiFi.RSSI()
WiFi.RSSI(),
(unsigned long)probeDrops,
(unsigned long)deauthDrops,
(unsigned long)assocDrops
);
}
@@ -426,6 +504,50 @@ void flushDeauthQueue() {
}
}
void sendAssocEvent(const AssocEvent& 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], bssid[18];
macStr(ev.src, src);
macStr(ev.bssid, bssid);
const char* stype = (ev.subtype == 0x00) ? "assoc" : "reassoc";
// Escape quotes in SSID
char ssidEsc[66];
int j = 0;
for (int i = 0; ev.ssid[i] && j < 64; i++) {
if (ev.ssid[i] == '"' || ev.ssid[i] == '\\') ssidEsc[j++] = '\\';
ssidEsc[j++] = ev.ssid[i];
}
ssidEsc[j] = '\0';
char buf[320];
snprintf(buf, sizeof(buf),
"{\"node_id\":\"%s\",\"ts\":%lu,\"type\":\"%s\","
"\"src\":\"%s\",\"bssid\":\"%s\",\"ssid\":\"%s\",\"rssi\":%d}",
nodeId.c_str(), millis(), stype,
src, bssid, ssidEsc, (int)ev.rssi
);
udp.beginPacket(COORDINATOR_IP, COORDINATOR_PORT);
udp.print(buf);
udp.endPacket();
Serial.printf(" [%s] %s → %s \"%s\" %ddBm\n",
stype, src, bssid, ev.ssid[0] ? ev.ssid : "<hidden>", (int)ev.rssi);
}
void flushAssocQueue() {
if (WiFi.status() != WL_CONNECTED) return;
AssocEvent ev;
while (xQueueReceive(assocQueue, &ev, 0) == pdTRUE) {
sendAssocEvent(ev);
}
}
// Advance to the next channel in the hop list.
// Non-blocking — returns immediately if the dwell time hasn't elapsed.
// Flushes queues each time we land back on homeChannel (WiFi is usable there).
@@ -441,6 +563,7 @@ void advanceHop() {
if (ch == homeChannel && WiFi.status() == WL_CONNECTED) {
flushProbeQueue();
flushDeauthQueue();
flushAssocQueue();
}
}