diff --git a/.gitignore b/.gitignore old mode 100755 new mode 100644 index a131952..3d665ac --- a/.gitignore +++ b/.gitignore @@ -1,117 +1,75 @@ -# macOS +# OS metadata .DS_Store .AppleDouble .LSOverride - -# Directory metadata .directory +Thumbs.db +ehthumbs.db +Desktop.ini +*:Zone.Identifier -# Temporary files +# Temporary files and logs *~ *.swp *.swo *.bak *.tmp - -# Log files *.log -# Build artifacts and directories +# Build outputs **/build/ -build/ +/build-esp32/ *.o *.a *.out -*.exe # For any host-side utilities compiled on Windows - -# ESP-IDF specific build outputs +*.exe *.bin *.elf *.map -flasher_args.json # Generated in build directory -sdkconfig.old -sdkconfig +flasher_args.json -# ESP-IDF dependencies -# For older versions or manual component management +# Local ESP-IDF configurations +/sdkconfig +/sdkconfig.old +/sdkconfig.esp32 +/sdkconfig.esp32.old + +# Dependencies and locally installed tools /components/.idf/ **/components/.idf/ -# For modern ESP-IDF component manager managed_components/ -# If ESP-IDF tools are installed/referenced locally to the project .espressif/ +# Local lwIP library is kept on disk; the modified ARP source is in patches/lwip. +/components/lwip/ + +# Personal instructions and IDE settings +/.local-notes/ +.vscode/ +*.user +*.workspace +*.suo +*.sln.docstates + # CMake generated files CMakeCache.txt CMakeFiles/ cmake_install.cmake install_manifest.txt CTestTestfile.cmake +compile_commands.json -# Python environment files +# Python caches and environments *.pyc *.pyo *.pyd __pycache__/ *.egg-info/ dist/ - -# Virtual environment folders venv/ .venv/ env/ -# Language Servers +# Language servers .clangd/ .ccls-cache/ -compile_commands.json - -# Windows specific -Thumbs.db -ehthumbs.db -Desktop.ini - -# User-specific configuration files -*.user -*.workspace # General workspace files, can be from various tools -*.suo # Visual Studio Solution User Options -*.sln.docstates # Visual Studio - -# 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/ -!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/CMakeLists.txt b/CMakeLists.txt index 46d69b7..4c3eecb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,8 +1,9 @@ -# The following five 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.22) set(COMPONENTS main) include($ENV{IDF_PATH}/tools/cmake/project.cmake) +include("${CMAKE_CURRENT_LIST_DIR}/cmake/prepare_lwip.cmake") project(monitoring) + +include("${CMAKE_CURRENT_LIST_DIR}/cmake/tapif_patch.cmake") diff --git a/README.md b/README.md index 0a9ebac..9eb621e 100644 --- a/README.md +++ b/README.md @@ -7,24 +7,30 @@ Рабочая конфигурация проверена на **ESP-IDF v6.0.2** с использованием целевой платформы Linux и TAP-интерфейса без физической платы ESP32. +Сборка прошивки для **ESP32** также выполнена успешно; запуск на физической +плате пока не проверялся. ## Структура проекта | Модуль | Назначение | | --- | --- | -| `components/lwip` | Локальная копия lwIP; в `etharp_input()` установлен перехват ARP | +| `patches/lwip/etharp.c` | Изменённый файл lwIP с перехватом ARP в `etharp_input()` | +| `patches/lwip/prepare.py` | Подготавливает lwIP из ESP-IDF в каталоге сборки с рабочими исправлениями | +| `cmake/prepare_lwip.cmake` | Подключает подготовленную библиотеку при сборке Linux и ESP32 | | `components/arp_monitor` | Принимает IP/MAC из lwIP через очередь FreeRTOS | | `components/device_list` | Хранит до 64 найденных устройств в RAM | -| `components/web_server` | Отдаёт HTML-таблицу устройств и обрабатывает `/ping` | +| `components/web_server` | GET `/` показывает таблицу; POST `/` запускает Ping с возвратом на главную | | `components/ping` | Запускает стандартный `esp_ping` для выбранных узлов | -| `components/protocol_examples_tapif_io` | Подключает lwIP к Linux TAP без ESP32 | -| `main/main.c` | Отвечает за инициализацию компонентов | +| `patches/protocol_examples_tapif_io` | Исправленные `tapif.c` и `tapif.h` для TAP-компонента ESP-IDF v6.0.2 | +| `cmake/tapif_patch.cmake` | Подставляет исправленные файлы при сборке Linux | +| `main/main.c` | Инициализирует компоненты и вызывает `example_connect()` | +| `main/idf_component.yml` | Подключает TAP-компонент для Linux или `protocol_examples_common` для платы из ESP-IDF | Путь входящего пакета: -1. TAP передаёт Ethernet-кадр в lwIP. +1. Сетевой интерфейс (TAP при тестировании на Linux) передаёт Ethernet-кадр в lwIP. 2. `ethernet_input()` определяет тип кадра `ETHTYPE_ARP` и вызывает - `etharp_input()` из `components/lwip/lwip/src/core/ipv4/etharp.c`. + `etharp_input()` из `patches/lwip/etharp.c`. 3. После разбора ARP-заголовка `etharp_input()` вызывает `arp_monitor_report()` с IP- и MAC-адресом отправителя. 4. `arp_monitor_report()` копирует данные в неблокирующую очередь FreeRTOS. @@ -37,7 +43,10 @@ ## Примечание -Текущая версия полностью проверена на целевой платформе Linux с использованием -TAP-интерфейса без физической платы. Для запуска на ESP32 необходимо -дополнительно реализовать инициализацию Wi-Fi и подключение устройства к -локальной сети. +Проект проверен в работе на Linux/TAP. Прошивка для ESP32 успешно собирается, +но на самой плате пока не запускалась. + +Изменённый `etharp.c` хранится в `patches/lwip`. Библиотека lwIP автоматически +подготавливается из ESP-IDF v6.0.2 в каталоге сборки. Хранить её в `components` +или вручную восстанавливать после клонирования не нужно. +TAP-компонент берётся из ESP-IDF с исправлениями из `patches/protocol_examples_tapif_io`. diff --git a/cmake/prepare_lwip.cmake b/cmake/prepare_lwip.cmake new file mode 100644 index 0000000..bc3c48d --- /dev/null +++ b/cmake/prepare_lwip.cmake @@ -0,0 +1,29 @@ +# Prepare the same complete component layout that worked locally, inside build. +# Run before project() so ESP-IDF can discover the generated component normally. +if(EXISTS "${CMAKE_CURRENT_LIST_DIR}/../components/lwip/CMakeLists.txt") + message(FATAL_ERROR "Remove components/lwip: it would override the generated build component.") +endif() +if(NOT PYTHON) + find_package(Python3 REQUIRED COMPONENTS Interpreter) + set(PYTHON "${Python3_EXECUTABLE}") +endif() +set(monitoring_lwip_generator "${CMAKE_CURRENT_LIST_DIR}/../patches/lwip/prepare.py") +set(monitoring_arp_source "${CMAKE_CURRENT_LIST_DIR}/../patches/lwip/etharp.c") +set(monitoring_lwip_dir "${CMAKE_BINARY_DIR}/monitoring_components/lwip") +execute_process( + COMMAND "${PYTHON}" "${monitoring_lwip_generator}" + "${IDF_PATH}/components/lwip" "${monitoring_lwip_dir}" "${monitoring_arp_source}" + RESULT_VARIABLE monitoring_lwip_result + OUTPUT_VARIABLE monitoring_lwip_output + ERROR_VARIABLE monitoring_lwip_error +) +if(NOT monitoring_lwip_result EQUAL 0) + message(FATAL_ERROR "lwIP preparation failed: ${monitoring_lwip_error}") +endif() +set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS + "${monitoring_lwip_generator}" + "${IDF_PATH}/components/lwip/CMakeLists.txt" + "${IDF_PATH}/components/lwip/lwip/src/include/lwip/sockets.h" + "${IDF_PATH}/components/lwip/port/linux/include/sys/fcntl.h") +list(APPEND EXTRA_COMPONENT_DIRS "${monitoring_lwip_dir}") +message(STATUS "lwIP: prepared in ${monitoring_lwip_dir}; ARP source is patches/lwip/etharp.c") diff --git a/cmake/tapif_patch.cmake b/cmake/tapif_patch.cmake new file mode 100644 index 0000000..def4744 --- /dev/null +++ b/cmake/tapif_patch.cmake @@ -0,0 +1,56 @@ +# Applied after project(): component targets already exist at this point. +include_guard(GLOBAL) + +function(monitoring_patch_tapif) + idf_build_get_property(tap_target IDF_TARGET) + if(NOT "${tap_target}" STREQUAL "linux") + return() + endif() + + idf_component_get_property(tap_lib protocol_examples_tapif_io COMPONENT_LIB) + idf_component_get_property(tap_dir protocol_examples_tapif_io COMPONENT_DIR) + idf_build_get_property(tap_idf_path IDF_PATH) + get_filename_component(tap_dir "${tap_dir}" REALPATH) + get_filename_component(tap_sdk_dir + "${tap_idf_path}/examples/common_components/protocol_examples_tapif_io" REALPATH) + if(NOT "${tap_dir}" STREQUAL "${tap_sdk_dir}") + message(FATAL_ERROR + "TAP must come from ESP-IDF. Move components/protocol_examples_tapif_io outside the project, then reconfigure.") + endif() + + set(tap_patch_dir "${CMAKE_CURRENT_FUNCTION_LIST_DIR}/../patches/protocol_examples_tapif_io") + set(tap_generated_dir "${CMAKE_BINARY_DIR}/monitoring_tapif") + file(MAKE_DIRECTORY "${tap_generated_dir}/lwip") + + # Quoted includes search the source's own directory before -I directories. + # Relocate this unmodified SDK source so it finds our matching declaration. + configure_file("${tap_dir}/linux_connect.c" + "${tap_generated_dir}/linux_connect.c" COPYONLY) + configure_file("${tap_patch_dir}/tapif.h" + "${tap_generated_dir}/lwip/tapif.h" COPYONLY) + + get_target_property(tap_sources "${tap_lib}" SOURCES) + set(tap_new_sources "") + set(tap_input_found FALSE) + set(tap_connect_found FALSE) + foreach(tap_source IN LISTS tap_sources) + get_filename_component(tap_absolute "${tap_source}" ABSOLUTE BASE_DIR "${tap_dir}") + if("${tap_absolute}" STREQUAL "${tap_dir}/lwip/tapif.c") + list(APPEND tap_new_sources "${tap_patch_dir}/tapif.c") + set(tap_input_found TRUE) + elseif("${tap_absolute}" STREQUAL "${tap_dir}/linux_connect.c") + list(APPEND tap_new_sources "${tap_generated_dir}/linux_connect.c") + set(tap_connect_found TRUE) + else() + list(APPEND tap_new_sources "${tap_source}") + endif() + endforeach() + if(NOT tap_input_found OR NOT tap_connect_found) + message(FATAL_ERROR + "Unexpected TAP source list. This compatibility patch was prepared for ESP-IDF v6.0.2.") + endif() + set_property(TARGET "${tap_lib}" PROPERTY SOURCES "${tap_new_sources}") + message(STATUS "TAP: using ESP-IDF component with project-local tapif.c/tapif.h fix") +endfunction() + +monitoring_patch_tapif() diff --git a/components/protocol_examples_tapif_io/CMakeLists.txt b/components/protocol_examples_tapif_io/CMakeLists.txt deleted file mode 100644 index 140acc5..0000000 --- a/components/protocol_examples_tapif_io/CMakeLists.txt +++ /dev/null @@ -1,9 +0,0 @@ -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 deleted file mode 100644 index ee60079..0000000 --- a/components/protocol_examples_tapif_io/Kconfig.projbuild +++ /dev/null @@ -1,63 +0,0 @@ -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 deleted file mode 100644 index 3ac36ea..0000000 --- a/components/protocol_examples_tapif_io/README.md +++ /dev/null @@ -1,161 +0,0 @@ -# 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 deleted file mode 100644 index 3ebfd2a..0000000 --- a/components/protocol_examples_tapif_io/include/protocol_examples_common.h +++ /dev/null @@ -1,32 +0,0 @@ -/* - * 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 deleted file mode 100644 index f646d75..0000000 --- a/components/protocol_examples_tapif_io/include/tapio.h +++ /dev/null @@ -1,27 +0,0 @@ -/* - * 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 deleted file mode 100644 index 4d46f9f..0000000 --- a/components/protocol_examples_tapif_io/linux/tapio.c +++ /dev/null @@ -1,134 +0,0 @@ -/* - * 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 deleted file mode 100644 index a7fce1e..0000000 --- a/components/protocol_examples_tapif_io/linux_connect.c +++ /dev/null @@ -1,86 +0,0 @@ -/* - * 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/make_tap_netif b/components/protocol_examples_tapif_io/make_tap_netif deleted file mode 100755 index 3857edb..0000000 --- a/components/protocol_examples_tapif_io/make_tap_netif +++ /dev/null @@ -1,4 +0,0 @@ -#!/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 0b405ca..378c1f0 100644 --- a/components/web_server/web_server.c +++ b/components/web_server/web_server.c @@ -3,12 +3,21 @@ #include "ping.h" #include "esp_http_server.h" #include "esp_log.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include #include +#include +#include + +#define PING_FORM_MAX_LENGTH (DEVICE_LIST_MAX_SIZE * (4 + ETH_HWADDR_LEN * 2 + 1) - 1) static const char *TAG = "web_server"; static esp_err_t main_page_handler(httpd_req_t *req); static esp_err_t ping_handler(httpd_req_t *req); +static esp_err_t redirect_to_main(httpd_req_t *req); +static void format_last_seen(TickType_t last_seen, char *text, size_t size); static esp_err_t send_page_header(httpd_req_t *req); static esp_err_t send_device_table(httpd_req_t *req); static esp_err_t send_device_row(httpd_req_t *req, const device_info_t *device); @@ -18,7 +27,7 @@ static httpd_handle_t start_webserver(void); static void register_http_handlers(httpd_handle_t server); static const httpd_uri_t main_uri = {.uri = "/", .method = HTTP_GET, .handler = main_page_handler, .user_ctx = NULL}; -static const httpd_uri_t ping_uri = {.uri = "/ping", .method = HTTP_GET, .handler = ping_handler, .user_ctx = NULL}; +static const httpd_uri_t main_post_uri = {.uri = "/", .method = HTTP_POST, .handler = ping_handler, .user_ctx = NULL}; void web_server_init(void) { @@ -55,18 +64,22 @@ static void register_http_handlers(httpd_handle_t server) ESP_LOGE(TAG, "Failed to register main URI"); } - err = httpd_register_uri_handler(server, &ping_uri); + err = httpd_register_uri_handler(server, &main_post_uri); if (err != ESP_OK) { - ESP_LOGE(TAG, "Failed to register ping URI"); + ESP_LOGE(TAG, "Failed to register main POST URI"); } } static esp_err_t main_page_handler(httpd_req_t *req) { - httpd_resp_set_type(req, "text/html"); + esp_err_t err = httpd_resp_set_type(req, "text/html; charset=utf-8"); + if (err != ESP_OK) + return err; - esp_err_t err; + err = httpd_resp_set_hdr(req, "Cache-Control", "no-store"); + if (err != ESP_OK) + return err; err = send_page_header(req); if (err != ESP_OK) @@ -74,7 +87,7 @@ static esp_err_t main_page_handler(httpd_req_t *req) ESP_LOGE(TAG, "Page header isn't created"); return err; } - + err = send_device_table(req); if (err != ESP_OK) { @@ -88,7 +101,7 @@ static esp_err_t main_page_handler(httpd_req_t *req) ESP_LOGE(TAG, "Page footer isn't created"); return err; } - + return httpd_resp_sendstr_chunk(req, NULL); } @@ -97,37 +110,131 @@ static esp_err_t send_page_header(httpd_req_t *req) return httpd_resp_sendstr_chunk( req, "" - "" + "" "" "" + "" "ARP Monitoring" + "" "" - "" + "
" "

