firmware&dashboard changes
This commit is contained in:
+137
-14
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user