--- /dev/null
+# The following lines of boilerplate have to be in your project's CMakeLists
+# in this exact order for cmake to work correctly
+cmake_minimum_required(VERSION 3.5)
+
+set(EXTRA_COMPONENT_DIRS $ENV{IDF_PATH}/examples/system/console/components)
+
+include($ENV{IDF_PATH}/tools/cmake/project.cmake)
+project(simple_sniffer)
--- /dev/null
+#
+# This is a project Makefile. It is assumed the directory this Makefile resides in is a
+# project subdirectory.
+#
+
+PROJECT_NAME := simple_sniffer
+
+EXTRA_COMPONENT_DIRS := $(IDF_PATH)/examples/system/console/components
+
+include $(IDF_PATH)/make/project.mk
+
--- /dev/null
+# Simple Sniffer Example
+
+(See the README.md file in the upper level 'examples' directory for more information about examples.)
+
+## Overview
+
+This example demonstrates basic usage of wifi sniffer mode by saving packets into SD card with pcap format. Go to wikipedia for more information about [pcap](https://en.wikipedia.org/wiki/Pcap).
+
+This example is based on esp-idf's console component. For more information about console you should read this [guide](https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/console.html).
+
+## How to use example
+
+### Hardware Required
+
+To run this example, you should have one ESP32 dev board integrated with a SD card slot (e.g ESP32-WROVER Kit) or just connect ESP32-DevKitC to a SD card breakout board.
+
+### Configure the project
+
+Enter `make menuconfig` if you are using GNU Make based build system or enter `idf.py menuconfig` if you are using CMake based build system. Then go into `Example Configuration` menu.
+
+- Check `Store command history in flash` if you want to save command history into flash (recommend).
+- Set the mount point in your filesystem, for example, `/sdcard` if you want to store pcap file into SD card.
+- Set the length of sniffer work queue.
+- Set the stack size of the sniffer task.
+- Set the priority of the sniffer task.
+- Set the max number of packets to store in a single pcap file. The number of packets usually will be very large, so we just truncate them into multiple files. You should set a threshold value here.
+
+### Build and Flash
+
+Enter `make -j4 flash monitor` if you are using GNU Make based build system or enter `idf.py build flash monitor` if you' are using CMake based build system.
+
+(To exit the serial monitor, type ``Ctrl-]``.)
+
+See the [Getting Started Guide](https://docs.espressif.com/projects/esp-idf/en/latest/get-started/index.html) for full steps to configure and use ESP-IDF to build projects.
+
+## Example Output
+
+### `sniffer` Command Usage
+
+> sniffer [-f <file>][-i ] [-F <mgmt|data|ctrl|misc|mpdu|ampdu>]... [-c <channel>][--stop]
+> Capture specific packet and store in pcap format
+> -f, --file=<file> name of the file storing the packets in pcap format
+> -i, --interface=<wlan> which interface to capture packet
+> -F, --filter=<mgmt|data|ctrl|misc|mpdu|ampdu> filter parameters
+> -c, --channel=<channel> communication channel to use
+> --stop stop running sniffer
+
+The `sniffer` command support some important options as follow:
+
+* `-f`: Specify the name of file who will store the packets, default value is `sniffer`, and the resulting file name will be like “snifferX.pcap”, here ‘X’ shows the file’s order.
+* `-i`: Specify the interface to sniffer packets, currently only support `wlan`
+* `-c` :Specify the channel to sniffer packet
+* `-F`: Specify the filter condition, currently only support following filter conditions, you can select any number of them
+ * mgmt: Management packets
+ * data: Data packets
+ * ctrl: Control packets
+ * misc: Other packets
+ * mpdu: MPDU packets
+ * ampdu: AMPDU packets
+* `--stop`: Stop sniffer job
+
+### Mount SD Card
+
+```bash
+ =======================================================
+ | Steps to sniffer WiFi packets |
+ | |
+ | 1. Enter 'help' to check all commands' usage |
+ | 2. Enter 'mount <device>' to mount filesystem |
+ | 3. Enter 'sniffer' to start capture packets |
+ | 4. Enter 'unmount <device>' to unmount filesystem |
+ | |
+ =======================================================
+
+esp32> mount sd
+I (158912) example: Initializing SD card
+I (158912) example: Using SDMMC peripheral
+I (158912) gpio: GPIO[13]| InputEn: 0| OutputEn: 1| OpenDrain: 0| Pullup: 0| Pulldown: 0| Intr:0
+Name: SA16G
+Type: SDHC/SDXC
+Speed: 20 MHz
+Size: 14832MB
+```
+
+### Start Sniffer
+
+```bash
+esp32> sniffer -f sniffer-example -i wlan -c 2
+I (36200) cmd_sniffer: Start WiFi Promicuous Mode
+I (36270) phy: phy_version: 4000, b6198fa, Sep 3 2018, 15:11:06, 0, 0
+I (36270) wifi: ic_enable_sniffer
+I (36290) pcap: Store packets to file: /sdcard/sniffer-example0.pcap
+I (103810) pcap: Close Pcap file OK
+I (103830) pcap: Store packets to file: /sdcard/sniffer-example1.pcap
+I (177300) pcap: Close Pcap file OK
+I (177320) pcap: Store packets to file: /sdcard/sniffer-example2.pcap
+esp32> sniffer --stop
+I (212250) wifi: ic_disable_sniffer
+I (212250) wifi: flush txq
+I (212250) wifi: stop sw txq
+I (212260) wifi: lmac stop hw txq
+I (212340) pcap: Close Pcap file OK
+I (212340) cmd_sniffer: Sniffer Stopped
+```
+
+### Unmount SD Card
+
+```bash
+esp32> unmount sd
+I (248800) example: Card unmounted
+```
+
+### Open PCap File in Wireshark
+
+![sniffer-example0.pcap](sniffer-example0-pcap.png)
+
+## Troubleshooting
+
+- Make sure you have pluged in your SD card and mount it into filesystem before doing sniffer work or you will get error message like “Create file /sdcard/sniffer0.pcap failed”.
+- To protect the SD card, we recommand you to execute command `unmount sd` before you plug out your SD card.
+
+
+
+(For any technical queries, please open an [issue](https://github.com/espressif/esp-idf/issues) on GitHub. We will get back to you as soon as possible.)
\ No newline at end of file
--- /dev/null
+set(COMPONENT_ADD_INCLUDEDIRS .)
+
+set(COMPONENT_SRCS "pcap.c")
+
+register_component()
--- /dev/null
+#
+# Component Makefile
+#
+# This Makefile should, at the very least, just include $(SDK_PATH)/Makefile. By default,
+# this will take the sources in the src/ directory, compile them and link them into
+# lib(subdirectory_name).a in the build directory. This behaviour is entirely configurable,
+# please read the SDK documents if you need to do this.
+#
+
+#include $(IDF_PATH)/make/component_common.mk
+COMPONENT_ADD_INCLUDEDIRS := .
--- /dev/null
+/* pcap encoder.
+ This example code is in the Public Domain (or CC0 licensed, at your option.)
+
+ Unless required by applicable law or agreed to in writing, this
+ software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
+ CONDITIONS OF ANY KIND, either express or implied.
+*/
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/unistd.h>
+#include <sys/fcntl.h>
+#include "esp_types.h"
+#include "esp_err.h"
+#include "esp_log.h"
+#include "pcap.h"
+
+static const char *TAG = "pcap";
+#define PCAP_CHECK(a, str, ret_val, ...) \
+ do \
+ { \
+ if (!(a)) \
+ { \
+ ESP_LOGE(TAG, "%s(%d): " str, __FUNCTION__, __LINE__, ##__VA_ARGS__); \
+ return (ret_val); \
+ } \
+ } while (0)
+
+/**
+ * @brief Pcap File Header Type Definition
+ *
+ */
+typedef struct {
+ uint32_t magic; /*!< Magic Number */
+ uint16_t major; /*!< Major Version */
+ uint16_t minor; /*!< Minor Version */
+ uint32_t zone; /*!< Time Zone Offset */
+ uint32_t sigfigs; /*!< Timestamp Accuracy */
+ uint32_t snaplen; /*!< Max Length to Capture */
+ uint32_t link_type; /*!< Link Layer Type */
+} pcap_file_header_t;
+
+/**
+ * @brief Pcap Packet Header Type Definition
+ *
+ */
+typedef struct {
+ uint32_t seconds; /*!< Number of seconds since January 1st, 1970, 00:00:00 GMT */
+ uint32_t microseconds; /*!< Number of microseconds when the packet was captured(offset from seconds) */
+ uint32_t capture_length; /*!< Number of bytes of captured data, not longer than packet_length */
+ uint32_t packet_length; /*!< Actual length of current packet */
+} pcap_packet_header_t;
+
+static FILE *file = NULL;
+
+esp_err_t pcap_capture_packet(void *payload, uint32_t length, uint32_t seconds, uint32_t microseconds)
+{
+ if (!file) {
+ return ESP_FAIL;
+ }
+ size_t real_write = 0;
+ pcap_packet_header_t header = {
+ .seconds = seconds,
+ .microseconds = microseconds,
+ .capture_length = length,
+ .packet_length = length
+ };
+ real_write = fwrite(&header, sizeof(header), 1, file);
+ PCAP_CHECK(real_write == 1, "Write packet header error", ESP_FAIL);
+ real_write = fwrite(payload, sizeof(uint8_t), length, file);
+ PCAP_CHECK(real_write == length, "Write packet payload error", ESP_FAIL);
+ /* Flush content in the buffer into device */
+ fflush(file);
+ return ESP_OK;
+}
+
+esp_err_t pcap_close(void)
+{
+ if (!file) {
+ return ESP_OK;
+ }
+ if (fclose(file)) {
+ ESP_LOGE(TAG, "Close pcap file failed");
+ file = NULL;
+ return ESP_FAIL;
+ }
+ ESP_LOGI(TAG, "Close Pcap file OK");
+ file = NULL;
+ return ESP_OK;
+}
+
+esp_err_t pcap_new(pcap_config_t *config)
+{
+ file = config->fp;
+ /* Write Pcap File header */
+ pcap_file_header_t header = {
+ .magic = PCAP_MAGIC_BIG_ENDIAN,
+ .major = PCAP_VERSION_MAJOR,
+ .minor = PCAP_VERSION_MINOR,
+ .zone = PCAP_TIME_ZONE_GMT,
+ .sigfigs = 0,
+ .snaplen = 0x40000,
+ .link_type = config->link_type
+ };
+ size_t real_write = fwrite(&header, sizeof(header), 1, file);
+ if (real_write != 1) {
+ ESP_LOGE(TAG, "Write Pcap file header error");
+ goto err_write;
+ }
+ /* Flush content in the buffer into device */
+ fflush(file);
+ return ESP_OK;
+
+ /* Error Handling */
+err_write:
+ fclose(file);
+ return ESP_FAIL;
+}
--- /dev/null
+/* pcap encoder.
+ This example code is in the Public Domain (or CC0 licensed, at your option.)
+
+ Unless required by applicable law or agreed to in writing, this
+ software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
+ CONDITIONS OF ANY KIND, either express or implied.
+*/
+#pragma once
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <stdio.h>
+
+#define PCAP_MAGIC_BIG_ENDIAN 0xA1B2C3D4 /*!< Big-Endian */
+#define PCAP_MAGIC_LITTLE_ENDIAN 0xD4C3B2A1 /*!< Little-Endian */
+
+#define PCAP_VERSION_MAJOR 0x02 /*!< Major Version */
+#define PCAP_VERSION_MINOR 0x04 /*!< Minor Version */
+
+#define PCAP_TIME_ZONE_GMT 0x00 /*!< Time Zone */
+
+#define PCAP_FILE_NAME_MAX_LEN 32 /*!< Max Name Length of Pcap File */
+
+/**
+* @brief Link layer Type Definition, used for Pcap reader to decode payload
+*
+*/
+typedef enum {
+ PCAP_LINK_TYPE_LOOPBACK = 0, /*!< Loopback devices, except for later OpenBSD */
+ PCAP_LINK_TYPE_ETHERNET = 1, /*!< Ethernet, and Linux loopback devices */
+ PCAP_LINK_TYPE_TOKEN_RING = 6, /*!< 802.5 Token Ring */
+ PCAP_LINK_TYPE_ARCNET = 7, /*!< ARCnet */
+ PCAP_LINK_TYPE_SLIP = 8, /*!< SLIP */
+ PCAP_LINK_TYPE_PPP = 9, /*!< PPP */
+ PCAP_LINK_TYPE_FDDI = 10, /*!< FDDI */
+ PCAP_LINK_TYPE_ATM = 100, /*!< LLC/SNAP encapsulated ATM */
+ PCAP_LINK_TYPE_RAW_IP = 101, /*!< Raw IP, without link */
+ PCAP_LINK_TYPE_BSD_SLIP = 102, /*!< BSD/OS SLIP */
+ PCAP_LINK_TYPE_BSD_PPP = 103, /*!< BSD/OS PPP */
+ PCAP_LINK_TYPE_CISCO_HDLC = 104, /*!< Cisco HDLC */
+ PCAP_LINK_TYPE_802_11 = 105, /*!< 802.11 */
+ PCAP_LINK_TYPE_BSD_LOOPBACK = 108, /*!< OpenBSD loopback devices(with AF_value in network byte order) */
+ PCAP_LINK_TYPE_LOCAL_TALK = 114 /*!< LocalTalk */
+} pcap_link_type_t;
+
+/**
+* @brief Pcap configuration Type Definition
+*
+*/
+typedef struct {
+ FILE *fp; /* Pointer to a standard file handle */
+ pcap_link_type_t link_type; /* Pcap Link Type */
+} pcap_config_t;
+
+/**
+ * @brief Create a pcap object
+ *
+ * @param config configuration of creating pcap object
+ * @return esp_err_t ESP_OK on success, ESP_FAIL on IO error
+ */
+esp_err_t pcap_new(pcap_config_t *config);
+
+/**
+ * @brief Close pcap file and recyle related resources
+ *
+ * @return esp_err_t ESP_OK on success, ESP_FAIL on error
+ */
+esp_err_t pcap_close(void);
+
+/**
+ * @brief Capture one packet into file in pcap format
+ *
+ * @param payload pointer to the captured data
+ * @param length length of captured data
+ * @param seconds second of capture time
+ * @param microseconds microsecond of capture time
+ * @return esp_err_t ESP_OK on success, ESP_FAIL on IO error
+ */
+esp_err_t pcap_capture_packet(void *payload, uint32_t length, uint32_t seconds, uint32_t microseconds);
+
+#ifdef __cplusplus
+}
+#endif
--- /dev/null
+
+# The following lines of boilerplate have to be in your project's CMakeLists
+# in this exact order for cmake to work correctly
+cmake_minimum_required(VERSION 3.5)
+
+set(COMPONENT_SRCS "simple_sniffer_example_main.c"
+ "cmd_sniffer.c")
+set(COMPONENT_ADD_INCLUDEDIRS ".")
+
+register_component()
--- /dev/null
+menu "Example Configuration"
+
+config STORE_HISTORY
+ bool "Store command history in flash"
+ default y
+ help
+ Linenoise line editing library provides functions to save and load
+ command history. If this option is enabled, initalizes a FAT filesystem
+ and uses it to store command history.
+
+config SNIFFER_MOUNT_POINT
+ string "Mount Point in your filesystem to store pcap files"
+ default "/sdcard"
+ help
+ Here you need to specify the mount point in the VFS (Virtual File System) where the pcap would be saved.
+
+config SNIFFER_WORK_QUEUE_LENGTH
+ int "Length of sniffer work queue"
+ default 128
+ help
+ The sniffer callback function should not do heavy work, so we put all heavy IO operation to another task.
+ The task gets some basic info of sniffer packet via queue.
+ Here you should specify the length of queue.
+
+config SNIFFER_TASK_STACK_SIZE
+ int "Stack size of sniffer task"
+ default 2560
+ help
+ The stack size of sniffer task.
+
+config SNIFFER_TASK_PRIORITY
+ int "Priority of sniffer task"
+ default 2
+ help
+ Priority of sniffer task.
+
+config PCAP_FILE_MAX_PACKETS
+ int "Max packets in a pcap file"
+ default 2000
+ help
+ To avoid the pcap file being very large, we should save packets into multiple fiiles.
+ Here you should specify the max number of packets that should be save in one pcap file.
+
+endmenu
--- /dev/null
+/* Iperf example — declarations of command registration functions.
+
+ This example code is in the Public Domain (or CC0 licensed, at your option.)
+
+ Unless required by applicable law or agreed to in writing, this
+ software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
+ CONDITIONS OF ANY KIND, either express or implied.
+*/
+#pragma once
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include "cmd_system.h"
+#include "cmd_sniffer.h"
+
+#ifdef __cplusplus
+}
+#endif
--- /dev/null
+/* cmd_sniffer example.
+ This example code is in the Public Domain (or CC0 licensed, at your option.)
+
+ Unless required by applicable law or agreed to in writing, this
+ software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
+ CONDITIONS OF ANY KIND, either express or implied.
+*/
+#include <string.h>
+#include "argtable3/argtable3.h"
+#include "freertos/FreeRTOS.h"
+#include "freertos/task.h"
+#include "freertos/queue.h"
+#include "freertos/semphr.h"
+#include <sys/unistd.h>
+#include <sys/fcntl.h>
+#include "esp_log.h"
+#include "esp_wifi.h"
+#include "esp_console.h"
+#include "cmd_sniffer.h"
+#include "pcap.h"
+#include "sdkconfig.h"
+
+#define SNIFFER_DEFAULT_FILE_NAME "sniffer"
+#define SNIFFER_DEFAULT_CHANNEL 1
+
+static const char *TAG = "cmd_sniffer";
+
+static bool sniffer_running = false;
+static pcap_config_t pcap_config;
+static QueueHandle_t sniffer_work_queue = NULL;
+static SemaphoreHandle_t sem_task_over = NULL;
+
+static wlan_filter_table_t wifi_filter_hash_table[SNIFFER_WLAN_FILTER_MAX] = {0};
+static char packet_filepath[PCAP_FILE_NAME_MAX_LEN];
+
+typedef struct {
+ void *payload;
+ uint32_t length;
+ uint32_t seconds;
+ uint32_t microseconds;
+} sniffer_packet_into_t;
+
+static esp_err_t create_packet_file(void)
+{
+ uint32_t file_no = 0;
+ char filename[PCAP_FILE_NAME_MAX_LEN];
+ do {
+ snprintf(filename, PCAP_FILE_NAME_MAX_LEN, "%s%d.pcap", packet_filepath, file_no);
+ file_no++;
+ } while (0 == access(filename, F_OK));
+ /* Create file to write, binary format */
+ pcap_config.fp = fopen(filename, "wb");
+ if (!pcap_config.fp) {
+ ESP_LOGE(TAG, "Create file %s failed", filename);
+ return ESP_FAIL;
+ }
+ ESP_LOGI(TAG, "Store packets to file: %s", filename);
+
+ return ESP_OK;
+}
+
+static uint32_t hash_func(const char *str, uint32_t max_num)
+{
+ uint32_t ret = 0;
+ char *p = (char *)str;
+ while (*p) {
+ ret += *p;
+ p++;
+ }
+ return ret % max_num;
+}
+
+static void create_wifi_filter_hashtable()
+{
+ char *wifi_filter_keys[SNIFFER_WLAN_FILTER_MAX] = {"mgmt", "data", "ctrl", "misc", "mpdu", "ampdu"};
+ uint32_t wifi_filter_values[SNIFFER_WLAN_FILTER_MAX] = {WIFI_PROMIS_FILTER_MASK_MGMT, WIFI_PROMIS_FILTER_MASK_DATA,
+ WIFI_PROMIS_FILTER_MASK_CTRL, WIFI_PROMIS_FILTER_MASK_MISC,
+ WIFI_PROMIS_FILTER_MASK_DATA_MPDU, WIFI_PROMIS_FILTER_MASK_DATA_AMPDU
+ };
+ for (int i = 0; i < SNIFFER_WLAN_FILTER_MAX; i++) {
+ uint32_t idx = hash_func(wifi_filter_keys[i], SNIFFER_WLAN_FILTER_MAX);
+ while (wifi_filter_hash_table[idx].filter_name) {
+ idx++;
+ if (idx >= SNIFFER_WLAN_FILTER_MAX) {
+ idx = 0;
+ }
+ }
+ wifi_filter_hash_table[idx].filter_name = wifi_filter_keys[i];
+ wifi_filter_hash_table[idx].filter_val = wifi_filter_values[i];
+ }
+}
+
+static uint32_t search_wifi_filter_hashtable(const char *key)
+{
+ uint32_t len = strlen(key);
+ uint32_t start_idx = hash_func(key, SNIFFER_WLAN_FILTER_MAX);
+ uint32_t idx = start_idx;
+ while (strncmp(wifi_filter_hash_table[idx].filter_name, key, len)) {
+ idx++;
+ if (idx >= SNIFFER_WLAN_FILTER_MAX) {
+ idx = 0;
+ }
+ /* wrong key */
+ if (idx == start_idx) {
+ return 0;
+ }
+ }
+ return wifi_filter_hash_table[idx].filter_val;
+}
+
+static void wifi_sniffer_cb(void *recv_buf, wifi_promiscuous_pkt_type_t type)
+{
+ if (sniffer_running) {
+ sniffer_packet_into_t packet_info;
+ wifi_promiscuous_pkt_t *sniffer = (wifi_promiscuous_pkt_t *)recv_buf;
+ /* prepare packet_info */
+ packet_info.seconds = sniffer->rx_ctrl.timestamp / 1000000U;
+ packet_info.microseconds = sniffer->rx_ctrl.timestamp % 1000000U;
+ packet_info.length = sniffer->rx_ctrl.sig_len;
+ wifi_promiscuous_pkt_t *backup = malloc(sniffer->rx_ctrl.sig_len);
+ if (backup) {
+ memcpy(backup, sniffer->payload, sniffer->rx_ctrl.sig_len);
+ packet_info.payload = backup;
+ if (sniffer_work_queue) {
+ /* send packet_info */
+ if (xQueueSend(sniffer_work_queue, &packet_info, 100 / portTICK_PERIOD_MS) != pdTRUE) {
+ ESP_LOGE(TAG, "sniffer work queue full");
+ }
+ }
+ } else {
+ ESP_LOGE(TAG, "No enough memory for promiscuous packet");
+ }
+ }
+}
+
+static void sniffer_task(void *parameters)
+{
+ static uint32_t count = 0;
+ sniffer_packet_into_t packet_info;
+ BaseType_t ret = 0;
+
+ while (sniffer_running) {
+ /* receive paclet info from queue */
+ ret = xQueueReceive(sniffer_work_queue, &packet_info, 100 / portTICK_PERIOD_MS);
+ if (ret != pdTRUE) {
+ continue;
+ }
+ if (pcap_capture_packet(packet_info.payload, packet_info.length,
+ packet_info.seconds, packet_info.microseconds) == ESP_OK) {
+ count++;
+ /* truncate, create another file */
+ if (count >= CONFIG_PCAP_FILE_MAX_PACKETS) {
+ pcap_close();
+ if (create_packet_file() != ESP_OK || pcap_new(&pcap_config) != ESP_OK) {
+ sniffer_running = false;
+ } else {
+ count = 0;
+ }
+ }
+ }
+ free(packet_info.payload);
+ }
+ /* notify that sniffer task is over */
+ xSemaphoreGive(sem_task_over);
+ vTaskDelete(NULL);
+}
+
+static esp_err_t snifer_stop(sniffer_config_t *sniffer)
+{
+ /* Do interface specific work here */
+ switch (sniffer->interf) {
+ case SNIFFER_INTF_WLAN:
+ /* Disable wifi promiscuous mode */
+ esp_wifi_set_promiscuous(false);
+ break;
+ default:
+ break;
+ }
+ /* stop sniffer local task */
+ sniffer_running = false;
+ /* wait for task over */
+ xSemaphoreTake(sem_task_over, portMAX_DELAY);
+ vSemaphoreDelete(sem_task_over);
+ sem_task_over = NULL;
+ /* make sure to free all resources in the left items */
+ UBaseType_t left_items = uxQueueMessagesWaiting(sniffer_work_queue);
+ sniffer_packet_into_t packet_info;
+ while (left_items--) {
+ xQueueReceive(sniffer_work_queue, &packet_info, 100 / portTICK_PERIOD_MS);
+ free(packet_info.payload);
+ }
+ /* delete queue */
+ vQueueDelete(sniffer_work_queue);
+ sniffer_work_queue = NULL;
+ /* Close the pcap file */
+ pcap_close();
+
+ ESP_LOGI(TAG, "Sniffer Stopped");
+ return ESP_OK;
+}
+
+static esp_err_t sniffer_start(sniffer_config_t *sniffer)
+{
+ wifi_promiscuous_filter_t wifi_filter;
+ /* set sniffer running status before it starts to run */
+ sniffer_running = true;
+ sniffer_work_queue = xQueueCreate(CONFIG_SNIFFER_WORK_QUEUE_LENGTH, sizeof(sniffer_packet_into_t));
+ sem_task_over = xSemaphoreCreateBinary();
+ /* sniffer task going to run*/
+ xTaskCreate(sniffer_task, "sniffer", CONFIG_SNIFFER_TASK_STACK_SIZE, NULL, CONFIG_SNIFFER_TASK_PRIORITY, NULL);
+
+ switch (sniffer->interf) {
+ case SNIFFER_INTF_WLAN:
+ /* Set Promicuous Mode */
+ wifi_filter.filter_mask = sniffer->filter;
+ esp_wifi_set_promiscuous_filter(&wifi_filter);
+ esp_wifi_set_promiscuous_rx_cb(wifi_sniffer_cb);
+ ESP_LOGI(TAG, "Start WiFi Promicuous Mode");
+ ESP_ERROR_CHECK(esp_wifi_set_promiscuous(true));
+ /* Specify the channel */
+ esp_wifi_set_channel(sniffer->channel, WIFI_SECOND_CHAN_NONE);
+ /* Create a new pcap object */
+ pcap_config.link_type = PCAP_LINK_TYPE_802_11;
+ pcap_new(&pcap_config);
+ break;
+ default:
+ break;
+ }
+ return ESP_OK;
+}
+
+static struct {
+ struct arg_str *file;
+ struct arg_str *interface;
+ struct arg_str *filter;
+ struct arg_int *channel;
+ struct arg_lit *stop;
+ struct arg_end *end;
+} sniffer_args;
+
+static int do_sniffer_cmd(int argc, char **argv)
+{
+ sniffer_config_t sniffer;
+
+ int nerrors = arg_parse(argc, argv, (void **)&sniffer_args);
+ if (nerrors != 0) {
+ arg_print_errors(stderr, sniffer_args.end, argv[0]);
+ return 0;
+ }
+
+ memset(&sniffer, 0, sizeof(sniffer));
+
+ /* Check interface: "-i" option */
+ if (sniffer_args.interface->count) {
+ if (!strncmp(sniffer_args.interface->sval[0], "wlan", 4)) {
+ sniffer.interf = SNIFFER_INTF_WLAN;
+ } else {
+ ESP_LOGE(TAG, "Do not support interface %s", sniffer_args.interface->sval[0]);
+ return 1;
+ }
+ } else {
+ sniffer.interf = SNIFFER_INTF_WLAN;
+ }
+ /* Check whether or not to stop sniffer: "--stop" option */
+ if (sniffer_args.stop->count) {
+ /* stop sniffer */
+ snifer_stop(&sniffer);
+ return 0;
+ }
+ /* Check channel: "-c" option */
+ sniffer.channel = 0;
+ if (sniffer_args.channel->count) {
+ sniffer.channel = sniffer_args.channel->ival[0];
+ } else {
+ sniffer.channel = SNIFFER_DEFAULT_CHANNEL;
+ }
+
+ /* set pcap file name: "-f" option */
+ if (sniffer_args.file->count) {
+ snprintf(packet_filepath, PCAP_FILE_NAME_MAX_LEN, "%s/%s",
+ CONFIG_SNIFFER_MOUNT_POINT, sniffer_args.file->sval[0]);
+ } else {
+ snprintf(packet_filepath, PCAP_FILE_NAME_MAX_LEN, "%s/%s",
+ CONFIG_SNIFFER_MOUNT_POINT, SNIFFER_DEFAULT_FILE_NAME);
+ }
+ /* Determin file name */
+ if (create_packet_file() != ESP_OK) {
+ return 1;
+ }
+
+ /* Check filter setting: "-F" option */
+ switch (sniffer.interf) {
+ case SNIFFER_INTF_WLAN:
+ if (sniffer_args.filter->count) {
+ for (int i = 0; i < sniffer_args.filter->count; i++) {
+ sniffer.filter += search_wifi_filter_hashtable(sniffer_args.filter->sval[i]);
+ }
+ /* When filter conditions are all wrong */
+ if (sniffer.filter == 0) {
+ sniffer.filter = WIFI_PROMIS_FILTER_MASK_ALL;
+ }
+ } else {
+ sniffer.filter = WIFI_PROMIS_FILTER_MASK_ALL;
+ }
+ break;
+ default:
+ break;
+ }
+
+ /* start sniffer */
+ sniffer_start(&sniffer);
+
+ return 0;
+}
+
+void register_sniffer()
+{
+ sniffer_args.file = arg_str0("f", "file", "<file>",
+ "name of the file storing the packets in pcap format");
+ sniffer_args.interface = arg_str0("i", "interface", "<wlan>",
+ "which interface to capture packet");
+ sniffer_args.filter = arg_strn("F", "filter", "<mgmt|data|ctrl|misc|mpdu|ampdu>", 0, 6, "filter parameters");
+ sniffer_args.channel = arg_int0("c", "channel", "<channel>", "communication channel to use");
+ sniffer_args.stop = arg_lit0(NULL, "stop", "stop running sniffer");
+ sniffer_args.end = arg_end(1);
+ const esp_console_cmd_t iperf_cmd = {
+ .command = "sniffer",
+ .help = "Capture specific packet and store in pcap format",
+ .hint = NULL,
+ .func = &do_sniffer_cmd,
+ .argtable = &sniffer_args
+ };
+ ESP_ERROR_CHECK(esp_console_cmd_register(&iperf_cmd));
+
+ create_wifi_filter_hashtable();
+}
--- /dev/null
+/* cmd_sniffer example — declarations of command registration functions.
+
+ This example code is in the Public Domain (or CC0 licensed, at your option.)
+
+ Unless required by applicable law or agreed to in writing, this
+ software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
+ CONDITIONS OF ANY KIND, either express or implied.
+*/
+#pragma once
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef enum {
+ SNIFFER_INTF_WLAN = 0,
+} sniffer_intf_t;
+
+typedef enum {
+ SNIFFER_WLAN_FILTER_MGMT = 0,
+ SNIFFER_WLAN_FILTER_CTRL,
+ SNIFFER_WLAN_FILTER_DATA,
+ SNIFFER_WLAN_FILTER_MISC,
+ SNIFFER_WLAN_FILTER_MPDU,
+ SNIFFER_WLAN_FILTER_AMPDU,
+ SNIFFER_WLAN_FILTER_MAX
+} sniffer_wlan_filter_t;
+
+typedef struct {
+ char *filter_name;
+ uint32_t filter_val;
+} wlan_filter_table_t;
+
+typedef struct {
+ sniffer_intf_t interf;
+ uint32_t channel;
+ uint32_t duration;
+ uint32_t filter;
+} sniffer_config_t;
+
+void register_sniffer();
+
+#ifdef __cplusplus
+}
+#endif
--- /dev/null
+#
+# "main" pseudo-component makefile.
+#
+# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.)
--- /dev/null
+/* Sniffer example.
+ This example code is in the Public Domain (or CC0 licensed, at your option.)
+
+ Unless required by applicable law or agreed to in writing, this
+ software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
+ CONDITIONS OF ANY KIND, either express or implied.
+*/
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include "linenoise/linenoise.h"
+#include "argtable3/argtable3.h"
+#include "tcpip_adapter.h"
+#include "esp_console.h"
+#include "esp_event_loop.h"
+#include "esp_vfs_dev.h"
+#include "esp_vfs_fat.h"
+#include "esp_wifi.h"
+#include "esp_err.h"
+#include "esp_log.h"
+#include "driver/uart.h"
+#include "driver/sdmmc_host.h"
+#include "driver/sdspi_host.h"
+#include "nvs_flash.h"
+#include "sdmmc_cmd.h"
+#include "cmd_decl.h"
+#include "sdkconfig.h"
+
+#if CONFIG_STORE_HISTORY
+#define HISTORY_MOUNT_POINT "/data"
+#define HISTORY_FILE_PATH HISTORY_MOUNT_POINT "/history.txt"
+#endif
+
+static const char *TAG = "example";
+
+#if CONFIG_STORE_HISTORY
+/* Initialize filesystem for command history store */
+static void initialize_filesystem()
+{
+ static wl_handle_t wl_handle;
+ const esp_vfs_fat_mount_config_t mount_config = {
+ .max_files = 4,
+ .format_if_mount_failed = true
+ };
+ esp_err_t err = esp_vfs_fat_spiflash_mount(HISTORY_MOUNT_POINT, "storage", &mount_config, &wl_handle);
+ if (err != ESP_OK) {
+ ESP_LOGE(TAG, "Failed to mount FATFS (%s)", esp_err_to_name(err));
+ return;
+ }
+}
+#endif
+
+static void initialize_nvs()
+{
+ esp_err_t err = nvs_flash_init();
+ if (err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW_VERSION_FOUND) {
+ ESP_ERROR_CHECK(nvs_flash_erase());
+ err = nvs_flash_init();
+ }
+ ESP_ERROR_CHECK(err);
+}
+
+/* Initialize wifi with tcp/ip adapter */
+static void initialize_wifi()
+{
+ tcpip_adapter_init();
+ ESP_ERROR_CHECK(esp_event_loop_init(NULL, NULL));
+ wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
+ ESP_ERROR_CHECK(esp_wifi_init(&cfg));
+ ESP_ERROR_CHECK(esp_wifi_set_storage(WIFI_STORAGE_RAM));
+ ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_NULL));
+}
+
+/* Initialize console component */
+static void initialize_console()
+{
+ /* Disable buffering on stdin and stdout */
+ setvbuf(stdin, NULL, _IONBF, 0);
+ setvbuf(stdout, NULL, _IONBF, 0);
+
+ /* Minicom, screen, idf_monitor send CR when ENTER key is pressed */
+ esp_vfs_dev_uart_set_rx_line_endings(ESP_LINE_ENDINGS_CR);
+ /* Move the caret to the beginning of the next line on '\n' */
+ esp_vfs_dev_uart_set_tx_line_endings(ESP_LINE_ENDINGS_CRLF);
+
+ /* Install UART driver for interrupt-driven reads and writes */
+ ESP_ERROR_CHECK(uart_driver_install(CONFIG_CONSOLE_UART_NUM,
+ 256, 0, 0, NULL, 0));
+
+ /* Tell VFS to use UART driver */
+ esp_vfs_dev_uart_use_driver(CONFIG_CONSOLE_UART_NUM);
+
+ /* Initialize the console */
+ esp_console_config_t console_config = {
+ .max_cmdline_args = 8,
+ .max_cmdline_length = 256,
+#if CONFIG_LOG_COLORS
+ .hint_color = atoi(LOG_COLOR_CYAN)
+#endif
+ };
+ ESP_ERROR_CHECK(esp_console_init(&console_config));
+
+ /* Configure linenoise line completion library */
+ /* Enable multiline editing. If not set, long commands will scroll within
+ * single line.
+ */
+ linenoiseSetMultiLine(1);
+
+ /* Tell linenoise where to get command completions and hints */
+ linenoiseSetCompletionCallback(&esp_console_get_completion);
+ linenoiseSetHintsCallback((linenoiseHintsCallback *)&esp_console_get_hint);
+
+ /* Set command history size */
+ linenoiseHistorySetMaxLen(100);
+
+#if CONFIG_STORE_HISTORY
+ /* Load command history from filesystem */
+ linenoiseHistoryLoad(HISTORY_FILE_PATH);
+#endif
+}
+
+static struct {
+ struct arg_str *device;
+ struct arg_end *end;
+} mount_args;
+
+/** 'mount' command */
+static int mount(int argc, char **argv)
+{
+ int nerrors = arg_parse(argc, argv, (void **)&mount_args);
+ if (nerrors != 0) {
+ arg_print_errors(stderr, mount_args.end, argv[0]);
+ return 1;
+ }
+ /* mount sd card */
+ if (!strncmp(mount_args.device->sval[0], "sd", 2)) {
+ ESP_LOGI(TAG, "Initializing SD card");
+ ESP_LOGI(TAG, "Using SDMMC peripheral");
+ sdmmc_host_t host = SDMMC_HOST_DEFAULT();
+ sdmmc_slot_config_t slot_config = SDMMC_SLOT_CONFIG_DEFAULT();
+
+ gpio_set_pull_mode(15, GPIO_PULLUP_ONLY); // CMD, needed in 4- and 1-line modes
+ gpio_set_pull_mode(2, GPIO_PULLUP_ONLY); // D0, needed in 4- and 1-line modes
+ gpio_set_pull_mode(4, GPIO_PULLUP_ONLY); // D1, needed in 4-line mode only
+ gpio_set_pull_mode(12, GPIO_PULLUP_ONLY); // D2, needed in 4-line mode only
+ gpio_set_pull_mode(13, GPIO_PULLUP_ONLY); // D3, needed in 4- and 1-line modes
+
+ esp_vfs_fat_sdmmc_mount_config_t mount_config = {
+ .format_if_mount_failed = true,
+ .max_files = 4,
+ .allocation_unit_size = 16 * 1024
+ };
+
+ // initialize SD card and mount FAT filesystem.
+ sdmmc_card_t *card;
+ esp_err_t ret = esp_vfs_fat_sdmmc_mount(CONFIG_SNIFFER_MOUNT_POINT, &host, &slot_config, &mount_config, &card);
+
+ if (ret != ESP_OK) {
+ if (ret == ESP_FAIL) {
+ ESP_LOGE(TAG, "Failed to mount filesystem. "
+ "If you want the card to be formatted, set format_if_mount_failed = true.");
+ } else {
+ ESP_LOGE(TAG, "Failed to initialize the card (%s). "
+ "Make sure SD card lines have pull-up resistors in place.",
+ esp_err_to_name(ret));
+ }
+ return 1;
+ }
+ /* print card info if mount successfully */
+ sdmmc_card_print_info(stdout, card);
+ }
+ return 0;
+}
+
+static void register_mount()
+{
+ mount_args.device = arg_str1(NULL, NULL, "<sd>", "choose a proper device to mount/unmount");
+ mount_args.end = arg_end(1);
+ const esp_console_cmd_t cmd = {
+ .command = "mount",
+ .help = "mount the filesystem",
+ .hint = NULL,
+ .func = &mount,
+ .argtable = &mount_args
+ };
+ ESP_ERROR_CHECK(esp_console_cmd_register(&cmd));
+}
+
+static int unmount(int argc, char **argv)
+{
+ int nerrors = arg_parse(argc, argv, (void **)&mount_args);
+ if (nerrors != 0) {
+ arg_print_errors(stderr, mount_args.end, argv[0]);
+ return 1;
+ }
+ /* mount sd card */
+ if (!strncmp(mount_args.device->sval[0], "sd", 2)) {
+ if (esp_vfs_fat_sdmmc_unmount() != ESP_OK) {
+ ESP_LOGE(TAG, "Card unmount failed");
+ return -1;
+ }
+ ESP_LOGI(TAG, "Card unmounted");
+ }
+ return 0;
+}
+
+static void register_unmount()
+{
+ mount_args.device = arg_str1(NULL, NULL, "<sd>", "choose a proper device to mount/unmount");
+ mount_args.end = arg_end(1);
+ const esp_console_cmd_t cmd = {
+ .command = "unmount",
+ .help = "unmount the filesystem",
+ .hint = NULL,
+ .func = &unmount,
+ .argtable = &mount_args
+ };
+ ESP_ERROR_CHECK(esp_console_cmd_register(&cmd));
+}
+
+void app_main(void)
+{
+ initialize_nvs();
+
+#if CONFIG_STORE_HISTORY
+ initialize_filesystem();
+#endif
+
+ /* Initialize WiFi */
+ initialize_wifi();
+ /* Initialize Console component */
+ initialize_console();
+
+ /* Register commands */
+ esp_console_register_help_command();
+ register_mount();
+ register_unmount();
+ register_sniffer();
+ register_system();
+
+ /* Prompt to be printed before each line.
+ * This can be customized, made dynamic, etc.
+ */
+ const char *prompt = LOG_COLOR_I "esp32> " LOG_RESET_COLOR;
+
+ printf("\n =======================================================\n");
+ printf(" | Steps to sniffer WiFi packets |\n");
+ printf(" | |\n");
+ printf(" | 1. Enter 'help' to check all commands' usage |\n");
+ printf(" | 2. Enter 'mount <device>' to mount filesystem |\n");
+ printf(" | 3. Enter 'sniffer' to start capture packets |\n");
+ printf(" | 4. Enter 'unmount <device>' to unmount filesystem |\n");
+ printf(" | |\n");
+ printf(" =======================================================\n\n");
+
+ /* Figure out if the terminal supports escape sequences */
+ int probe_status = linenoiseProbe();
+ if (probe_status) {
+ /* zero indicates success */
+ printf("\n"
+ "Your terminal application does not support escape sequences.\n"
+ "Line editing and history features are disabled.\n"
+ "On Windows, try using Putty instead.\n");
+ linenoiseSetDumbMode(1);
+#if CONFIG_LOG_COLORS
+ /* Since the terminal doesn't support escape sequences,
+ * don't use color codes in the prompt.
+ */
+ prompt = "esp32> ";
+#endif //CONFIG_LOG_COLORS
+ }
+
+ /* Main loop */
+ while (true) {
+ /* Get a line using linenoise.
+ * The line is returned when ENTER is pressed.
+ */
+ char *line = linenoise(prompt);
+ if (line == NULL) {
+ /* Ignore empty lines */
+ continue;
+ }
+ /* Add the command to the history */
+ linenoiseHistoryAdd(line);
+
+#if CONFIG_STORE_HISTORY
+ /* Save command history to filesystem */
+ linenoiseHistorySave(HISTORY_FILE_PATH);
+#endif
+ /* Try to run the command */
+ int ret;
+ esp_err_t err = esp_console_run(line, &ret);
+ if (err == ESP_ERR_NOT_FOUND) {
+ printf("Unrecognized command\n");
+ } else if (err == ESP_OK && ret != ESP_OK) {
+ printf("Command returned non-zero error code: 0x%x\n", ret);
+ } else if (err != ESP_OK) {
+ printf("Internal error: %s\n", esp_err_to_name(err));
+ }
+ /* linenoise allocates line buffer on the heap, so need to free it */
+ linenoiseFree(line);
+ }
+}
--- /dev/null
+# Name, Type, SubType, Offset, Size, Flags
+# Note: if you change the phy_init or app partition offset, make sure to change the offset in Kconfig.projbuild
+nvs, data, nvs, 0x9000, 0x6000,
+phy_init, data, phy, 0xf000, 0x1000,
+factory, app, factory, 0x10000, 1M,
+storage, data, fat, , 1M,
--- /dev/null
+# Reduce bootloader log verbosity
+CONFIG_LOG_BOOTLOADER_LEVEL_WARN=y
+CONFIG_LOG_BOOTLOADER_LEVEL=2
+
+# Increase main task stack size
+CONFIG_MAIN_TASK_STACK_SIZE=7168
+
+# Enable filesystem
+CONFIG_PARTITION_TABLE_CUSTOM=y
+CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions_example.csv"
+CONFIG_PARTITION_TABLE_FILENAME="partitions_example.csv"
+
+# Enable FreeRTOS stats formatting functions, needed for 'tasks' command
+CONFIG_FREERTOS_USE_TRACE_FACILITY=y
+CONFIG_FREERTOS_USE_STATS_FORMATTING_FUNCTIONS=y
+
+CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y
+
+# FatFS
+CONFIG_FATFS_LFN_HEAP=y
+CONFIG_FATFS_MAX_LFN=31
+