simple_snifferコードリーディング
特にWiFiでのスニファ部分を読みたい
simple_sniffer_example_main.c
esp_wifi.hでESP32のWiFiの設定ができる
esp_wifi_init
esp_wifi_set_storage
esp_wifi_set_mode
のようなAPIが提供されている
code: initialize_wifi
/* Initialize wifi with tcp/ip adapter */
static void initialize_wifi(void)
{
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));
}
esp_wifi_initを少しみにいくと、周波数やkeep alive time、色々なsleep時間の設定が書かれていた。
結構煩雑なので、ここまでを自分で実装するのはかなり骨が折れそう。
今回はこれらのAPIより上のレイヤでコードを見ていく方針でいく。
initialize_wifiでは通信の記録をメモリ上に記録し(WIFI_STORAGE_RAM)、
通信モードは無指定(WIFI_MODE_NULL)という具合でWiFi設定の初期化をしている。
ESP_ERROR_CHECKはespのAPIで使えるエラーハンドリングマクロだ。
code:initialize_nvs
static void initialize_nvs(void)
{
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_nvsではNVSパーティションを初期化している
NVSとは
Non-Volatile Storage
key value store
code:initialize_filesystem
static void initialize_filesystem(void)
{
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_rw_wl(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;
}
}
VFSとは
Virtual Filesystem
ファイルのようなものを扱える
app_mainの一部
code:repl
esp_console_repl_t *repl = NULL;
esp_console_repl_config_t repl_config = ESP_CONSOLE_REPL_CONFIG_DEFAULT();
#if CONFIG_SNIFFER_STORE_HISTORY initialize_filesystem();
repl_config.history_save_path = HISTORY_FILE_PATH;
repl_config.prompt = "sniffer>";
// install console REPL environment
#if CONFIG_ESP_CONSOLE_UART esp_console_dev_uart_config_t uart_config = ESP_CONSOLE_DEV_UART_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_console_new_repl_uart(&uart_config, &repl_config, &repl));
esp_console_new_repl_uartはREPL環境をUARTで確立する。
REPLとは
対話実行環境。Shellのようなもの。
UARTとは
シリアル通信方式の1つ。
register_sniffer_cmd();
register_pcap_cmd();
これらがコマンドの中身で、スニファの機能のコードなどはここに入ってる。
app_mainでは最後にesp_console_start_replでREPLを呼び出して終わっている。
今回はsnifferを主に見ていきたいので、register_sniffer_cmdの先を読む。
code:register_sniffer_cmd
void register_sniffer_cmd(void)
{
sniffer_args.number = arg_int0("n", "number", "<num>",
"the number of the packets to be captured");
sniffer_args.interface = arg_str0("i", "interface", "<wlan|eth0|eth1|...>",
"which interface to capture packet");
sniffer_args.filter = arg_strn("F", "filter", "<mgmt|data|ctrl|misc|mpdu|ampdu|fcsfail>", 0, 7, "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 sniffer_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(&sniffer_cmd));
create_wifi_filter_hashtable();
}
コマンドの登録までもesp_console_cmd_registerといったAPIでできてしまうので、かなり便利だ。
arg_int0, arg_str0, arg_strn, arg_lit0などでコマンド引数を作っている。
作った引数はargtableとして渡してあげれば良い。
funcにdo_sniffer_cmd関数が指定されているので、create_wifi_filter_hashtableの後でみよう。
code: create_wifi_filter_hashtable
static void create_wifi_filter_hashtable(void)
{
WIFI_PROMIS_FILTER_MASK_CTRL, WIFI_PROMIS_FILTER_MASK_MISC,
WIFI_PROMIS_FILTER_MASK_DATA_MPDU, WIFI_PROMIS_FILTER_MASK_DATA_AMPDU,
WIFI_PROMIS_FILTER_MASK_FCSFAIL
};
for (int i = 0; i < SNIFFER_WLAN_FILTER_MAX; i++) {
uint32_t idx = hash_func(wifi_filter_keysi, SNIFFER_WLAN_FILTER_MAX); while (wifi_filter_hash_tableidx.filter_name) { idx++;
if (idx >= SNIFFER_WLAN_FILTER_MAX) {
idx = 0;
}
}
wifi_filter_hash_tableidx.filter_name = wifi_filter_keysi; wifi_filter_hash_tableidx.filter_val = wifi_filter_valuesi; }
}
グローバルなwifi_filter_hash_tableに対し、操作を行う。
このハッシュテーブルはdo_sniffer_cmdで使用される。
mgmt, data, ctrl, misc, mpdu, ampdu, fcsfail これらは何を表しているのだろうか。
まあでも普通に考えたらキャプチャしたパケットをフィルタリングするための情報だから、
パケットのメタデータの何かだろう。
mgmt, data, ctrlはフレームの種別かな。
miscはよくわからない。
mpdf, ampduはフレームアグリゲーションの方式で、fcsfailはFrame Check Sequenceでフェイルしているかどうかみたいな感じか。
do_sniffer_cmdでは主にオプションの読み取りと、それに対応する設定をしている。
iオプション
code:i
/* Check interface: "-i" option */
if (sniffer_args.interface->count) {
if (!strncmp(sniffer_args.interface->sval0, "wlan", 4)) { snf_rt.interf = SNIFFER_INTF_WLAN;
} else if (!strncmp(sniffer_args.interface->sval0, "eth", 3) ...}
} else {
snf_rt.interf = SNIFFER_INTF_WLAN;
ESP_LOGW(SNIFFER_TAG, "sniffing interface set to wlan by default");
}
snf_rt.interfにSNIFFER_INTF_WLANを設定している。
デフォルトでもそうなる。ESP_LOGWでログを出せるのかな?LOGWだからWarning扱い?
cオプション
code:c
/* Check channel: "-c" option */
switch (snf_rt.interf) {
case SNIFFER_INTF_WLAN:
snf_rt.channel = SNIFFER_DEFAULT_CHANNEL;
if (sniffer_args.channel->count) {
snf_rt.channel = sniffer_args.channel->ival0; }
break;
...
}
チャンネルを指定できる。
2.4GHzで使えるチャネルは、IEEE802.11bの場合は1CHから14CHで、IEEE802.11gの場合は1CHから13CHです。
チャンネルは全部見たい気もするけど、それは無理か。どれか一つのチャンネルが見れれば問題ない?
fオプション
code:f
/* Check filter setting: "-F" option */
switch (snf_rt.interf) {
case SNIFFER_INTF_WLAN:
if (sniffer_args.filter->count) {
snf_rt.filter = 0;
for (int i = 0; i < sniffer_args.filter->count; i++) {
snf_rt.filter += search_wifi_filter_hashtable(sniffer_args.filter->svali); }
/* When filter conditions are all wrong */
if (snf_rt.filter == 0) {
snf_rt.filter = WIFI_PROMIS_FILTER_MASK_ALL;
}
} else {
snf_rt.filter = WIFI_PROMIS_FILTER_MASK_ALL;
}
break;
...
}
ここでsearch_wifi_filter_hashtableを使って、wifi_filter_hash_tableを見てあげる。
テーブルに何もなければWIFI_PROMIS_FILTER_MASK_ALLでフィルターなし状態にする。
nオプション
code:n
/* Check the number of captured packages: "-n" option */
snf_rt.packets_to_sniff = -1;
if (sniffer_args.number->count) {
snf_rt.packets_to_sniff = sniffer_args.number->ival0; ESP_LOGI(SNIFFER_TAG, "%" PRIi32 " packages will be captured", snf_rt.packets_to_sniff);
}
キャプチャするパケット数を指定できる。
snf_rt.packets_to_sniffに数値を渡しているだけ。
これらのコマンド引数処理が終わったらsniffer_startで実際にスニッフィングが走る。
snf_rtは以下の型を持つ。これらがどのように使われるかはsniffer_startを見ればわかるだろう。
code:sniffer_runtime_t
typedef struct {
bool is_running;
sniffer_intf_t interf;
uint32_t interf_num;
uint32_t channel;
uint32_t filter;
int32_t packets_to_sniff;
TaskHandle_t task;
QueueHandle_t work_queue;
SemaphoreHandle_t sem_task_over;
} sniffer_runtime_t;
sniffer_start
code:work queue
sniffer->work_queue = xQueueCreate(CONFIG_SNIFFER_WORK_QUEUE_LEN, sizeof(sniffer_packet_info_t));
sniffer_packet_info_tサイズのオブジェクトを格納するキューを作っていることから、
取得したパケットのデータを入れておくキューであると予想する。
code: semaphore handle
sniffer->sem_task_over = xSemaphoreCreateBinary();
ESP_GOTO_ON_FALSE(sniffer->sem_task_over, ESP_FAIL, err_sem, SNIFFER_TAG, "create work queue failed");
ESP_GOTO_ON_FALSE(xTaskCreate(sniffer_task, "snifferT", CONFIG_SNIFFER_TASK_STACK_SIZE,
sniffer, CONFIG_SNIFFER_TASK_PRIORITY, &sniffer->task), ESP_FAIL,
err_task, SNIFFER_TAG, "create task failed");
これは......何だろう。排他制御のためのフラグかな?
code:sniffer
switch (sniffer->interf) {
case SNIFFER_INTF_WLAN:
/* Start WiFi Promiscuous Mode */
wifi_filter.filter_mask = sniffer->filter;
esp_wifi_set_promiscuous_filter(&wifi_filter);
esp_wifi_set_promiscuous_rx_cb(wifi_sniffer_cb);
ESP_GOTO_ON_ERROR(esp_wifi_set_promiscuous(true), err_start, SNIFFER_TAG, "create work queue failed");
esp_wifi_set_channel(sniffer->channel, WIFI_SECOND_CHAN_NONE);
ESP_LOGI(SNIFFER_TAG, "start WiFi promiscuous ok");
break;
...
esp_wifi_set_promiscuous_filterでフィルターをセットしている。
esp_wifi_set_promiscuous_rx_cb RX callback functionをセットしている。これはパケットを受け取った際に動作する関数になる。
esp_wifi_set_promiscuousでプロミスキャストモードをオンにしている。
ということでパケットを受け取った時の動作、wifi_sniffer_cbを見てみる。
code:wifi_sniffer_cb
static void wifi_sniffer_cb(void *recv_buf, wifi_promiscuous_pkt_type_t type)
{
sniffer_packet_info_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;
/* For now, the sniffer only dumps the length of the MISC type frame */
if (type != WIFI_PKT_MISC && !sniffer->rx_ctrl.rx_state) {
packet_info.length -= SNIFFER_PAYLOAD_FCS_LEN;
queue_packet(sniffer->payload, &packet_info);
}
}
おそらくrecv_bufに受け取ったパケットのデータが入ってきて(デバッガー動かしながら確認したいな......)、
それをwifi_promiscuous_pkt_t * にキャストして扱えるようにしている。
rx_ctrlにはいろいろ入っていてrssiとかも取得できるっぽい。
MISCもフレームタイプの一種なのか。というよりかは「その他」みたいな扱いっぽい。
正常な(MISCじゃない)パケットだけキャプチャ(記録)している。
queue_packet
code:queue_packet
static void queue_packet(void *recv_packet, sniffer_packet_info_t *packet_info)
{
/* Copy a packet from Link Layer driver and queue the copy to be processed by sniffer task */
void *packet_to_queue = malloc(packet_info->length);
if (packet_to_queue) {
memcpy(packet_to_queue, recv_packet, packet_info->length);
packet_info->payload = packet_to_queue;
if (snf_rt.work_queue) {
/* send packet_info */
if (xQueueSend(snf_rt.work_queue, packet_info, pdMS_TO_TICKS(SNIFFER_PROCESS_PACKET_TIMEOUT_MS)) != pdTRUE) {
ESP_LOGE(SNIFFER_TAG, "sniffer work queue full");
free(packet_info->payload);
}
}
} else {
ESP_LOGE(SNIFFER_TAG, "No enough memory for promiscuous packet");
}
}
パケットをwork_queueにenqueueしている。
xQueueSendでキューに送り、sniffer_taskのxQueueReceiveで取り出してフレーム解析している。
キューで別のタスクにデータを送り、並行処理している。
sniffer_task
code:sniffer_task
static void sniffer_task(void *parameters)
{
sniffer_packet_info_t packet_info;
sniffer_runtime_t *sniffer = (sniffer_runtime_t *)parameters;
while (sniffer->is_running) {
if (sniffer->packets_to_sniff == 0) {
sniffer_stop(sniffer);
break;
}
/* receive packet info from queue */
if (xQueueReceive(sniffer->work_queue, &packet_info, pdMS_TO_TICKS(SNIFFER_PROCESS_PACKET_TIMEOUT_MS)) != pdTRUE) {
continue;
}
if (packet_capture(packet_info.payload, packet_info.length, packet_info.seconds,
packet_info.microseconds) != ESP_OK) {
ESP_LOGW(SNIFFER_TAG, "save captured packet failed");
}
free(packet_info.payload);
if (sniffer->packets_to_sniff > 0) {
sniffer->packets_to_sniff--;
}
}
/* notify that sniffer task is over */
if (sniffer->packets_to_sniff != 0) {
xSemaphoreGive(sniffer->sem_task_over);
}
vTaskDelete(NULL);
}
packet_captureはcmd_pcap.cで定義されていそう。後で読む。
おそらく、pcapファイルにする時にpacket_captureを呼び出すのだと思う。
packets_to_sniffが0になるまでキャプチャし続ける。
xTaskCreate
vTaskDelete
xSemaphoreTake
xSemaphoreGive
vSemaphoreDelete
vQueueDelete
こいつらはどのように使うんだろう。
xSemaphoreTake
xSemaphoreGive
こいつらは共有リソースにロックをかけるセマフォ。
セマフォをTakeしてリソースにアクセスし、終わったらGiveして返す。
このセマフォはxSemaphoreCreateBinaryで作成される。vSemaphoreDeleteで削除かな。
xTaskCreate
vTaskDelete
タスクを作成する。ここにおけるタスクとは?
sniffer_taskがタスク。RTOSがらみの概念な気がする。
vQueueDelete
キューを消す。ここではwork_queueを消していた。
RTOSのTask
RTOSのスケジュラーがスケジューリングする実行単位っぽい。
それで、セマフォはタスクの待ち合わせ機構らしい。
だいぶわかった。