example: add console command for wifi sniffer

1. add mount/unmount command
2. add sniffer command, supporting options
3. make pcap a component
This commit is contained in:
morris 2018-09-27 11:24:12 +08:00
parent c02e218da1
commit 900b69a33e
17 changed files with 1153 additions and 0 deletions

View file

@ -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)

View file

@ -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

View file

@ -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 <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 files 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.)

View file

@ -0,0 +1,5 @@
set(COMPONENT_ADD_INCLUDEDIRS .)
set(COMPONENT_SRCS "pcap.c")
register_component()

View file

@ -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 := .

View file

@ -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 <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;
}

View file

@ -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 <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

View file

@ -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()

View file

@ -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

View file

@ -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

View file

@ -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 <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();
}

View file

@ -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

View file

@ -0,0 +1,4 @@
#
# "main" pseudo-component makefile.
#
# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.)

View file

@ -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 <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);
}
}

View file

@ -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,
1 # Name, Type, SubType, Offset, Size, Flags
2 # Note: if you change the phy_init or app partition offset, make sure to change the offset in Kconfig.projbuild
3 nvs, data, nvs, 0x9000, 0x6000,
4 phy_init, data, phy, 0xf000, 0x1000,
5 factory, app, factory, 0x10000, 1M,
6 storage, data, fat, , 1M,

View file

@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 504 KiB