ARP Monitoring

" + "

Отслеживание устройств в Вашей локальной сети с помощью ARP. Отслеживайте и диагностируйте подключенные устройства в реальном времени.

" ); } static esp_err_t send_device_table(httpd_req_t *req) { esp_err_t err; + const size_t count = device_list_count(); + size_t online = 0, total = 0; + for (size_t index = 0; index < count; index++) + { + const device_info_t *device = device_list_get_by_index(index); + if (device != NULL) + { + total++; + if (device->active) + online++; + } + } + + static const char *const summary_parts[] = { + "
" + "
" + "

Всего устройств

", + "Обнаружено по ARP
" + "
" + "

Online

", + "Сейчас онлайн
" + "
" + "

Offline

", + "Не ответили
" + "

Устройства (", + ")

ARP / IPv4
" + }; + const size_t values[] = {total, online, total - online, total}; + for (size_t index = 0; index < sizeof(values) / sizeof(values[0]); index++) + { + err = httpd_resp_sendstr_chunk(req, summary_parts[index]); + if (err != ESP_OK) + return err; + char number[32]; + int length = snprintf(number, sizeof(number), "%zu", values[index]); + if (length < 0 || (size_t)length >= sizeof(number)) + return ESP_FAIL; + err = httpd_resp_sendstr_chunk(req, number); + if (err != ESP_OK) + return err; + } + err = httpd_resp_sendstr_chunk(req, summary_parts[4]); + if (err != ESP_OK) + return err; err = httpd_resp_sendstr_chunk( req, - "
" - "" - "" - "" - "" - "" - "" - "" - "" + "" + "
SelectIP addressMAC addressLast seenStatus
" + "" + "" + "" + "" + "" + "" + "" ); if (err != ESP_OK) return err; - for (size_t index = 0; index < device_list_count(); index++) + if (total == 0) + { + err = httpd_resp_sendstr_chunk(req, + ""); + if (err != ESP_OK) + return err; + } + + for (size_t index = 0; index < count; index++) { const device_info_t *current_device; current_device = device_list_get_by_index(index); @@ -142,30 +249,35 @@ static esp_err_t send_device_table(httpd_req_t *req) } return httpd_resp_sendstr_chunk( - req, - "
ВыборIP-адресMAC-адресПоследний ARPСтатус
Устройства пока не обнаружены.
Ожидаем входящие ARP-пакеты.
" - "
" - "" - "
" + req, + "" + "

