diff --git a/examples/wifi/simple_sniffer/CMakeLists.txt b/examples/wifi/simple_sniffer/CMakeLists.txt new file mode 100644 index 000000000..2ec8c4d9c --- /dev/null +++ b/examples/wifi/simple_sniffer/CMakeLists.txt @@ -0,0 +1,8 @@ +# 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) diff --git a/examples/wifi/simple_sniffer/Makefile b/examples/wifi/simple_sniffer/Makefile new file mode 100644 index 000000000..13aa6f824 --- /dev/null +++ b/examples/wifi/simple_sniffer/Makefile @@ -0,0 +1,11 @@ +# +# 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 + diff --git a/examples/wifi/simple_sniffer/README.md b/examples/wifi/simple_sniffer/README.md new file mode 100644 index 000000000..e1385bcfe --- /dev/null +++ b/examples/wifi/simple_sniffer/README.md @@ -0,0 +1,124 @@ +# 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 ][-i ] [-F ]... [-c ][--stop] +> Capture specific packet and store in pcap format +> -f, --file= name of the file storing the packets in pcap format +> -i, --interface= which interface to capture packet +> -F, --filter= filter parameters +> -c, --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 ' to mount filesystem | + | 3. Enter 'sniffer' to start capture packets | + | 4. Enter 'unmount ' 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 diff --git a/examples/wifi/simple_sniffer/components/pcap/CMakeLists.txt b/examples/wifi/simple_sniffer/components/pcap/CMakeLists.txt new file mode 100644 index 000000000..15f5b5da6 --- /dev/null +++ b/examples/wifi/simple_sniffer/components/pcap/CMakeLists.txt @@ -0,0 +1,5 @@ +set(COMPONENT_ADD_INCLUDEDIRS .) + +set(COMPONENT_SRCS "pcap.c") + +register_component() diff --git a/examples/wifi/simple_sniffer/components/pcap/component.mk b/examples/wifi/simple_sniffer/components/pcap/component.mk new file mode 100644 index 000000000..da115f7d4 --- /dev/null +++ b/examples/wifi/simple_sniffer/components/pcap/component.mk @@ -0,0 +1,11 @@ +# +# 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 := . diff --git a/examples/wifi/simple_sniffer/components/pcap/pcap.c b/examples/wifi/simple_sniffer/components/pcap/pcap.c new file mode 100644 index 000000000..4032f2dcb --- /dev/null +++ b/examples/wifi/simple_sniffer/components/pcap/pcap.c @@ -0,0 +1,118 @@ +/* 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 +#include +#include +#include +#include +#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; +} diff --git a/examples/wifi/simple_sniffer/components/pcap/pcap.h b/examples/wifi/simple_sniffer/components/pcap/pcap.h new file mode 100644 index 000000000..ef2dfc78a --- /dev/null +++ b/examples/wifi/simple_sniffer/components/pcap/pcap.h @@ -0,0 +1,85 @@ +/* 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 + +#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 diff --git a/examples/wifi/simple_sniffer/main/CMakeLists.txt b/examples/wifi/simple_sniffer/main/CMakeLists.txt new file mode 100644 index 000000000..8e95d42fc --- /dev/null +++ b/examples/wifi/simple_sniffer/main/CMakeLists.txt @@ -0,0 +1,10 @@ + +# 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() diff --git a/examples/wifi/simple_sniffer/main/Kconfig.projbuild b/examples/wifi/simple_sniffer/main/Kconfig.projbuild new file mode 100644 index 000000000..61d227638 --- /dev/null +++ b/examples/wifi/simple_sniffer/main/Kconfig.projbuild @@ -0,0 +1,44 @@ +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 diff --git a/examples/wifi/simple_sniffer/main/cmd_decl.h b/examples/wifi/simple_sniffer/main/cmd_decl.h new file mode 100644 index 000000000..908f6f0de --- /dev/null +++ b/examples/wifi/simple_sniffer/main/cmd_decl.h @@ -0,0 +1,20 @@ +/* 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 diff --git a/examples/wifi/simple_sniffer/main/cmd_sniffer.c b/examples/wifi/simple_sniffer/main/cmd_sniffer.c new file mode 100644 index 000000000..fb5aea630 --- /dev/null +++ b/examples/wifi/simple_sniffer/main/cmd_sniffer.c @@ -0,0 +1,336 @@ +/* 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 +#include "argtable3/argtable3.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/queue.h" +#include "freertos/semphr.h" +#include +#include +#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", "", + "name of the file storing the packets in pcap format"); + sniffer_args.interface = arg_str0("i", "interface", "", + "which interface to capture packet"); + sniffer_args.filter = arg_strn("F", "filter", "", 0, 6, "filter parameters"); + sniffer_args.channel = arg_int0("c", "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(); +} diff --git a/examples/wifi/simple_sniffer/main/cmd_sniffer.h b/examples/wifi/simple_sniffer/main/cmd_sniffer.h new file mode 100644 index 000000000..bf046380b --- /dev/null +++ b/examples/wifi/simple_sniffer/main/cmd_sniffer.h @@ -0,0 +1,45 @@ +/* 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 diff --git a/examples/wifi/simple_sniffer/main/component.mk b/examples/wifi/simple_sniffer/main/component.mk new file mode 100644 index 000000000..a98f634ea --- /dev/null +++ b/examples/wifi/simple_sniffer/main/component.mk @@ -0,0 +1,4 @@ +# +# "main" pseudo-component makefile. +# +# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.) diff --git a/examples/wifi/simple_sniffer/main/simple_sniffer_example_main.c b/examples/wifi/simple_sniffer/main/simple_sniffer_example_main.c new file mode 100644 index 000000000..404859056 --- /dev/null +++ b/examples/wifi/simple_sniffer/main/simple_sniffer_example_main.c @@ -0,0 +1,304 @@ +/* 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 +#include +#include +#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, "", "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, "", "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 ' to mount filesystem |\n"); + printf(" | 3. Enter 'sniffer' to start capture packets |\n"); + printf(" | 4. Enter 'unmount ' 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); + } +} diff --git a/examples/wifi/simple_sniffer/partitions_example.csv b/examples/wifi/simple_sniffer/partitions_example.csv new file mode 100644 index 000000000..7e28b5668 --- /dev/null +++ b/examples/wifi/simple_sniffer/partitions_example.csv @@ -0,0 +1,6 @@ +# 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, diff --git a/examples/wifi/simple_sniffer/sdkconfig.defaults b/examples/wifi/simple_sniffer/sdkconfig.defaults new file mode 100644 index 000000000..23fd7b851 --- /dev/null +++ b/examples/wifi/simple_sniffer/sdkconfig.defaults @@ -0,0 +1,22 @@ +# 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 + diff --git a/examples/wifi/simple_sniffer/sniffer-example0-pcap.png b/examples/wifi/simple_sniffer/sniffer-example0-pcap.png new file mode 100644 index 000000000..acff3c53a Binary files /dev/null and b/examples/wifi/simple_sniffer/sniffer-example0-pcap.png differ