Files
c5-project/ESP32-C5-Toolkit/main/wifi_sniffer.c
2026-05-03 23:19:51 -07:00

396 lines
14 KiB
C

#include "wifi_sniffer.h"
#include "signal_analysis.h"
#include "esp_wifi.h"
#include "esp_log.h"
#include "esp_system.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "freertos/semphr.h"
#include <string.h>
#include <stdlib.h>
static const char *TAG = "wifi_sniffer";
// Maximum number of packets to store
#define MAX_PACKETS_QUEUE 32
#define MAX_PACKET_SIZE 1024
// Structure to hold packet info
typedef struct {
uint8_t data[MAX_PACKET_SIZE];
uint16_t length;
int8_t rssi;
uint8_t channel;
wifi_pkt_rx_ctrl_t rx_ctrl;
} packet_info_t;
// Global variables
static QueueHandle_t packet_queue = NULL;
static SemaphoreHandle_t sniffer_running_mutex = NULL;
static volatile bool is_sniffer_running = false;
static uint8_t current_channel = 0;
static uint8_t current_filter = 0;
static TaskHandle_t channel_hopper_task_handle = NULL;
// Channel hopping settings
#define CHANNEL_HOP_INTERVAL_MS 200
static const uint8_t channels[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13};
static const uint8_t channels_count = sizeof(channels)/sizeof(channels[0]);
// Forward declaration
static void wifi_sniffer_packet_handler(void *buf, wifi_promiscuous_pkt_type_t type);
static void channel_hopper_task(void *pvParameters);
static void single_channel_retry_task(void *pvParameters);
// Start WiFi sniffer
bool start_wifi_sniffer(uint8_t channel, uint8_t filter_type) {
ESP_LOGI(TAG, "Starting WiFi sniffer on channel %d with filter type %d", channel, filter_type);
// Create mutex if not already created
if (sniffer_running_mutex == NULL) {
sniffer_running_mutex = xSemaphoreCreateMutex();
if (sniffer_running_mutex == NULL) {
ESP_LOGI(TAG, "Failed to create sniffer mutex");
return false;
}
}
// Take mutex
if (xSemaphoreTake(sniffer_running_mutex, portMAX_DELAY) != pdTRUE) {
ESP_LOGI(TAG, "Failed to take sniffer mutex");
return false;
}
// If sniffer is already running, release mutex and return error
// (calling stop_wifi_sniffer while holding the mutex would cause deadlock)
if (is_sniffer_running) {
ESP_LOGW(TAG, "Sniffer already running");
xSemaphoreGive(sniffer_running_mutex);
return false;
}
// Create packet queue if not already created
if (packet_queue == NULL) {
packet_queue = xQueueCreate(MAX_PACKETS_QUEUE, sizeof(packet_info_t*));
if (packet_queue == NULL) {
ESP_LOGI(TAG, "Failed to create packet queue");
xSemaphoreGive(sniffer_running_mutex);
return false;
}
} else {
// Queue exists, make sure it's empty
packet_info_t *packet;
while (xQueueReceive(packet_queue, &packet, 0) == pdTRUE) {
if (packet) free(packet);
}
}
// Save configuration
current_channel = channel;
current_filter = filter_type;
// Get current WiFi mode and save it
wifi_mode_t original_mode;
ESP_ERROR_CHECK(esp_wifi_get_mode(&original_mode));
// Set to APSTA mode to ensure we keep the AP running while scanning
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_APSTA));
// Set sniffer filter based on packet type
wifi_promiscuous_filter_t filter = {0};
switch (filter_type) {
case 1: // Management frames
filter.filter_mask = WIFI_PROMIS_FILTER_MASK_MGMT;
break;
case 2: // Data frames
filter.filter_mask = WIFI_PROMIS_FILTER_MASK_DATA;
break;
case 3: // Control frames
filter.filter_mask = WIFI_PROMIS_FILTER_MASK_CTRL;
break;
case 4: // Beacon frames only
filter.filter_mask = WIFI_PROMIS_FILTER_MASK_MGMT;
// We'll filter in the callback for beacon frames only
break;
case 5: // Probe frames only
filter.filter_mask = WIFI_PROMIS_FILTER_MASK_MGMT;
// We'll filter in the callback for probe frames only
break;
default: // All packets
filter.filter_mask = WIFI_PROMIS_FILTER_MASK_ALL;
}
esp_wifi_set_promiscuous_filter(&filter);
// Register packet handler
esp_wifi_set_promiscuous_rx_cb(wifi_sniffer_packet_handler);
// Enable promiscuous mode
esp_wifi_set_promiscuous(true);
// Set the channel or start channel hopping
if (channel == 0) {
// Start channel hopping task
xTaskCreate(channel_hopper_task, "channel_hopper", 2048, NULL, 5, &channel_hopper_task_handle);
} else {
// Set specific channel
esp_err_t err = esp_wifi_set_channel(channel, WIFI_SECOND_CHAN_NONE);
if (err != ESP_OK) {
ESP_LOGW(TAG, "Failed to set initial channel %d: %s", channel, esp_err_to_name(err));
ESP_LOGI(TAG, "Will retry setting channel in background");
// Create a parameter structure to pass the channel
uint8_t *channel_param = malloc(sizeof(uint8_t));
if (channel_param) {
*channel_param = channel;
// Start a task to keep trying to set the channel
BaseType_t ret = xTaskCreate(single_channel_retry_task, "channel_retry", 2048, channel_param, 5, &channel_hopper_task_handle);
if (ret != pdPASS) {
// Task creation failed, free the allocated memory
free(channel_param);
ESP_LOGE(TAG, "Failed to create channel retry task");
}
}
}
}
is_sniffer_running = true;
xSemaphoreGive(sniffer_running_mutex);
ESP_LOGI(TAG, "WiFi sniffer started successfully");
return true;
}
// Helper function to clean up packet queue (fixes memory leak)
static void cleanup_packet_queue(void) {
if (packet_queue == NULL) return;
packet_info_t *packet;
int freed_count = 0;
while (xQueueReceive(packet_queue, &packet, 0) == pdTRUE) {
if (packet) {
free(packet);
freed_count++;
}
}
if (freed_count > 0) {
ESP_LOGI(TAG, "Cleaned up %d packets from queue", freed_count);
}
}
// Stop WiFi sniffer
bool stop_wifi_sniffer(void) {
ESP_LOGI(TAG, "Stopping WiFi sniffer");
// Take mutex
if (sniffer_running_mutex == NULL || xSemaphoreTake(sniffer_running_mutex, portMAX_DELAY) != pdTRUE) {
ESP_LOGI(TAG, "Failed to take sniffer mutex");
return false;
}
// Check if sniffer is running
if (!is_sniffer_running) {
ESP_LOGW(TAG, "Sniffer not running");
xSemaphoreGive(sniffer_running_mutex);
return false;
}
// Mark as not running first to stop packet handler from adding more packets
is_sniffer_running = false;
// Disable promiscuous mode
esp_wifi_set_promiscuous(false);
// Stop channel hopping task if running (safe deletion)
TaskHandle_t task_to_delete = channel_hopper_task_handle;
channel_hopper_task_handle = NULL; // Clear handle first
if (task_to_delete != NULL) {
// Suspend task before deletion to ensure it's in a safe state
vTaskSuspend(task_to_delete);
vTaskDelay(pdMS_TO_TICKS(10)); // Allow task to reach safe point
vTaskDelete(task_to_delete);
}
// Clean up any remaining packets in the queue (fixes memory leak)
cleanup_packet_queue();
xSemaphoreGive(sniffer_running_mutex);
ESP_LOGI(TAG, "WiFi sniffer stopped successfully");
return true;
}
// Get captured packets
int get_captured_packets(void **packets, int max_packets) {
// Take mutex
if (sniffer_running_mutex == NULL || xSemaphoreTake(sniffer_running_mutex, portMAX_DELAY) != pdTRUE) {
ESP_LOGI(TAG, "Failed to take sniffer mutex");
return 0;
}
// Check if sniffer is running
if (!is_sniffer_running) {
xSemaphoreGive(sniffer_running_mutex);
return 0;
}
// Get packets from queue (up to max_packets)
int count = 0;
packet_info_t *packet;
while (count < max_packets && xQueueReceive(packet_queue, &packet, 0) == pdTRUE) {
packets[count++] = packet;
}
xSemaphoreGive(sniffer_running_mutex);
return count;
}
// Channel hopper task
static void channel_hopper_task(void *pvParameters) {
int current_idx = 0;
int failed_attempts = 0;
ESP_LOGI(TAG, "Channel hopper task started");
while (1) {
// Use each channel in sequence
uint8_t new_channel = channels[current_idx];
// Try to set the channel and check for errors
esp_err_t err = esp_wifi_set_channel(new_channel, WIFI_SECOND_CHAN_NONE);
if (err == ESP_OK) {
// Channel set successfully
ESP_LOGD(TAG, "Hopped to channel %d", new_channel);
current_idx = (current_idx + 1) % channels_count;
failed_attempts = 0;
} else {
// Failed to set channel
failed_attempts++;
ESP_LOGD(TAG, "Failed to hop to channel %d: %s (attempt %d)",
new_channel, esp_err_to_name(err), failed_attempts);
// If we've failed multiple times, wait longer before trying again
if (failed_attempts > 5) {
ESP_LOGW(TAG, "Multiple channel hop failures, waiting longer...");
vTaskDelay((CHANNEL_HOP_INTERVAL_MS * 5) / portTICK_PERIOD_MS);
// Reset failed attempts counter after waiting
failed_attempts = 0;
continue;
}
}
// Wait before hopping again
vTaskDelay(CHANNEL_HOP_INTERVAL_MS / portTICK_PERIOD_MS);
}
}
// Packet handler
static void wifi_sniffer_packet_handler(void *buf, wifi_promiscuous_pkt_type_t type) {
if (!buf) return;
// Check if sniffer is running (volatile read for thread safety)
// Note: We don't take the mutex here as it would be too slow for packet handling
// The is_sniffer_running flag is checked atomically and we gracefully handle
// any packets that arrive during shutdown
if (!is_sniffer_running || packet_queue == NULL) return;
wifi_promiscuous_pkt_t *pkt = (wifi_promiscuous_pkt_t*)buf;
wifi_pkt_rx_ctrl_t *rx_ctrl = &pkt->rx_ctrl;
// If we have a specific filter for beacon or probe, check it here
if (current_filter == 4 || current_filter == 5) {
// Get frame control field to determine if it's a beacon or probe
const uint8_t *frame = pkt->payload;
uint16_t frame_control = frame[0] | (frame[1] << 8);
uint8_t type = (frame_control & 0x000C) >> 2;
uint8_t subtype = (frame_control & 0x00F0) >> 4;
if (current_filter == 4) { // Beacon frames only
if (!(type == 0 && subtype == 8)) { // Not a beacon
return;
}
} else if (current_filter == 5) { // Probe frames only
if (!(type == 0 && (subtype == 4 || subtype == 5))) { // Not a probe request/response
return;
}
}
}
// Allocate memory for packet info
packet_info_t *packet_info = malloc(sizeof(packet_info_t));
if (!packet_info) {
ESP_LOGI(TAG, "Failed to allocate memory for packet info");
return;
}
// Copy packet data
uint16_t payload_len = rx_ctrl->sig_len - 4; // Remove FCS
if (payload_len > MAX_PACKET_SIZE) {
payload_len = MAX_PACKET_SIZE;
}
// Fill packet info
packet_info->rx_ctrl = *rx_ctrl;
packet_info->length = payload_len;
packet_info->rssi = rx_ctrl->rssi;
packet_info->channel = rx_ctrl->channel;
memcpy(packet_info->data, pkt->payload, payload_len);
// Update signal analysis
signal_update_packet(rx_ctrl->channel, rx_ctrl->rssi);
// Add to queue, if queue is full, discard oldest packet
packet_info_t *old_packet;
if (xQueueSend(packet_queue, &packet_info, 0) != pdTRUE) {
if (xQueueReceive(packet_queue, &old_packet, 0) == pdTRUE) {
if (old_packet) free(old_packet);
xQueueSend(packet_queue, &packet_info, 0);
} else {
// This shouldn't happen, but free the packet if we can't add it
free(packet_info);
}
}
}
// Task to retry setting a single channel
static void single_channel_retry_task(void *pvParameters) {
uint8_t target_channel = *(uint8_t*)pvParameters;
int retry_count = 0;
// Free the parameter memory
free(pvParameters);
ESP_LOGI(TAG, "Channel retry task started for channel %d", target_channel);
while (is_sniffer_running && retry_count < 20) { // Limit retries to avoid infinite loop
esp_err_t err = esp_wifi_set_channel(target_channel, WIFI_SECOND_CHAN_NONE);
if (err == ESP_OK) {
ESP_LOGI(TAG, "Successfully set channel to %d after %d retries", target_channel, retry_count);
break;
}
retry_count++;
ESP_LOGD(TAG, "Retry %d: Failed to set channel %d: %s",
retry_count, target_channel, esp_err_to_name(err));
// Exponential backoff for retries
int delay_ms = CHANNEL_HOP_INTERVAL_MS * (1 << (retry_count > 5 ? 5 : retry_count));
vTaskDelay(delay_ms / portTICK_PERIOD_MS);
}
if (retry_count >= 20) {
ESP_LOGW(TAG, "Failed to set channel %d after maximum retries", target_channel);
}
// Delete self
channel_hopper_task_handle = NULL;
vTaskDelete(NULL);
}