Выберите устройства для проверки доступности.

" + "
" + "
" + "

Последний ARP — время с последнего ARP-пакета устройства. Значения пересчитываются при загрузке страницы.

" + "

Статус обновляется при получении ARP и проверке Ping; постоянная проверка доступности не выполняется.

" ); } static esp_err_t send_device_row(httpd_req_t *req, const device_info_t *device) { - char html[256]; + char html[640]; + char last_seen[96]; - snprintf( + format_last_seen(device->last_seen, last_seen, sizeof(last_seen)); + + int length = snprintf( html, sizeof(html), "" "" - "" + "" "" - "%u.%u.%u.%u" - "%02X:%02X:%02X:%02X:%02X:%02X" - "%lu" + "%u.%u.%u.%u" + "%02X:%02X:%02X:%02X:%02X:%02X" "%s" + "%s" "", device->mac.addr[0], @@ -180,6 +292,11 @@ static esp_err_t send_device_row(httpd_req_t *req, const device_info_t *device) ip4_addr3(&device->ip), ip4_addr4(&device->ip), + ip4_addr1(&device->ip), + ip4_addr2(&device->ip), + ip4_addr3(&device->ip), + ip4_addr4(&device->ip), + device->mac.addr[0], device->mac.addr[1], device->mac.addr[2], @@ -187,31 +304,106 @@ static esp_err_t send_device_row(httpd_req_t *req, const device_info_t *device) device->mac.addr[4], device->mac.addr[5], - (unsigned long)device->last_seen, + last_seen, + device->active ? "online" : "offline", device->active ? "Online" : "Offline" ); + if (length < 0 || (size_t)length >= sizeof(html)) + return ESP_FAIL; + return httpd_resp_sendstr_chunk(req, html); } +static void format_last_seen(TickType_t last_seen, char *text, size_t size) +{ + TickType_t elapsed = (TickType_t)(xTaskGetTickCount() - last_seen); + uint64_t seconds = (uint64_t)elapsed / configTICK_RATE_HZ; + + if (seconds == 0) + snprintf(text, size, "только что"); + else if (seconds < 60) + snprintf(text, size, "%" PRIu64 " с назад", seconds); + else if (seconds < 3600) + snprintf(text, size, "%" PRIu64 " мин %" PRIu64 " с назад", + seconds / 60, seconds % 60); + else if (seconds < 86400) + snprintf(text, size, "%" PRIu64 " ч %" PRIu64 " мин назад", + seconds / 3600, (seconds % 3600) / 60); + else + snprintf(text, size, "%" PRIu64 " д %" PRIu64 " ч назад", + seconds / 86400, (seconds % 86400) / 3600); +} + static esp_err_t send_page_footer(httpd_req_t *req) { return httpd_resp_sendstr_chunk( req, - "" + "
" "" ); } static esp_err_t ping_handler(httpd_req_t *req) { - char query[256]; + if (req->content_len == 0) + return redirect_to_main(req); - if (httpd_req_get_url_query_str(req, query, sizeof(query)) == ESP_OK) + if (req->content_len > PING_FORM_MAX_LENGTH) { - ping_selected_devices(query); + httpd_resp_set_status(req, "413 Payload Too Large"); + httpd_resp_sendstr(req, "Too many selected devices"); + return ESP_FAIL; } - return main_page_handler(req); + char *body = malloc(req->content_len + 1); + if (body == NULL) + { + httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Not enough memory"); + return ESP_FAIL; + } + + size_t received = 0; + while (received < req->content_len) + { + int result = httpd_req_recv(req, body + received, req->content_len - received); + if (result <= 0) + { + free(body); + if (result == HTTPD_SOCK_ERR_TIMEOUT) + httpd_resp_send_err(req, HTTPD_408_REQ_TIMEOUT, "Request timed out"); + return ESP_FAIL; + } + received += (size_t)result; + } + + if (memchr(body, '\0', received) != NULL) + { + free(body); + return httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Invalid form data"); + } + + body[received] = '\0'; + ping_selected_devices(body); + free(body); + + return redirect_to_main(req); } + +static esp_err_t redirect_to_main(httpd_req_t *req) +{ + esp_err_t err = httpd_resp_set_status(req, "303 See Other"); + if (err != ESP_OK) + return err; + + err = httpd_resp_set_hdr(req, "Location", "/"); + if (err != ESP_OK) + return err; + + err = httpd_resp_set_hdr(req, "Cache-Control", "no-store"); + if (err != ESP_OK) + return err; + + return httpd_resp_sendstr(req, ""); +} \ No newline at end of file diff --git a/dependencies.lock b/dependencies.lock new file mode 100644 index 0000000..5fca785 --- /dev/null +++ b/dependencies.lock @@ -0,0 +1,16 @@ +dependencies: + idf: + source: + type: idf + version: 6.0.2 + protocol_examples_tapif_io: + dependencies: [] + source: + path: ../../esp-idf/examples/common_components/protocol_examples_tapif_io + type: local + version: '*' +direct_dependencies: +- protocol_examples_tapif_io +manifest_hash: 33a56fe4b18b5bca5e7e9903993768ba91305bb35b493fb3746cfbe293644f19 +target: linux +version: 3.0.0 diff --git a/docs/web-interface.png b/docs/web-interface.png index 1035dcb..36f8ec5 100644 Binary files a/docs/web-interface.png and b/docs/web-interface.png differ diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt index 86ec110..4527943 100644 --- a/main/CMakeLists.txt +++ b/main/CMakeLists.txt @@ -6,10 +6,13 @@ set(requires web_server esp_netif esp_event + nvs_flash ) if("${target}" STREQUAL "linux") list(APPEND requires protocol_examples_tapif_io) +else() + list(APPEND requires protocol_examples_common) endif() idf_component_register( diff --git a/main/idf_component.yml b/main/idf_component.yml new file mode 100644 index 0000000..d51e66f --- /dev/null +++ b/main/idf_component.yml @@ -0,0 +1,9 @@ +dependencies: + protocol_examples_tapif_io: + path: ${IDF_PATH}/examples/common_components/protocol_examples_tapif_io + rules: + - if: "target in [linux]" + protocol_examples_common: + path: ${IDF_PATH}/examples/common_components/protocol_examples_common + rules: + - if: "target not in [linux]" diff --git a/main/main.c b/main/main.c index 83f3992..94a1a14 100644 --- a/main/main.c +++ b/main/main.c @@ -5,26 +5,22 @@ #include "esp_event.h" #include "esp_netif.h" #include "esp_log.h" - -#ifdef CONFIG_IDF_TARGET_LINUX +#include "nvs_flash.h" #include "protocol_examples_common.h" -#endif static const char *TAG = "main"; void app_main(void) { + ESP_ERROR_CHECK(nvs_flash_init()); + ESP_ERROR_CHECK(esp_netif_init()); + ESP_ERROR_CHECK(esp_event_loop_create_default()); + 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 + ESP_LOGI(TAG, "Network interface initialized"); web_server_init(); } diff --git a/components/lwip/lwip/src/core/ipv4/etharp.c b/patches/lwip/etharp.c similarity index 100% rename from components/lwip/lwip/src/core/ipv4/etharp.c rename to patches/lwip/etharp.c diff --git a/patches/lwip/prepare.py b/patches/lwip/prepare.py new file mode 100644 index 0000000..47375f4 --- /dev/null +++ b/patches/lwip/prepare.py @@ -0,0 +1,75 @@ +"""Prepare the known-working lwIP component layout for ESP-IDF v6.0.2.""" +import filecmp +from pathlib import Path +import shutil +import sys + + +def replace_once(text, old, new, label): + if text.count(old) != 1: + raise RuntimeError(f"Unexpected {label}; expected ESP-IDF v6.0.2 sources") + return text.replace(old, new, 1) + + +def write_changed(path, data): + path.parent.mkdir(parents=True, exist_ok=True) + if not path.exists() or path.read_bytes() != data: + path.write_bytes(data) + + +def prepare(source, destination, arp): + source, destination, arp = source.resolve(), destination.resolve(), arp.resolve() + if not arp.is_file(): + raise RuntimeError(f"Missing modified ARP source: {arp}") + if source == destination or source in destination.parents or destination in source.parents: + raise RuntimeError("Source and generated directories must be separate") + if any(c in arp.as_posix() for c in ('"', ';', '$')): + raise RuntimeError("ARP path contains characters that require CMake escaping") + + cmake = (source / "CMakeLists.txt").read_text() + cmake = replace_once(cmake, '"lwip/src/core/ipv4/etharp.c"', + f'"{arp.as_posix()}"', "lwIP CMakeLists.txt") + cmake = replace_once(cmake, + 'if(NOT ${target} STREQUAL "linux")\n set(priv_requires vfs)\nendif()', + 'set(priv_requires)\n\nif(NOT ${target} STREQUAL "linux")\n list(APPEND priv_requires vfs)\nendif()', + "lwIP dependencies") + sockets_path = "lwip/src/include/lwip/sockets.h" + sockets = (source / sockets_path).read_text() + if "#ifndef SIOCATMARK" not in sockets: + line = "#define SIOCATMARK _IOR('s', 7, unsigned long) /* at oob mark? */" + sockets = replace_once(sockets, line, "#endif\n#ifndef SIOCATMARK\n" + line, + "SIOCATMARK definition") + fcntl_path = "port/linux/include/sys/fcntl.h" + fcntl = (source / fcntl_path).read_text() + if "#define F_GETFD" not in fcntl: + fcntl = fcntl.rstrip("\n") + "\n\n#ifndef F_GETFD\n#define F_GETFD 1\n#endif\n" + changed = {"CMakeLists.txt": cmake.encode(), sockets_path: sockets.encode(), + fcntl_path: fcntl.encode()} + + # Validate all substitutions before writing. Do not copy or compile stock ARP. + destination.mkdir(parents=True, exist_ok=True) + for path in source.rglob("*"): + relative = path.relative_to(source) + if ".git" in relative.parts or not path.is_file(): + continue + name = relative.as_posix() + if name in changed or name == "lwip/src/core/ipv4/etharp.c": + continue + target = destination / relative + target.parent.mkdir(parents=True, exist_ok=True) + if not target.is_file() or not filecmp.cmp(path, target, shallow=False): + shutil.copy2(path, target) + for name, data in changed.items(): + write_changed(destination / name, data) + stock_arp = destination / "lwip/src/core/ipv4/etharp.c" + if stock_arp.exists(): + stock_arp.unlink() + + +if __name__ == "__main__": + try: + if len(sys.argv) != 4: + raise RuntimeError("Usage: prepare.py SDK_LWIP BUILD_LWIP PATCHED_ETHARP") + prepare(*(Path(value) for value in sys.argv[1:])) + except (OSError, RuntimeError) as error: + sys.exit(str(error)) diff --git a/components/protocol_examples_tapif_io/lwip/tapif.c b/patches/protocol_examples_tapif_io/tapif.c similarity index 99% rename from components/protocol_examples_tapif_io/lwip/tapif.c rename to patches/protocol_examples_tapif_io/tapif.c index a96ece6..d70ff08 100644 --- a/components/protocol_examples_tapif_io/lwip/tapif.c +++ b/patches/protocol_examples_tapif_io/tapif.c @@ -14,6 +14,7 @@ #include "esp_netif.h" #include "esp_netif_net_stack.h" +#include "tapif.h" #define IFNAME0 't' #define IFNAME1 'p' diff --git a/components/protocol_examples_tapif_io/lwip/tapif.h b/patches/protocol_examples_tapif_io/tapif.h similarity index 96% rename from components/protocol_examples_tapif_io/lwip/tapif.h rename to patches/protocol_examples_tapif_io/tapif.h index aae29e7..e84b51c 100644 --- a/components/protocol_examples_tapif_io/lwip/tapif.h +++ b/patches/protocol_examples_tapif_io/tapif.h @@ -6,6 +6,7 @@ * SPDX-FileContributor: 2022-2023 Espressif Systems (Shanghai) CO LTD */ #pragma once +#include "esp_err.h" #include "lwip/esp_netif_net_stack.h" /**