ESP8266 – WiFi sniffer

This is my another example of simple and inexpensive WiFi packet analyzer (also known as a WiFi sniffer). The heart of this project is ESP8266 WiFi module which is able to work in a promiscusous mode. This module allows IEEE802.11 network packets capturing for further analyzing. Because of the fact that ESP modules don’t listening on all channels at a time, additional code has been added in the main loop to switch channels in 2s intervals.  Presented sniffer requires a callback function that will process all received promiscusous packets. Example callback function displays few basic information like packet channel, length, RSSI or MAC addresses. The code is using ESP-IDF and can be found on GitHub, click here.

Parts Required

  • ESP8266 Development Module (for example based on ESP-12x)


This code is written in C and can be compiled using xtensa-lx106-elf-gcc.  Don’t know how to start? Please read about building ESP8266 toolchain for Linux.

 * Copyright (c) 2019, Łukasz Marcin Podkalicki <>
 * ESP8266/016
 * Example of WiFi sniffer.

#include <stdint.h>
#include <esp/gpio.h>
#include "espressif/esp_common.h"
#include "esp/uart.h"
#include "FreeRTOS.h"
#include "task.h"

typedef struct {
	signed rssi: 8;
	unsigned rate: 4;
	unsigned is_group: 1;
	unsigned: 1;
	unsigned sig_mode: 2;
	unsigned legacy_length: 12;
	unsigned damatch0: 1;
	unsigned damatch1: 1;
	unsigned bssidmatch0: 1;
	unsigned bssidmatch1: 1;
	unsigned MCS: 7;
	unsigned CWB: 1;
	unsigned HT_length: 16;
	unsigned Smoothing: 1;
	unsigned Not_Sounding: 1;
	unsigned: 1;
	unsigned Aggregation: 1;
	unsigned STBC: 2;
	unsigned FEC_CODING: 1;
	unsigned SGI: 1;
	unsigned rxend_state: 8;
	unsigned ampdu_cnt: 8;
	unsigned channel: 4;
	unsigned: 12;
} wifi_pkt_rx_ctrl_t;

typedef struct {
	wifi_pkt_rx_ctrl_t rx_ctrl;
	uint8_t payload[0]; /* ieee80211 packet buff */
} wifi_promiscuous_pkt_t;

typedef struct {
	unsigned frame_ctrl:16;
	unsigned duration_id:16;
	uint8_t addr1[6]; /* receiver address */
	uint8_t addr2[6]; /* sender address */
	uint8_t addr3[6]; /* filtering address */
	unsigned sequence_ctrl:16;
	uint8_t addr4[6]; /* optional */
} wifi_ieee80211_mac_hdr_t;

typedef struct {
	wifi_ieee80211_mac_hdr_t hdr;
	uint8_t payload[0]; /* network data ended with 4 bytes csum (CRC32) */
} wifi_ieee80211_packet_t;

extern sdk_wifi_promiscuous_cb_t sdk_promiscuous_cb;
static void sniffer_init(void);
static void sniffer_task(void *prv);
static void sniffer_packet_handler(uint8_t *buf, uint16_t len);


	uart_set_baud(0, 115200);
	printf("SDK version:%s\n", sdk_system_get_sdk_version());
	xTaskCreate(sniffer_task, "sniffer_task", 1024, NULL, 1, NULL);


	sdk_wifi_set_opmode(NULL_MODE); // reset AP mode
	sdk_wifi_set_channel(1); // set start channel
	sdk_promiscuous_cb = sniffer_packet_handler; // set promiscuous callback
	sdk_wifi_promiscuous_enable(true); // enable promiscuous mode

sniffer_task(void *prv)

	while (1) {
		vTaskDelay(2000 / portTICK_PERIOD_MS);
		sdk_wifi_set_channel(sdk_wifi_get_channel() % 13 + 1);

sniffer_packet_handler(uint8_t *buff, uint16_t len)

	const wifi_promiscuous_pkt_t *ppkt = (wifi_promiscuous_pkt_t *)buff;
	const wifi_ieee80211_packet_t *ipkt = (wifi_ieee80211_packet_t *)ppkt->payload;
	const wifi_ieee80211_mac_hdr_t *hdr = &ipkt->hdr;

	printf("PACKET LEN=%u CHAN=%02d, RSSI=%02d,"
		" ADDR1=%02x:%02x:%02x:%02x:%02x:%02x,"
		" ADDR2=%02x:%02x:%02x:%02x:%02x:%02x,"
		" ADDR3=%02x:%02x:%02x:%02x:%02x:%02x\n",
		/* ADDR1 */
		/* ADDR2 */
		/* ADDR3 */

Leave a Comment