diff --git a/.gitignore b/.gitignore old mode 100644 new mode 100755 index 659c34b..a131952 --- a/.gitignore +++ b/.gitignore @@ -77,15 +77,41 @@ Desktop.ini *.suo # Visual Studio Solution User Options *.sln.docstates # Visual Studio -# Игнорируем весь lwip +# components/lwip is a complete local ESP-IDF component override and must be +# kept in source archives/repositories. Ignoring it would make a fresh checkout +# impossible to build. + +# Local IDE configuration +.vscode/ + +# Windows download metadata +*:Zone.Identifier + +# Keep only the modified lwIP source file components/lwip/** -# Но разрешаем нужные папки !components/lwip/ !components/lwip/lwip/ !components/lwip/lwip/src/ !components/lwip/lwip/src/core/ !components/lwip/lwip/src/core/ipv4/ +!components/lwip/lwip/src/core/ipv4/etharp.c +# Keep only the modified lwIP source file +components/lwip/** -# И сам файл -!components/lwip/lwip/src/core/ipv4/etharp.c \ No newline at end of file +!components/lwip/ +!components/lwip/lwip/ +!components/lwip/lwip/src/ +!components/lwip/lwip/src/core/ +!components/lwip/lwip/src/core/ipv4/ +!components/lwip/lwip/src/core/ipv4/etharp.c + +# Keep only the modified lwIP source file +components/lwip/** + +!components/lwip/ +!components/lwip/lwip/ +!components/lwip/lwip/src/ +!components/lwip/lwip/src/core/ +!components/lwip/lwip/src/core/ipv4/ +!components/lwip/lwip/src/core/ipv4/etharp.c diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json deleted file mode 100644 index 33cd4d7..0000000 --- a/.vscode/c_cpp_properties.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "configurations": [ - { - "name": "ESP-IDF", - "compilerPath": "C:\\Espressif\\tools\\xtensa-esp-elf\\esp-15.2.0_20251204\\xtensa-esp-elf\\bin\\xtensa-esp32-elf-gcc.exe", - "compileCommands": [ - "${config:idf.buildPath}/compile_commands.json" - ], - "intelliSenseMode": "gcc-x86", - "browse": { - "path": [ - "${workspaceFolder}" - ], - "limitSymbolsToIncludedHeaders": true - } - } - ], - "version": 4 -} diff --git a/.vscode/launch.json b/.vscode/launch.json deleted file mode 100644 index 3694ae4..0000000 --- a/.vscode/launch.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "version": "0.2.0", - "configurations": [ - { - "type": "gdbtarget", - "request": "attach", - "name": "Eclipse CDT GDB Adapter" - } - ] -} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index 4d8c984..0000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "C_Cpp.intelliSenseEngine": "default", - "idf.extensionActivationMode": "always", - "idf.openOcdConfigs": [ - "board/esp32-wrover-kit-3.3v.cfg" - ], - "idf.currentSetup": "C:\\esp\\v6.0.2\\esp-idf", - "idf.customExtraVars": { - "IDF_TARGET": "esp32" - }, - "clangd.path": "C:\\Espressif\\tools\\esp-clang\\esp-20.1.1_20250829\\esp-clang\\bin\\clangd.exe", - "clangd.arguments": [ - "--background-index", - "--query-driver=**", - "--compile-commands-dir=c:\\Users\\Maxpr\\monitoring\\build" - ] -} diff --git a/CMakeLists.txt b/CMakeLists.txt index 157ba0f..46d69b7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,5 +2,7 @@ # CMakeLists in this exact order for cmake to work correctly cmake_minimum_required(VERSION 3.22) +set(COMPONENTS main) + include($ENV{IDF_PATH}/tools/cmake/project.cmake) project(monitoring) diff --git a/README.md b/README.md index f0b7787..0a9ebac 100644 --- a/README.md +++ b/README.md @@ -1,34 +1,43 @@ -# ARP Monitoring for ESP32 +# ARP Monitoring (ESP-IDF / Linux TAP) -> Практическая работа по анализу и модификации сетевого стека **lwIP** в среде **ESP-IDF**. +Практическая работа по анализу и модификации сетевого стека **lwIP** в +**ESP-IDF**. Проект перехватывает входящие ARP-пакеты, хранит обнаруженные узлы +в оперативной памяти, показывает их во встроенном веб-интерфейсе и позволяет +проверять выбранные адреса с помощью ICMP Echo (Ping). -## Описание - -Проект представляет собой систему мониторинга устройств локальной сети на базе **ESP32**. - -В ходе работы выполняется перехват входящих ARP-пакетов на уровне сетевого стека **lwIP**, извлекается информация об обнаруженных устройствах и формируется динамический список сетевых узлов. Полученные данные отображаются через встроенный HTTP-сервер. - -Дополнительно реализована возможность проверки доступности устройств с помощью **ICMP Echo (Ping)**. - -## Возможности - -* Перехват входящих ARP-пакетов. -* Обнаружение устройств локальной сети. -* Хранение списка устройств в оперативной памяти. -* Веб-интерфейс для просмотра обнаруженных устройств. -* Проверка доступности устройств с помощью Ping. +Рабочая конфигурация проверена на **ESP-IDF v6.0.2** с использованием целевой +платформы Linux и TAP-интерфейса без физической платы ESP32. ## Структура проекта -```text -components/ -│ -├── arp_monitor/ Перехват ARP-пакетов -├── device_list/ Работа со списком устройств -├── ping/ Проверка доступности устройств -├── web_server/ HTTP-сервер и веб-интерфейс -└── lwip/ Изменённый файл etharp.c +| Модуль | Назначение | +| --- | --- | +| `components/lwip` | Локальная копия lwIP; в `etharp_input()` установлен перехват ARP | +| `components/arp_monitor` | Принимает IP/MAC из lwIP через очередь FreeRTOS | +| `components/device_list` | Хранит до 64 найденных устройств в RAM | +| `components/web_server` | Отдаёт HTML-таблицу устройств и обрабатывает `/ping` | +| `components/ping` | Запускает стандартный `esp_ping` для выбранных узлов | +| `components/protocol_examples_tapif_io` | Подключает lwIP к Linux TAP без ESP32 | +| `main/main.c` | Отвечает за инициализацию компонентов | -main/ -└── main.c Точка входа приложения -``` \ No newline at end of file +Путь входящего пакета: + +1. TAP передаёт Ethernet-кадр в lwIP. +2. `ethernet_input()` определяет тип кадра `ETHTYPE_ARP` и вызывает + `etharp_input()` из `components/lwip/lwip/src/core/ipv4/etharp.c`. +3. После разбора ARP-заголовка `etharp_input()` вызывает + `arp_monitor_report()` с IP- и MAC-адресом отправителя. +4. `arp_monitor_report()` копирует данные в неблокирующую очередь FreeRTOS. +5. Задача `arp_monitor_task` обновляет статический массив `device_list` в RAM. +6. HTTP-обработчик читает актуальный список и формирует HTML-таблицу. + +## Веб-интерфейс + +![Веб-интерфейс ARP Monitoring](docs/web-interface.png) + +## Примечание + +Текущая версия полностью проверена на целевой платформе Linux с использованием +TAP-интерфейса без физической платы. Для запуска на ESP32 необходимо +дополнительно реализовать инициализацию Wi-Fi и подключение устройства к +локальной сети. diff --git a/components/arp_monitor/CMakeLists.txt b/components/arp_monitor/CMakeLists.txt index dafc5a5..9ca012b 100644 --- a/components/arp_monitor/CMakeLists.txt +++ b/components/arp_monitor/CMakeLists.txt @@ -3,4 +3,5 @@ idf_component_register( INCLUDE_DIRS "include" REQUIRES device_list -) \ No newline at end of file + lwip +) diff --git a/components/arp_monitor/arp_monitor.c b/components/arp_monitor/arp_monitor.c index b7ce3e7..142e496 100644 --- a/components/arp_monitor/arp_monitor.c +++ b/components/arp_monitor/arp_monitor.c @@ -20,7 +20,7 @@ void arp_monitor_init(void) arp_queue = xQueueCreate(ARP_QUEUE_LENGTH, sizeof(arp_event_t)); if (arp_queue == NULL) { - ESP_LOGE(TAG, "Failde to create ARP queue"); + ESP_LOGE(TAG, "Failed to create ARP queue"); return; } ESP_LOGI(TAG, "ARP queue created"); @@ -46,6 +46,11 @@ void arp_monitor_init(void) bool arp_monitor_post_event(const arp_event_t *event) { + if (event == NULL) + { + return false; + } + if (arp_queue == NULL) { ESP_LOGE(TAG, "ARP queue isn't initialized"); @@ -61,6 +66,21 @@ bool arp_monitor_post_event(const arp_event_t *event) return true; } +void arp_monitor_report(const ip4_addr_t *ip, const struct eth_addr *mac) +{ + if ((ip == NULL) || (mac == NULL)) + { + return; + } + + arp_event_t event = { + .ip = *ip + }; + + memcpy(event.mac, mac->addr, ETH_HWADDR_LEN); + arp_monitor_post_event(&event); +} + static void process_arp_event(const arp_event_t *event) { device_info_t device = { @@ -88,4 +108,4 @@ static void arp_monitor_task(void *arg) process_arp_event(&event); } } -} \ No newline at end of file +} diff --git a/components/arp_monitor/include/arp_monitor.h b/components/arp_monitor/include/arp_monitor.h index 7b5acbc..94a40d5 100644 --- a/components/arp_monitor/include/arp_monitor.h +++ b/components/arp_monitor/include/arp_monitor.h @@ -13,5 +13,6 @@ typedef struct void arp_monitor_init(void); bool arp_monitor_post_event(const arp_event_t *event); +void arp_monitor_report(const ip4_addr_t *ip, const struct eth_addr *mac); -#endif \ No newline at end of file +#endif diff --git a/components/device_list/CMakeLists.txt b/components/device_list/CMakeLists.txt index f8a54b7..c185026 100644 --- a/components/device_list/CMakeLists.txt +++ b/components/device_list/CMakeLists.txt @@ -1,4 +1,5 @@ idf_component_register( SRCS "device_list.c" INCLUDE_DIRS "include" -) \ No newline at end of file + REQUIRES lwip +) diff --git a/components/device_list/device_list.c b/components/device_list/device_list.c index 435fd41..b336e81 100644 --- a/components/device_list/device_list.c +++ b/components/device_list/device_list.c @@ -34,6 +34,11 @@ device_info_t *device_list_find_by_mac(const struct eth_addr *mac) bool device_list_add_or_update(const device_info_t *device) { + if (device == NULL) + { + return false; + } + device_info_t *existing_device = device_list_find_by_mac(&device->mac); if (existing_device != NULL) @@ -73,4 +78,4 @@ const device_info_t *device_list_get_by_index(size_t index) } return &device_list[index]; -} \ No newline at end of file +} diff --git a/components/lwip/lwip/src/core/ipv4/etharp.c b/components/lwip/lwip/src/core/ipv4/etharp.c index cb037d0..32769dd 100644 --- a/components/lwip/lwip/src/core/ipv4/etharp.c +++ b/components/lwip/lwip/src/core/ipv4/etharp.c @@ -57,17 +57,8 @@ #include "netif/ethernet.h" #include -#include "arp_monitor.h" -static inline void arp_monitor_report(const ip4_addr_t *ip, const struct eth_addr *mac) -{ - arp_event_t event; - - event.ip = *ip; - memcpy(event.mac, mac->addr, ETH_HWADDR_LEN); - - arp_monitor_post_event(&event); -} +void arp_monitor_report(const ip4_addr_t *ip, const struct eth_addr *mac); #ifdef LWIP_HOOK_FILENAME #include LWIP_HOOK_FILENAME diff --git a/components/ping/ping.c b/components/ping/ping.c index 167bc0c..b7abea7 100644 --- a/components/ping/ping.c +++ b/components/ping/ping.c @@ -8,7 +8,6 @@ #include "freertos/FreeRTOS.h" #include "freertos/semphr.h" -static const char *TAG = "ping"; typedef struct { @@ -16,7 +15,7 @@ typedef struct SemaphoreHandle_t finished; } ping_context_t; -static bool parse_mac_address(const char *text, struct eth_addr * mac); +static bool parse_mac_address(const char *text, struct eth_addr *mac); static void ping_device(device_info_t *device); void ping_selected_devices(const char *query); static void ping_success_callback(esp_ping_handle_t handle, void *arguments); @@ -25,11 +24,11 @@ static void ping_end_callback(esp_ping_handle_t handle, void *arguments); static bool parse_mac_address(const char *text, struct eth_addr *mac) { - if ((text == NULL) || (mac == NULL)) + if ((text == NULL) || (mac == NULL) || (strlen(text) != 12)) return false; return (sscanf(text, - "%2hhX:%2hhX:%2hhX:%2hhX:%2hhX:%2hhX", + "%2hhX%2hhX%2hhX%2hhX%2hhX%2hhX", &mac->addr[0], &mac->addr[1], &mac->addr[2], @@ -40,6 +39,9 @@ static bool parse_mac_address(const char *text, struct eth_addr *mac) void ping_selected_devices(const char *query) { + if (query == NULL) + return; + const char *current = query; while ((current = strstr(current, "mac=")) != NULL) @@ -47,7 +49,7 @@ void ping_selected_devices(const char *query) struct eth_addr mac; device_info_t *device; - char mac_string[18]; + char mac_string[13]; size_t length; current += 4; @@ -110,10 +112,17 @@ static void ping_device(device_info_t *device) if (esp_ping_new_session(&config, &callbacks, &ping) != ESP_OK) { vSemaphoreDelete(context.finished); + device->active = false; return; } - esp_ping_start(ping); + if (esp_ping_start(ping) != ESP_OK) + { + esp_ping_delete_session(ping); + vSemaphoreDelete(context.finished); + device->active = false; + return; + } xSemaphoreTake(context.finished, portMAX_DELAY); @@ -135,4 +144,4 @@ static void ping_end_callback(esp_ping_handle_t handle, void *args) { ping_context_t *context = (ping_context_t *)args; xSemaphoreGive(context->finished); -} \ No newline at end of file +} diff --git a/components/protocol_examples_tapif_io/CMakeLists.txt b/components/protocol_examples_tapif_io/CMakeLists.txt new file mode 100644 index 0000000..140acc5 --- /dev/null +++ b/components/protocol_examples_tapif_io/CMakeLists.txt @@ -0,0 +1,9 @@ +idf_build_get_property(target IDF_TARGET) + +if(${target} STREQUAL "linux") + idf_component_register(INCLUDE_DIRS include + SRCS linux/tapio.c linux_connect.c lwip/tapif.c + PRIV_REQUIRES esp_netif lwip) +else() + message(FATAL_ERROR "This component is currently only supported for linux target") +endif() diff --git a/components/protocol_examples_tapif_io/Kconfig.projbuild b/components/protocol_examples_tapif_io/Kconfig.projbuild new file mode 100644 index 0000000..ee60079 --- /dev/null +++ b/components/protocol_examples_tapif_io/Kconfig.projbuild @@ -0,0 +1,63 @@ +menu "Example Connection Configuration" + + config EXAMPLE_CONNECT_LWIP_TAPIF + bool "connect using lwip to linux tap interface" + depends on IDF_TARGET_LINUX && ESP_NETIF_TCPIP_LWIP + default y + + if EXAMPLE_CONNECT_LWIP_TAPIF + config EXAMPLE_CONNECT_IPV4 + bool + depends on LWIP_IPV4 + default y + + config EXAMPLE_CONNECT_IPV6 + bool "Obtain IPv6 address" + depends on LWIP_IPV6 + default y + help + Set to true to setup link local address and wait until it's valid + + config EXAMPLE_CONNECT_WAIT_FOR_IP + bool "run DHCP and wait for IP" + default y + + config EXAMPLE_CONNECT_TAPIF_IP_ADDR + string "Static IP address" + default "192.168.5.100" + depends on EXAMPLE_CONNECT_IPV4 && !EXAMPLE_CONNECT_WAIT_FOR_IP + help + Set static IP address. + + config EXAMPLE_CONNECT_TAPIF_NETMASK + string "Static netmask address" + default "255.255.255.0" + depends on EXAMPLE_CONNECT_IPV4 && !EXAMPLE_CONNECT_WAIT_FOR_IP + help + Set static netmask address. + + config EXAMPLE_CONNECT_TAPIF_GW + string "Static gateway address" + default "192.168.5.1" + depends on EXAMPLE_CONNECT_IPV4 && !EXAMPLE_CONNECT_WAIT_FOR_IP + help + Set static gateway address. + + config EXAMPLE_CONNECT_TAPIF_OUT_LOSS + int "Percentage of packets to be dropped on transmission" + default 0 + range 0 100 + help + Set non-zero number simulate packet loss when sending data. + Number represents probability between 0 and 100% + + config EXAMPLE_CONNECT_TAPIF_IN_LOSS + int "Percentage of packets to be dropped on reception" + default 0 + range 0 100 + help + Set non-zero number simulate packet loss when receiving data. + Number represents probability between 0 and 100% + endif + +endmenu diff --git a/components/protocol_examples_tapif_io/README.md b/components/protocol_examples_tapif_io/README.md new file mode 100644 index 0000000..3ac36ea --- /dev/null +++ b/components/protocol_examples_tapif_io/README.md @@ -0,0 +1,161 @@ +# tapif-io Component + +This component implements a tap networking interface that provides connectivity to host network using `tuntap` interface in Linux. +It could be used to route lwip traffic to host side network, typically when working with the **Linux target**. + +## How to use this component + +### Usage of the API + +1) Add the path to this component to as a dependency to the `main` component of your project using the following idf_component.yml file: +```yaml +dependencies: + tapif_io: + path: ${IDF_PATH}/examples/common_components/tapif_io +``` +2) Include lwip and linux side of the configuration +```cpp +#include "esp_netif.h" // esp-netif +#include "tapio.h" // esp-netif's driver side +#include "lwip/tapif.h" // esp-netif's network stack side +``` +3) Configure the esp-netif + a) setup the linux tap I/O config +```cpp + esp_netif_driver_ifconfig_t driver_cfg = { + .handle = tapio_create(), + .transmit = tapio_output, + }; +``` + + b) configure the lwip netif for the tap interface +```cpp + struct esp_netif_netstack_config stack_cfg = { + .lwip = { + .init_fn = lwip_tapif_init, + .input_fn = lwip_tapif_input, + } + }; +``` + + c) configure the esp-netif basic parameters +```cpp + esp_netif_inherent_config_t base_cfg = { + .if_key = "TAP", // unique name of the interface + .flags = ESP_NETIF_FLAG_AUTOUP, // no dhcp client, starts when it's set up + .ip_info = &ip_info, // add static IP info + .route_prio = 100 // priority for setting default gateway + }; +``` + + +4) Initialize and attach the esp_netif to the I/O handle +```cpp + esp_netif_t *tap_netif = esp_netif_new(&cfg); + esp_netif_attach(tap_netif, driver_cfg.handle); +``` + +### Host side networking + +1) Create a new tun/tap interface type named `tap0` + a) You can run the script `./make_tap_netif` + b) Update the IP address of the interface to correspond to the configured static IP in previous step + +2) Start the application and send/receive the packets via `tap0` interface + * it is possible to create server or client test application listening or connecting to this interface. + * it is also possible to route these packets to external network (using routing rules or simply by ip forwarding if using the same subnet) + +#### Common networking/routing examples + +##### Isolated internal connection + +Is useful to experiment with one interface with no intention to connect to internet or external facilities. +Typically, when we want to create a server listening on the `tap0` interface and run a client in lwip, e.g. the default `tcp_client` socket example in IDF. +* Create the tap interface using `./make_tap_netif` and set the IP address **not to overlap** with any other IPv4 network range (e.g. `ip addr add 192.168.5.1/24 dev tap0`) +* Configure the `tapif_io` component to use static address from that range (e.g. `192.168.5.x`) +* Configure the `tcp_client` example to connect to the tap interface IP address (e.g. `192.168.5.1`) +* Execute a tcp server listening on the tap interface and the configured port (e.g. `nc -l 3333`) +* Build and run the `tcp_client` example to send and receive data between the server created in the previous step. + +##### Connecting to the external network using IP forwarding + +This allows using full-featured network facilities of your host network, but a care must be taken to the selected IP addresses to avoid potential conflicts. +* Set the IP address of the `tap0` interface from the range used by your host system's default gateway (e.g. `ip addr add 192.168.0.123/24 dev tap0`, assuming the default netif is `eth0` with IP range of `192.168.0.x` and this address doesn't overlap with any other IP address in this network) +* Configure the `tapif_io` with another address from the same range, e.g. +```text +CONFIG_EXAMPLE_CONNECT_TAPIF_IP_ADDR="192.168.0.100" +CONFIG_EXAMPLE_CONNECT_TAPIF_NETMASK="255.255.255.0" +CONFIG_EXAMPLE_CONNECT_TAPIF_GW="192.168.0.1" +``` +assuming that the default gateway of your host network is configured to `192.168.0.1` +* Build and run the lwip example to interact with the host network, e.g, to send an HTTP request to a publicly available http server (if the server is reachable from your host network) + +(Note, that the IP forwarding must be enabled in the host system: +```bash +echo 1 > /proc/sys/net/ipv4/ip_forward +``` +) + +##### Routing the internal interface to the host network with IP tables + +Uses an isolated interface with routing and NAT-ing between interfaces +* Configure the `tap0` interface address **not to overlap** with any other IPv4 network range. +* Setup `MASQUERADE` target to route network traffic between `tap0` and your default network interface (`eth0` in the below example). +```bash +sudo iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE +sudo iptables -A FORWARD -i eth0 -o tap0 -m state --state RELATED,ESTABLISHED -j ACCEPT +sudo iptables -A FORWARD -i tap0 -o eth0 -j ACCEPT +``` + +##### Using DHCP + +It's also possible to configure the lwip interface to use DHCP client (common setup for most default network interfaces, such as Ethernet or WiFi station) +and set up a DHCP server on the host machine to assign the IP address dynamically. + +This component sets up a DHCP client if `CONFIG_EXAMPLE_CONNECT_WAIT_FOR_IP` is enabled and waits for assigning an IP address. See below the description of DHCP client workflow for tap interface: + +1) **Configure and set the `esp-netif` up** + +* Same as in [API usage](#Usage-of-the-API), but update the base esp-netif config `3c)` to enable DHCP client +```cpp + esp_netif_inherent_config_t base_cfg = { + .if_key = "TAP", + .flags = (esp_netif_flags_t)(ESP_NETIF_DHCP_CLIENT | ESP_NETIF_FLAG_EVENT_IP_MODIFIED | ESP_NETIF_FLAG_AUTOUP), + .route_prio = 100 + }; +``` +* After starting the netif, tell the lwIP that we're connected +```cpp + esp_netif_action_connected(tap_netif, 0, 0, 0); +``` +* Wait for the IP address to be assigned. +This could be implemented using an event handler +```cpp + esp_netif_inherent_config_t base_cfg = { + ... + .get_ip_event = TAP0_GOT_IP, + ... + }; + ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, TAP0_GOT_IP, event_handler, NULL)); + // wait for the IP event (e.g. using signalling semaphores from the handler) + // ... + esp_netif_ip_info_t ip_info = {}; + ESP_LOGI("tap-init", "Assigned IP address:"IPSTR ",", IP2STR(&ip_info.ip)); +``` + +2) **Configure forwarding/routing** if needed based on the previous sections. + +3) **Configure the DHCP server on the host machine** + +Example for `isc-dhcp-server` + +```bash +INTERFACES="tap0"; +authoritative; + +subnet 192.168.5.0 netmask 255.255.255.0 { + range 192.168.5.2 192.168.5.200; + option routers 192.168.5.1; + option domain-name-servers 8.8.8.8; +} +``` diff --git a/components/protocol_examples_tapif_io/include/protocol_examples_common.h b/components/protocol_examples_tapif_io/include/protocol_examples_common.h new file mode 100644 index 0000000..3ebfd2a --- /dev/null +++ b/components/protocol_examples_tapif_io/include/protocol_examples_common.h @@ -0,0 +1,32 @@ +/* + * SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ +/* Common functions for protocol examples, to establish tap interface connection + * For linux target + + 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 +#include "esp_err.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Configure and connect, wait for IP + * + * @return ESP_OK on successful connection + */ +esp_err_t example_connect(void); + + +#ifdef __cplusplus +} +#endif diff --git a/components/protocol_examples_tapif_io/include/tapio.h b/components/protocol_examples_tapif_io/include/tapio.h new file mode 100644 index 0000000..f646d75 --- /dev/null +++ b/components/protocol_examples_tapif_io/include/tapio.h @@ -0,0 +1,27 @@ +/* + * SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#pragma once +#include "esp_err.h" +#include "esp_netif.h" + +/** + * @brief Creates tapio layer as a driver interface to esp-netif + * + * @warning Implemented as singleton, can use only one tapio in the system! + * + * @return pointer to the tapio driver handle + */ +void *tapio_create(void); + +/** + * @brief esp-netif driver I/O output path + * + * @param h Driver's handle + * @param buffer Data to output + * @param len Data size + * @return ESP_OK on success + */ +esp_err_t tapio_output(void *h, void *buffer, size_t len); diff --git a/components/protocol_examples_tapif_io/linux/tapio.c b/components/protocol_examples_tapif_io/linux/tapio.c new file mode 100644 index 0000000..4d46f9f --- /dev/null +++ b/components/protocol_examples_tapif_io/linux/tapio.c @@ -0,0 +1,134 @@ +/* + * SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include +#include +#include +#include "esp_err.h" +#include "esp_log.h" +#include "esp_netif.h" + +// Use linux system sockets to connect to tap interface +#define LWIP_HDR_LINUX_SYS_SOCKETS_H +#include +#include + +#include +#include +#include +#include +#include "errno.h" + +#include +#include + +#define DEVTAP "/dev/net/tun" +#define DEVTAP_NAME "tap0" + + +typedef struct tap_io { + esp_netif_driver_base_t base; + int fd; +} tap_io_t; + +static const char *TAG = "tap-netif"; + +static void tapio_input_task(void *arg) +{ + tap_io_t *io = arg; + fd_set fdset; + int ret; + while (1) { + FD_ZERO(&fdset); + FD_SET(io->fd, &fdset); + + /* Wait for a packet to arrive. */ + ret = select(io->fd + 1, &fdset, NULL, NULL, NULL); + + if (ret == 1) { + /* Handle incoming packet. */ + ssize_t readlen; + char buf[1518]; /* max packet size including VLAN excluding CRC */ + + /* Obtain the size of the packet and put it into the "len" + variable. */ + readlen = read(io->fd, buf, sizeof(buf)); + if (readlen < 0) { + ESP_LOGE(TAG, "Failed to read from tap fd: returned %ld", readlen); + exit(1); + } +#if CONFIG_EXAMPLE_CONNECT_TAPIF_IN_LOSS + if (((double)rand()/(double)RAND_MAX) < ((double)CONFIG_EXAMPLE_CONNECT_TAPIF_IN_LOSS)/100.0) { + ESP_LOGW(TAG, "Simulated packet drop on input"); + continue; + } +#endif + esp_netif_receive(io->base.netif, buf, readlen, NULL); + } else if (ret == -1) { + if (errno == EINTR /* Interrupted system call (used by FreeRTOS simulated interrupts) */) { + vTaskDelay(1); // yield to the FreeRTOS simulator + } else { + ESP_LOGE(TAG, "tapif_thread: select() error(%d), %s", errno, strerror(errno)); + } + } + } +} + +static esp_err_t tapio_start(esp_netif_t *esp_netif, void *arg) +{ + tap_io_t *io = arg; + io->base.netif = esp_netif; + esp_netif_action_start(esp_netif, 0, 0, 0); + + sys_thread_new("tapio_rx", tapio_input_task, io, DEFAULT_THREAD_STACKSIZE, DEFAULT_THREAD_PRIO); + + return ESP_OK; +} + +void *tapio_create(void) +{ + static tap_io_t tap_io = {}; + tap_io.base.post_attach = tapio_start; + tap_io.fd = open(DEVTAP, O_RDWR); + if (tap_io.fd == -1) { + ESP_LOGE(TAG, "Cannot open tap device %s", DEVTAP); + return NULL; + } + + struct ifreq ifr = {}; + memset(&ifr, 0, sizeof(ifr)); + + strncpy(ifr.ifr_name, DEVTAP_NAME, sizeof(ifr.ifr_name)); + ifr.ifr_name[sizeof(ifr.ifr_name)-1] = 0; /* ensure \0 termination */ + ifr.ifr_flags = IFF_TAP|IFF_NO_PI; + if (ioctl(tap_io.fd, TUNSETIFF, (void *) &ifr) < 0) { + ESP_LOGE(TAG, "Cannot configure ioctl(TUNSETIFF) for \"%s\"", DEVTAP); + return NULL; + } + + + return &tap_io; +} + +esp_err_t tapio_output(void *h, void *buffer, size_t len) +{ + tap_io_t *io = h; + ssize_t written; + +#if CONFIG_EXAMPLE_CONNECT_TAPIF_OUT_LOSS + if (((double)rand()/(double)RAND_MAX) < ((double)CONFIG_EXAMPLE_CONNECT_TAPIF_OUT_LOSS)/100.0) { + ESP_LOGW(TAG, "Simulated packet drop on output"); + return ESP_OK; /* ESP_OK because we simulate packet loss on cable */ + } +#endif + + /* signal that packet should be sent(); */ + written = write(io->fd, buffer, len); + if (written < len) { + ESP_LOGE(TAG, "Failed to write from tap fd: returned %ld", written); + return ESP_FAIL; + } + return ESP_OK; +} diff --git a/components/protocol_examples_tapif_io/linux_connect.c b/components/protocol_examples_tapif_io/linux_connect.c new file mode 100644 index 0000000..a7fce1e --- /dev/null +++ b/components/protocol_examples_tapif_io/linux_connect.c @@ -0,0 +1,86 @@ +/* + * SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include "esp_err.h" +#include "esp_netif.h" // esp-netif +#include "tapio.h" // esp-netif's driver side +#include "lwip/tapif.h" // esp-netif's network stack side +#include "esp_log.h" + +#if CONFIG_EXAMPLE_CONNECT_WAIT_FOR_IP +#include "esp_event.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/event_groups.h" + +static const char *TAG = "linux_connect"; + +static EventGroupHandle_t s_events; + +static void event_handler(void* arg, esp_event_base_t event_base, int32_t event_id, void* event_data) +{ + xEventGroupSetBits(s_events, 1); +} + +#endif // CONFIG_EXAMPLE_CONNECT_WAIT_FOR_IP + +#define TAP0_GOT_IP (0x1234) + +esp_err_t example_connect(void) +{ +#if CONFIG_EXAMPLE_CONNECT_LWIP_TAPIF + // configure linux tapio + esp_netif_driver_ifconfig_t driver_cfg = { + .handle = tapio_create(), + .transmit = tapio_output, + }; + // configure lwip netif for the tapif + struct esp_netif_netstack_config stack_cfg = { + .lwip = { + .init_fn = lwip_tapif_init, + .input_fn = lwip_tapif_input, + } + }; + // configure inherent esp-netif parameters + esp_netif_ip_info_t ip_info = {}; + esp_netif_flags_t netif_flags = (ESP_NETIF_FLAG_EVENT_IP_MODIFIED | ESP_NETIF_FLAG_AUTOUP); +#if !CONFIG_EXAMPLE_CONNECT_WAIT_FOR_IP + ip_info.ip.addr = ipaddr_addr(CONFIG_EXAMPLE_CONNECT_TAPIF_IP_ADDR); + ip_info.netmask.addr = ipaddr_addr(CONFIG_EXAMPLE_CONNECT_TAPIF_NETMASK); + ip_info.gw.addr = ipaddr_addr(CONFIG_EXAMPLE_CONNECT_TAPIF_GW); +#else + netif_flags |= ESP_NETIF_DHCP_CLIENT; +#endif + + esp_netif_inherent_config_t base_cfg = { + .if_key = "TAP", + .ip_info = &ip_info, + .flags = netif_flags, + .get_ip_event = TAP0_GOT_IP, + .route_prio = 100 + }; + + // put all configs together + esp_netif_config_t cfg = { + .base = &base_cfg, + .driver = &driver_cfg, + .stack = &stack_cfg + }; + + // create the interface and attach it to the tapio-handle + esp_netif_t *tap_netif = esp_netif_new(&cfg); + esp_netif_attach(tap_netif, driver_cfg.handle); +#if CONFIG_EXAMPLE_CONNECT_WAIT_FOR_IP + ESP_LOGI(TAG, "Waiting for IP addresses..."); + esp_netif_action_connected(tap_netif, 0, 0, 0); + ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, TAP0_GOT_IP, event_handler, NULL)); + s_events = xEventGroupCreate(); + xEventGroupWaitBits(s_events, 1, pdFALSE, pdFALSE, portMAX_DELAY); + esp_netif_get_ip_info(tap_netif, &ip_info); + ESP_LOGI(TAG, "Assigned IP address:"IPSTR ",", IP2STR(&ip_info.ip)); +#endif // CONFIG_EXAMPLE_CONNECT_WAIT_FOR_IP +#endif // EXAMPLE_CONNECT_LWIP_TAPIF + return ESP_OK; +} diff --git a/components/protocol_examples_tapif_io/lwip/tapif.c b/components/protocol_examples_tapif_io/lwip/tapif.c new file mode 100644 index 0000000..a96ece6 --- /dev/null +++ b/components/protocol_examples_tapif_io/lwip/tapif.c @@ -0,0 +1,103 @@ +/* + * SPDX-FileCopyrightText: 2001-2003 Swedish Institute of Computer Science + * + * SPDX-License-Identifier: BSD-3-Clause + * + * SPDX-FileContributor: 2022-2023 Espressif Systems (Shanghai) CO LTD + */ +#include +#include "lwip/opt.h" +#include "lwip/pbuf.h" +#include "lwip/snmp.h" +#include "lwip/ethip6.h" +#include "netif/etharp.h" + +#include "esp_netif.h" +#include "esp_netif_net_stack.h" + +#define IFNAME0 't' +#define IFNAME1 'p' + +static err_t +low_level_output(struct netif *netif, struct pbuf *p) +{ + esp_netif_t *esp_netif = esp_netif_get_handle_from_netif_impl(netif); + char buf[1518]; /* max packet size including VLAN excluding CRC */ + + if (p->tot_len > sizeof(buf)) { + MIB2_STATS_NETIF_INC(netif, ifoutdiscards); + LWIP_DEBUGF(NETIF_DEBUG, ("tapif: packet too large")); + return ERR_IF; + } + + /* initiate transfer(); */ + pbuf_copy_partial(p, buf, p->tot_len, 0); + + int ret = esp_netif_transmit(esp_netif, buf, p->tot_len); + /* Check error */ + if (likely(ret == ESP_OK)) { + return ERR_OK; + } + if (ret == ESP_ERR_NO_MEM) { + return ERR_MEM; + } + return ERR_IF; +} + +static void +low_level_init(struct netif *netif) +{ + + /* Obtain MAC address from network interface. */ + netif->hwaddr[0] = 0x02; + netif->hwaddr[1] = 0x12; + netif->hwaddr[2] = 0x34; + netif->hwaddr[3] = 0x56; + netif->hwaddr[4] = 0x78; + netif->hwaddr[5] = 0xab; + netif->hwaddr_len = 6; + + /* device capabilities */ + netif->flags = NETIF_FLAG_BROADCAST | NETIF_FLAG_ETHARP | NETIF_FLAG_IGMP; +} + +err_t lwip_tapif_init(struct netif *netif) +{ + LWIP_ASSERT("Tried to initialize tapif with NULL netif", netif != NULL); + netif->name[0] = IFNAME0; + netif->name[1] = IFNAME1; +#if LWIP_IPV4 + netif->output = etharp_output; +#endif /* LWIP_IPV4 */ +#if LWIP_IPV6 + netif->output_ip6 = ethip6_output; +#endif /* LWIP_IPV6 */ + netif->linkoutput = low_level_output; + netif->mtu = 1500; + + low_level_init(netif); + netif_set_link_up(netif); + return ERR_OK; +} + +esp_err_t lwip_tapif_input(void *h, void *buffer, size_t len, void *l2_buff) +{ + struct netif *netif = h; + struct pbuf *p; + LWIP_ASSERT("running tapif input with NULL netif", netif != NULL); + + p = pbuf_alloc(PBUF_RAW, len, PBUF_RAM); + if (p == NULL) { + return ESP_ERR_NO_MEM; + } + memcpy(p->payload, buffer, len); + + /* full packet send to tcpip_thread to process */ + if (unlikely(netif->input(p, netif) != ERR_OK)) { + LWIP_DEBUGF(NETIF_DEBUG, ("tapif_input: IP input error\n")); + pbuf_free(p); + return ESP_FAIL; + } + + return ESP_OK; +} diff --git a/components/protocol_examples_tapif_io/lwip/tapif.h b/components/protocol_examples_tapif_io/lwip/tapif.h new file mode 100644 index 0000000..aae29e7 --- /dev/null +++ b/components/protocol_examples_tapif_io/lwip/tapif.h @@ -0,0 +1,25 @@ +/* + * SPDX-FileCopyrightText: 2001-2003 Swedish Institute of Computer Science + * + * SPDX-License-Identifier: BSD-3-Clause + * + * SPDX-FileContributor: 2022-2023 Espressif Systems (Shanghai) CO LTD + */ +#pragma once +#include "lwip/esp_netif_net_stack.h" + +/** + * @brief lwip netif init API + * @param netif pointer to lwip's netif + * @return ERR_OK on success + */ +err_t lwip_tapif_init(struct netif *netif); + +/** + * @brief Input data path + * @param h pointer to network stack handle (stuct netif* in our case) + * @param buffer Data + * @param len Data size + * @param l2_buff Data L2 buffer + */ +esp_err_t lwip_tapif_input(void *h, void *buffer, size_t len, void *l2_buff); diff --git a/components/protocol_examples_tapif_io/make_tap_netif b/components/protocol_examples_tapif_io/make_tap_netif new file mode 100755 index 0000000..3857edb --- /dev/null +++ b/components/protocol_examples_tapif_io/make_tap_netif @@ -0,0 +1,4 @@ +#!/usr/bin/env bash +sudo ip tuntap add dev tap0 mode tap user `whoami` +sudo ip link set tap0 up +sudo ip addr add 192.168.5.1/24 dev tap0 diff --git a/components/web_server/web_server.c b/components/web_server/web_server.c index 2cdf12f..0b405ca 100644 --- a/components/web_server/web_server.c +++ b/components/web_server/web_server.c @@ -3,6 +3,7 @@ #include "ping.h" #include "esp_http_server.h" #include "esp_log.h" +#include static const char *TAG = "web_server"; @@ -88,7 +89,7 @@ static esp_err_t main_page_handler(httpd_req_t *req) return err; } - return httpd_resp_sendstr_chunk(req, NULL);; + return httpd_resp_sendstr_chunk(req, NULL); } static esp_err_t send_page_header(httpd_req_t *req) @@ -123,6 +124,9 @@ static esp_err_t send_device_table(httpd_req_t *req) "" ); + if (err != ESP_OK) + return err; + for (size_t index = 0; index < device_list_count(); index++) { const device_info_t *current_device; @@ -156,7 +160,7 @@ static esp_err_t send_device_row(httpd_req_t *req, const device_info_t *device) "" "" - "" + "" "" "%u.%u.%u.%u" "%02X:%02X:%02X:%02X:%02X:%02X" @@ -210,4 +214,4 @@ static esp_err_t ping_handler(httpd_req_t *req) } return main_page_handler(req); -} \ No newline at end of file +} diff --git a/docs/web-interface.png b/docs/web-interface.png new file mode 100644 index 0000000..1035dcb Binary files /dev/null and b/docs/web-interface.png differ diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt index 84a912a..86ec110 100644 --- a/main/CMakeLists.txt +++ b/main/CMakeLists.txt @@ -1,8 +1,19 @@ +idf_build_get_property(target IDF_TARGET) + +set(requires + arp_monitor + device_list + web_server + esp_netif + esp_event +) + +if("${target}" STREQUAL "linux") + list(APPEND requires protocol_examples_tapif_io) +endif() + idf_component_register( SRCS "main.c" INCLUDE_DIRS "." - REQUIRES - arp_monitor - device_list - web_server -) \ No newline at end of file + REQUIRES ${requires} +) diff --git a/main/main.c b/main/main.c index 617b5aa..83f3992 100644 --- a/main/main.c +++ b/main/main.c @@ -1,20 +1,30 @@ #include "arp_monitor.h" #include "device_list.h" #include "web_server.h" +#include "esp_err.h" +#include "esp_event.h" +#include "esp_netif.h" #include "esp_log.h" +#ifdef CONFIG_IDF_TARGET_LINUX +#include "protocol_examples_common.h" +#endif + +static const char *TAG = "main"; + void app_main(void) { device_list_init(); arp_monitor_init(); + +#ifdef CONFIG_IDF_TARGET_LINUX + ESP_ERROR_CHECK(esp_netif_init()); + ESP_ERROR_CHECK(esp_event_loop_create_default()); + ESP_ERROR_CHECK(example_connect()); + ESP_LOGI(TAG, "Linux network interface initialized"); +#else + ESP_LOGW(TAG, "Network initialization for the ESP32 target is not configured"); +#endif + web_server_init(); - - arp_event_t event = {0}; - IP4_ADDR(&event.ip, 192, 168, 1, 10); - uint8_t mac[6] = {0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF}; - memcpy(event.mac, mac, ETH_HWADDR_LEN); - - arp_monitor_post_event(&event); - vTaskDelay(pdMS_TO_TICKS(100)); - ESP_LOGI("TEST", "Count = %u", device_list_count()); } diff --git a/sdkconfig.defaults b/sdkconfig.defaults new file mode 100644 index 0000000..e69de29 diff --git a/sdkconfig.defaults.linux b/sdkconfig.defaults.linux new file mode 100644 index 0000000..82d7ad8 --- /dev/null +++ b/sdkconfig.defaults.linux @@ -0,0 +1,9 @@ +CONFIG_LWIP_ENABLE=y +CONFIG_ESP_NETIF_TCPIP_LWIP=y +CONFIG_EXAMPLE_CONNECT_LWIP_TAPIF=y +CONFIG_EXAMPLE_CONNECT_IPV4=y +# CONFIG_EXAMPLE_CONNECT_IPV6 is not set +# CONFIG_EXAMPLE_CONNECT_WAIT_FOR_IP is not set +CONFIG_EXAMPLE_CONNECT_TAPIF_IP_ADDR="192.168.5.100" +CONFIG_EXAMPLE_CONNECT_TAPIF_NETMASK="255.255.255.0" +CONFIG_EXAMPLE_CONNECT_TAPIF_GW="192.168.5.1"