Подключены ESP32|Linux, обновлен интерфейс
This commit is contained in:
Executable → Regular
+31
-73
@@ -1,117 +1,75 @@
|
|||||||
# macOS
|
# OS metadata
|
||||||
.DS_Store
|
.DS_Store
|
||||||
.AppleDouble
|
.AppleDouble
|
||||||
.LSOverride
|
.LSOverride
|
||||||
|
|
||||||
# Directory metadata
|
|
||||||
.directory
|
.directory
|
||||||
|
Thumbs.db
|
||||||
|
ehthumbs.db
|
||||||
|
Desktop.ini
|
||||||
|
*:Zone.Identifier
|
||||||
|
|
||||||
# Temporary files
|
# Temporary files and logs
|
||||||
*~
|
*~
|
||||||
*.swp
|
*.swp
|
||||||
*.swo
|
*.swo
|
||||||
*.bak
|
*.bak
|
||||||
*.tmp
|
*.tmp
|
||||||
|
|
||||||
# Log files
|
|
||||||
*.log
|
*.log
|
||||||
|
|
||||||
# Build artifacts and directories
|
# Build outputs
|
||||||
**/build/
|
**/build/
|
||||||
build/
|
/build-esp32/
|
||||||
*.o
|
*.o
|
||||||
*.a
|
*.a
|
||||||
*.out
|
*.out
|
||||||
*.exe # For any host-side utilities compiled on Windows
|
*.exe
|
||||||
|
|
||||||
# ESP-IDF specific build outputs
|
|
||||||
*.bin
|
*.bin
|
||||||
*.elf
|
*.elf
|
||||||
*.map
|
*.map
|
||||||
flasher_args.json # Generated in build directory
|
flasher_args.json
|
||||||
sdkconfig.old
|
|
||||||
sdkconfig
|
|
||||||
|
|
||||||
# ESP-IDF dependencies
|
# Local ESP-IDF configurations
|
||||||
# For older versions or manual component management
|
/sdkconfig
|
||||||
|
/sdkconfig.old
|
||||||
|
/sdkconfig.esp32
|
||||||
|
/sdkconfig.esp32.old
|
||||||
|
|
||||||
|
# Dependencies and locally installed tools
|
||||||
/components/.idf/
|
/components/.idf/
|
||||||
**/components/.idf/
|
**/components/.idf/
|
||||||
# For modern ESP-IDF component manager
|
|
||||||
managed_components/
|
managed_components/
|
||||||
# If ESP-IDF tools are installed/referenced locally to the project
|
|
||||||
.espressif/
|
.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
|
# CMake generated files
|
||||||
CMakeCache.txt
|
CMakeCache.txt
|
||||||
CMakeFiles/
|
CMakeFiles/
|
||||||
cmake_install.cmake
|
cmake_install.cmake
|
||||||
install_manifest.txt
|
install_manifest.txt
|
||||||
CTestTestfile.cmake
|
CTestTestfile.cmake
|
||||||
|
compile_commands.json
|
||||||
|
|
||||||
# Python environment files
|
# Python caches and environments
|
||||||
*.pyc
|
*.pyc
|
||||||
*.pyo
|
*.pyo
|
||||||
*.pyd
|
*.pyd
|
||||||
__pycache__/
|
__pycache__/
|
||||||
*.egg-info/
|
*.egg-info/
|
||||||
dist/
|
dist/
|
||||||
|
|
||||||
# Virtual environment folders
|
|
||||||
venv/
|
venv/
|
||||||
.venv/
|
.venv/
|
||||||
env/
|
env/
|
||||||
|
|
||||||
# Language Servers
|
# Language servers
|
||||||
.clangd/
|
.clangd/
|
||||||
.ccls-cache/
|
.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
|
|
||||||
|
|||||||
+3
-2
@@ -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)
|
cmake_minimum_required(VERSION 3.22)
|
||||||
|
|
||||||
set(COMPONENTS main)
|
set(COMPONENTS main)
|
||||||
|
|
||||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||||
|
include("${CMAKE_CURRENT_LIST_DIR}/cmake/prepare_lwip.cmake")
|
||||||
project(monitoring)
|
project(monitoring)
|
||||||
|
|
||||||
|
include("${CMAKE_CURRENT_LIST_DIR}/cmake/tapif_patch.cmake")
|
||||||
|
|||||||
@@ -7,24 +7,30 @@
|
|||||||
|
|
||||||
Рабочая конфигурация проверена на **ESP-IDF v6.0.2** с использованием целевой
|
Рабочая конфигурация проверена на **ESP-IDF v6.0.2** с использованием целевой
|
||||||
платформы Linux и TAP-интерфейса без физической платы ESP32.
|
платформы 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/arp_monitor` | Принимает IP/MAC из lwIP через очередь FreeRTOS |
|
||||||
| `components/device_list` | Хранит до 64 найденных устройств в RAM |
|
| `components/device_list` | Хранит до 64 найденных устройств в RAM |
|
||||||
| `components/web_server` | Отдаёт HTML-таблицу устройств и обрабатывает `/ping` |
|
| `components/web_server` | GET `/` показывает таблицу; POST `/` запускает Ping с возвратом на главную |
|
||||||
| `components/ping` | Запускает стандартный `esp_ping` для выбранных узлов |
|
| `components/ping` | Запускает стандартный `esp_ping` для выбранных узлов |
|
||||||
| `components/protocol_examples_tapif_io` | Подключает lwIP к Linux TAP без ESP32 |
|
| `patches/protocol_examples_tapif_io` | Исправленные `tapif.c` и `tapif.h` для TAP-компонента ESP-IDF v6.0.2 |
|
||||||
| `main/main.c` | Отвечает за инициализацию компонентов |
|
| `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` и вызывает
|
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()` вызывает
|
3. После разбора ARP-заголовка `etharp_input()` вызывает
|
||||||
`arp_monitor_report()` с IP- и MAC-адресом отправителя.
|
`arp_monitor_report()` с IP- и MAC-адресом отправителя.
|
||||||
4. `arp_monitor_report()` копирует данные в неблокирующую очередь FreeRTOS.
|
4. `arp_monitor_report()` копирует данные в неблокирующую очередь FreeRTOS.
|
||||||
@@ -37,7 +43,10 @@
|
|||||||
|
|
||||||
## Примечание
|
## Примечание
|
||||||
|
|
||||||
Текущая версия полностью проверена на целевой платформе Linux с использованием
|
Проект проверен в работе на Linux/TAP. Прошивка для ESP32 успешно собирается,
|
||||||
TAP-интерфейса без физической платы. Для запуска на ESP32 необходимо
|
но на самой плате пока не запускалась.
|
||||||
дополнительно реализовать инициализацию Wi-Fi и подключение устройства к
|
|
||||||
локальной сети.
|
Изменённый `etharp.c` хранится в `patches/lwip`. Библиотека lwIP автоматически
|
||||||
|
подготавливается из ESP-IDF v6.0.2 в каталоге сборки. Хранить её в `components`
|
||||||
|
или вручную восстанавливать после клонирования не нужно.
|
||||||
|
TAP-компонент берётся из ESP-IDF с исправлениями из `patches/protocol_examples_tapif_io`.
|
||||||
|
|||||||
@@ -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")
|
||||||
@@ -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()
|
||||||
@@ -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()
|
|
||||||
@@ -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
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
@@ -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
|
|
||||||
@@ -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);
|
|
||||||
@@ -1,134 +0,0 @@
|
|||||||
/*
|
|
||||||
* SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: Apache-2.0
|
|
||||||
*/
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
#include <string.h>
|
|
||||||
#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 <sys/socket.h>
|
|
||||||
#include <fcntl.h>
|
|
||||||
|
|
||||||
#include <sys/ioctl.h>
|
|
||||||
#include <sys/types.h>
|
|
||||||
#include <sys/ioctl.h>
|
|
||||||
#include <lwip/sys.h>
|
|
||||||
#include "errno.h"
|
|
||||||
|
|
||||||
#include <linux/if.h>
|
|
||||||
#include <linux/if_tun.h>
|
|
||||||
|
|
||||||
#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;
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
@@ -3,12 +3,21 @@
|
|||||||
#include "ping.h"
|
#include "ping.h"
|
||||||
#include "esp_http_server.h"
|
#include "esp_http_server.h"
|
||||||
#include "esp_log.h"
|
#include "esp_log.h"
|
||||||
|
#include "freertos/FreeRTOS.h"
|
||||||
|
#include "freertos/task.h"
|
||||||
|
#include <inttypes.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#define PING_FORM_MAX_LENGTH (DEVICE_LIST_MAX_SIZE * (4 + ETH_HWADDR_LEN * 2 + 1) - 1)
|
||||||
|
|
||||||
static const char *TAG = "web_server";
|
static const char *TAG = "web_server";
|
||||||
|
|
||||||
static esp_err_t main_page_handler(httpd_req_t *req);
|
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 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_page_header(httpd_req_t *req);
|
||||||
static esp_err_t send_device_table(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);
|
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 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 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)
|
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");
|
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)
|
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)
|
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);
|
err = send_page_header(req);
|
||||||
if (err != ESP_OK)
|
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");
|
ESP_LOGE(TAG, "Page header isn't created");
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
err = send_device_table(req);
|
err = send_device_table(req);
|
||||||
if (err != ESP_OK)
|
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");
|
ESP_LOGE(TAG, "Page footer isn't created");
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
return httpd_resp_sendstr_chunk(req, NULL);
|
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(
|
return httpd_resp_sendstr_chunk(
|
||||||
req,
|
req,
|
||||||
"<!DOCTYPE html>"
|
"<!DOCTYPE html>"
|
||||||
"<html>"
|
"<html lang=\"ru\">"
|
||||||
"<head>"
|
"<head>"
|
||||||
"<meta charset=\"UTF-8\">"
|
"<meta charset=\"UTF-8\">"
|
||||||
|
"<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">"
|
||||||
"<title>ARP Monitoring</title>"
|
"<title>ARP Monitoring</title>"
|
||||||
|
"<style>"
|
||||||
|
":root{color-scheme:light;--ink:#142139;--muted:#63738c;--line:#e6ecf3;--blue:#1463ef}"
|
||||||
|
"*{box-sizing:border-box}"
|
||||||
|
"body{margin:0;background:#f4f7fb;color:var(--ink);font-family:system-ui,-apple-system,'Segoe UI',sans-serif;font-size:14px;line-height:1.5}"
|
||||||
|
"main{max-width:1280px;margin:0 auto;padding:44px 28px 28px}"
|
||||||
|
"h1{margin:0;font-size:36px;line-height:1.2;letter-spacing:-1.2px;color:#0e1729}"
|
||||||
|
".intro{margin:12px 0 28px;color:var(--muted);font-size:15px}"
|
||||||
|
".summary{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:18px;margin-bottom:26px}"
|
||||||
|
".card{display:flex;align-items:center;gap:20px;padding:24px;background:#fff;border:1px solid var(--line);border-radius:14px;box-shadow:0 3px 14px #23395d04}"
|
||||||
|
".icon{display:flex;align-items:center;justify-content:center;flex:none;width:54px;height:54px;border-radius:13px;background:#eaf1ff;color:var(--blue)}"
|
||||||
|
".icon svg{width:27px;height:27px;fill:none;stroke:currentColor;stroke-width:1.8;stroke-linecap:round;stroke-linejoin:round}"
|
||||||
|
".icon.green{background:#e8f8ee;color:#16803d}.icon.red{background:#fff0f0;color:#ca3540}"
|
||||||
|
".card-label{margin:0;color:#485a73;font-weight:500}.card-value{display:block;font-size:32px;font-weight:700;line-height:1.2;letter-spacing:-.7px;margin:3px 0}"
|
||||||
|
".card-note{font-size:12px;color:var(--muted)}"
|
||||||
|
".panel{background:#fff;border:1px solid var(--line);border-radius:14px;box-shadow:0 4px 20px #23395d05;overflow:hidden}"
|
||||||
|
".panel-head{padding:22px 24px;display:flex;align-items:center;justify-content:space-between;gap:12px}"
|
||||||
|
"h2{font-size:19px;letter-spacing:-.4px;margin:0}.count{margin-left:8px;color:var(--muted);font-weight:500}"
|
||||||
|
".tag{color:#576b87;background:#f4f7fb;border:1px solid var(--line);padding:4px 10px;border-radius:6px;font-size:12px;white-space:nowrap}"
|
||||||
|
".table-scroll{overflow-x:auto}table{width:100%;border-collapse:collapse;text-align:left;white-space:nowrap}"
|
||||||
|
"th{background:#f8fafd;color:#5c6c83;font-size:12px;font-weight:600;padding:13px 24px;border-top:1px solid var(--line)}"
|
||||||
|
"td{padding:19px 24px;border-top:1px solid var(--line);color:#43536c}"
|
||||||
|
"th:first-child,td:first-child{width:76px;padding-right:12px}"
|
||||||
|
"tbody tr:hover{background:#f8fbff}tbody tr:has(input:checked){background:#f0f6ff}"
|
||||||
|
"input[type=checkbox]{width:18px;height:18px;margin:0;vertical-align:middle;accent-color:var(--blue);cursor:pointer}"
|
||||||
|
".address{font-weight:600;color:#1b2c47;font-variant-numeric:tabular-nums}"
|
||||||
|
".mac{font-family:ui-monospace,SFMono-Regular,Consolas,monospace;font-size:13px}"
|
||||||
|
".status{display:inline-flex;align-items:center;gap:7px;border-radius:20px;padding:4px 11px;font-size:12px;font-weight:600}"
|
||||||
|
".status:before{content:'';width:7px;height:7px;border-radius:50%;background:currentColor}"
|
||||||
|
".online{background:#e7f8ec;color:#18763a}.offline{background:#ffeded;color:#ba303b}"
|
||||||
|
".panel-foot{border-top:1px solid var(--line);padding:18px 24px;display:flex;align-items:center;justify-content:space-between;gap:18px}"
|
||||||
|
".hint{margin:0;color:var(--muted);font-size:13px}"
|
||||||
|
"button{display:inline-flex;align-items:center;justify-content:center;gap:10px;border:1px solid #1059db;border-radius:8px;background:var(--blue);color:white;padding:11px 19px;line-height:1.5;font-family:inherit;font-size:14px;font-weight:600;cursor:pointer;box-shadow:0 3px 7px #1463ef20;white-space:nowrap}"
|
||||||
|
"button:hover{background:#0c53d3}button:active{transform:translateY(1px)}"
|
||||||
|
"button svg{width:16px;height:16px;fill:currentColor}"
|
||||||
|
"input:focus-visible,button:focus-visible,.table-scroll:focus-visible{outline:3px solid #87b4ff;outline-offset:4px}"
|
||||||
|
".notes{margin:18px 4px 0;color:var(--muted);font-size:12px;line-height:1.8}.notes p{margin:0}"
|
||||||
|
".empty{text-align:center;padding:44px 24px;white-space:normal;color:var(--muted)}"
|
||||||
|
"@media(max-width:760px){main{padding:28px 16px}h1{font-size:29px}.intro{font-size:14px}.summary{gap:10px}.card{padding:16px;gap:10px}.icon{display:none}.card-value{font-size:27px}.card-note{font-size:11px}.panel-head{padding:18px}.panel-foot{padding:18px;flex-wrap:wrap}th,td{padding-left:18px;padding-right:18px}}"
|
||||||
|
"@media(max-width:420px){.summary{gap:8px}.card{padding:12px 9px}.card-label{font-size:12px}.card-note{display:none}.panel-foot button{width:100%}.tag{display:none}}"
|
||||||
|
"</style>"
|
||||||
"</head>"
|
"</head>"
|
||||||
"<body>"
|
"<body><main>"
|
||||||
"<h1>ARP Monitoring</h1>"
|
"<h1>ARP Monitoring</h1>"
|
||||||
|
"<p class=\"intro\">Отслеживание устройств в Вашей локальной сети с помощью ARP. Отслеживайте и диагностируйте подключенные устройства в реальном времени.</p>"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
static esp_err_t send_device_table(httpd_req_t *req)
|
static esp_err_t send_device_table(httpd_req_t *req)
|
||||||
{
|
{
|
||||||
esp_err_t err;
|
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[] = {
|
||||||
|
"<section class=\"summary\" aria-label=\"Сводка устройств\">"
|
||||||
|
"<div class=\"card\"><span class=\"icon\" aria-hidden=\"true\"><svg viewBox=\"0 0 24 24\"><rect x=\"3\" y=\"4\" width=\"18\" height=\"13\" rx=\"2\"/><path d=\"M12 17v4m-4 0h8\"/></svg></span>"
|
||||||
|
"<div><p class=\"card-label\">Всего устройств</p><strong class=\"card-value\">",
|
||||||
|
"</strong><span class=\"card-note\">Обнаружено по ARP</span></div></div>"
|
||||||
|
"<div class=\"card\"><span class=\"icon green\" aria-hidden=\"true\"><svg viewBox=\"0 0 24 24\"><path d=\"M3 9a14 14 0 0 1 18 0M6 12a9 9 0 0 1 12 0M9 15a5 5 0 0 1 6 0\"/><circle cx=\"12\" cy=\"19\" r=\"1\"/></svg></span>"
|
||||||
|
"<div><p class=\"card-label\">Online</p><strong class=\"card-value\">",
|
||||||
|
"</strong><span class=\"card-note\">Сейчас онлайн</span></div></div>"
|
||||||
|
"<div class=\"card\"><span class=\"icon red\" aria-hidden=\"true\"><svg viewBox=\"0 0 24 24\"><path d=\"m3 3 18 18M9 6a14 14 0 0 1 12 3M3 9l2-1m9 3a9 9 0 0 1 4 1M6 12l2-1m1 4a5 5 0 0 1 6 0\"/><circle cx=\"12\" cy=\"19\" r=\"1\"/></svg></span>"
|
||||||
|
"<div><p class=\"card-label\">Offline</p><strong class=\"card-value\">",
|
||||||
|
"</strong><span class=\"card-note\">Не ответили</span></div></div></section>"
|
||||||
|
"<section class=\"panel\" aria-labelledby=\"devices-title\"><div class=\"panel-head\"><h2 id=\"devices-title\">Устройства <span class=\"count\">(",
|
||||||
|
")</span></h2><span class=\"tag\">ARP / IPv4</span></div>"
|
||||||
|
};
|
||||||
|
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(
|
err = httpd_resp_sendstr_chunk(
|
||||||
req,
|
req,
|
||||||
"<form action=\"/ping\" method=\"get\">"
|
"<form action=\"/\" method=\"post\">"
|
||||||
"<table border=\"1\">"
|
"<div class=\"table-scroll\" tabindex=\"0\" role=\"region\" aria-label=\"Список устройств\"><table>"
|
||||||
"<tr>"
|
"<thead><tr>"
|
||||||
"<th>Select</th>"
|
"<th scope=\"col\">Выбор</th>"
|
||||||
"<th>IP address</th>"
|
"<th scope=\"col\">IP-адрес</th>"
|
||||||
"<th>MAC address</th>"
|
"<th scope=\"col\">MAC-адрес</th>"
|
||||||
"<th>Last seen</th>"
|
"<th scope=\"col\">Последний ARP</th>"
|
||||||
"<th>Status</th>"
|
"<th scope=\"col\">Статус</th>"
|
||||||
"</tr>"
|
"</tr></thead><tbody>"
|
||||||
);
|
);
|
||||||
|
|
||||||
if (err != ESP_OK)
|
if (err != ESP_OK)
|
||||||
return err;
|
return err;
|
||||||
|
|
||||||
for (size_t index = 0; index < device_list_count(); index++)
|
if (total == 0)
|
||||||
|
{
|
||||||
|
err = httpd_resp_sendstr_chunk(req,
|
||||||
|
"<tr><td colspan=\"5\" class=\"empty\">Устройства пока не обнаружены.<br>Ожидаем входящие ARP-пакеты.</td></tr>");
|
||||||
|
if (err != ESP_OK)
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (size_t index = 0; index < count; index++)
|
||||||
{
|
{
|
||||||
const device_info_t *current_device;
|
const device_info_t *current_device;
|
||||||
current_device = device_list_get_by_index(index);
|
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(
|
return httpd_resp_sendstr_chunk(
|
||||||
req,
|
req,
|
||||||
"</table>"
|
"</tbody></table></div>"
|
||||||
"<br>"
|
"<div class=\"panel-foot\"><p class=\"hint\">Выберите устройства для проверки доступности.</p>"
|
||||||
"<input type=\"submit\" value=\"Ping selected devices\">"
|
"<button type=\"submit\"><svg viewBox=\"0 0 16 16\" aria-hidden=\"true\"><path d=\"M4 2v12l10-6z\"/></svg>Проверить выбранные (Ping)</button></div>"
|
||||||
"</form>"
|
"</form></section>"
|
||||||
|
"<div class=\"notes\"><p>Последний ARP — время с последнего ARP-пакета устройства. Значения пересчитываются при загрузке страницы.</p>"
|
||||||
|
"<p>Статус обновляется при получении ARP и проверке Ping; постоянная проверка доступности не выполняется.</p></div>"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
static esp_err_t send_device_row(httpd_req_t *req, const device_info_t *device)
|
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,
|
html,
|
||||||
sizeof(html),
|
sizeof(html),
|
||||||
|
|
||||||
"<tr>"
|
"<tr>"
|
||||||
"<td>"
|
"<td>"
|
||||||
"<input type=\"checkbox\" name=\"mac\" value=\"%02X%02X%02X%02X%02X%02X\">"
|
"<input type=\"checkbox\" name=\"mac\" value=\"%02X%02X%02X%02X%02X%02X\" aria-label=\"Выбрать %u.%u.%u.%u\">"
|
||||||
"</td>"
|
"</td>"
|
||||||
"<td>%u.%u.%u.%u</td>"
|
"<td class=\"address\">%u.%u.%u.%u</td>"
|
||||||
"<td>%02X:%02X:%02X:%02X:%02X:%02X</td>"
|
"<td class=\"mac\">%02X:%02X:%02X:%02X:%02X:%02X</td>"
|
||||||
"<td>%lu</td>"
|
|
||||||
"<td>%s</td>"
|
"<td>%s</td>"
|
||||||
|
"<td><span class=\"status %s\">%s</span></td>"
|
||||||
"</tr>",
|
"</tr>",
|
||||||
|
|
||||||
device->mac.addr[0],
|
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_addr3(&device->ip),
|
||||||
ip4_addr4(&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[0],
|
||||||
device->mac.addr[1],
|
device->mac.addr[1],
|
||||||
device->mac.addr[2],
|
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[4],
|
||||||
device->mac.addr[5],
|
device->mac.addr[5],
|
||||||
|
|
||||||
(unsigned long)device->last_seen,
|
last_seen,
|
||||||
|
|
||||||
|
device->active ? "online" : "offline",
|
||||||
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);
|
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)
|
static esp_err_t send_page_footer(httpd_req_t *req)
|
||||||
{
|
{
|
||||||
return httpd_resp_sendstr_chunk(
|
return httpd_resp_sendstr_chunk(
|
||||||
req,
|
req,
|
||||||
"</body>"
|
"</main></body>"
|
||||||
"</html>"
|
"</html>"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
static esp_err_t ping_handler(httpd_req_t *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, "");
|
||||||
|
}
|
||||||
@@ -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
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 202 KiB |
@@ -6,10 +6,13 @@ set(requires
|
|||||||
web_server
|
web_server
|
||||||
esp_netif
|
esp_netif
|
||||||
esp_event
|
esp_event
|
||||||
|
nvs_flash
|
||||||
)
|
)
|
||||||
|
|
||||||
if("${target}" STREQUAL "linux")
|
if("${target}" STREQUAL "linux")
|
||||||
list(APPEND requires protocol_examples_tapif_io)
|
list(APPEND requires protocol_examples_tapif_io)
|
||||||
|
else()
|
||||||
|
list(APPEND requires protocol_examples_common)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
idf_component_register(
|
idf_component_register(
|
||||||
|
|||||||
@@ -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]"
|
||||||
+6
-10
@@ -5,26 +5,22 @@
|
|||||||
#include "esp_event.h"
|
#include "esp_event.h"
|
||||||
#include "esp_netif.h"
|
#include "esp_netif.h"
|
||||||
#include "esp_log.h"
|
#include "esp_log.h"
|
||||||
|
#include "nvs_flash.h"
|
||||||
#ifdef CONFIG_IDF_TARGET_LINUX
|
|
||||||
#include "protocol_examples_common.h"
|
#include "protocol_examples_common.h"
|
||||||
#endif
|
|
||||||
|
|
||||||
static const char *TAG = "main";
|
static const char *TAG = "main";
|
||||||
|
|
||||||
void app_main(void)
|
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();
|
device_list_init();
|
||||||
arp_monitor_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_ERROR_CHECK(example_connect());
|
||||||
ESP_LOGI(TAG, "Linux network interface initialized");
|
ESP_LOGI(TAG, "Network interface initialized");
|
||||||
#else
|
|
||||||
ESP_LOGW(TAG, "Network initialization for the ESP32 target is not configured");
|
|
||||||
#endif
|
|
||||||
|
|
||||||
web_server_init();
|
web_server_init();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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))
|
||||||
+1
@@ -14,6 +14,7 @@
|
|||||||
|
|
||||||
#include "esp_netif.h"
|
#include "esp_netif.h"
|
||||||
#include "esp_netif_net_stack.h"
|
#include "esp_netif_net_stack.h"
|
||||||
|
#include "tapif.h"
|
||||||
|
|
||||||
#define IFNAME0 't'
|
#define IFNAME0 't'
|
||||||
#define IFNAME1 'p'
|
#define IFNAME1 'p'
|
||||||
+1
@@ -6,6 +6,7 @@
|
|||||||
* SPDX-FileContributor: 2022-2023 Espressif Systems (Shanghai) CO LTD
|
* SPDX-FileContributor: 2022-2023 Espressif Systems (Shanghai) CO LTD
|
||||||
*/
|
*/
|
||||||
#pragma once
|
#pragma once
|
||||||
|
#include "esp_err.h"
|
||||||
#include "lwip/esp_netif_net_stack.h"
|
#include "lwip/esp_netif_net_stack.h"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Reference in New Issue
Block a user