diff --git a/.github/workflows/examples_build-host-test.yml b/.github/workflows/examples_build-host-test.yml index 0a1cbbc1a6..02cee46344 100644 --- a/.github/workflows/examples_build-host-test.yml +++ b/.github/workflows/examples_build-host-test.yml @@ -14,13 +14,14 @@ jobs: strategy: matrix: idf_ver: ["latest", "release-v5.1", "release-v5.2", "release-v5.3", "release-v5.4"] + example: ["slip_custom_netif", "cslip_custom_netif"] include: - idf_ver: "latest" warning: "Warning: The smallest app partition is nearly full" runs-on: ubuntu-22.04 container: espressif/idf:${{ matrix.idf_ver }} env: - TARGET_TEST: examples/esp_netif/slip_custom_netif/ + TARGET_TEST: examples/esp_netif/${{ matrix.example }}/ TARGET_TEST_DIR: build_esp32c3_target steps: - name: Checkout esp-protocols @@ -41,7 +42,7 @@ jobs: zip -qur artifacts.zip ${TARGET_TEST_DIR} - uses: actions/upload-artifact@v4 with: - name: slip_target_${{ matrix.idf_ver }} + name: ${{ matrix.example }}_target_${{ matrix.idf_ver }} path: ${{ env.TARGET_TEST }}/artifacts.zip if-no-files-found: error @@ -71,22 +72,23 @@ jobs: if: | github.repository == 'espressif/esp-protocols' && ( contains(github.event.pull_request.labels.*.name, 'examples') || github.event_name == 'push' ) - name: Slip example target test + name: Netif example target test needs: build_all_examples strategy: matrix: idf_ver: ["release-v5.4", "latest"] + example: ["slip_custom_netif", "cslip_custom_netif"] runs-on: - self-hosted - modem env: - TARGET_TEST: examples/esp_netif/slip_custom_netif/ + TARGET_TEST: examples/esp_netif/${{ matrix.example }}/ TARGET_TEST_DIR: build_esp32c3_target steps: - uses: actions/checkout@v4 - uses: actions/download-artifact@v4 with: - name: slip_target_${{ matrix.idf_ver }} + name: ${{ matrix.example }}_target_${{ matrix.idf_ver }} path: ${{ env.TARGET_TEST }}/ci/ - name: Run Test working-directory: ${{ env.TARGET_TEST }} diff --git a/examples/esp_netif/cslip_custom_netif/CMakeLists.txt b/examples/esp_netif/cslip_custom_netif/CMakeLists.txt new file mode 100644 index 0000000000..9f05f8a59b --- /dev/null +++ b/examples/esp_netif/cslip_custom_netif/CMakeLists.txt @@ -0,0 +1,6 @@ +# The following lines of boilerplate have to be in your project's CMakeLists +# in this exact order for cmake to work correctly +cmake_minimum_required(VERSION 3.16) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(cslip_client) diff --git a/examples/esp_netif/cslip_custom_netif/README.md b/examples/esp_netif/cslip_custom_netif/README.md new file mode 100644 index 0000000000..d48612c97f --- /dev/null +++ b/examples/esp_netif/cslip_custom_netif/README.md @@ -0,0 +1,23 @@ +| Supported Targets | ESP32 | ESP32-C3 | ESP32-S2 | ESP32-S3 | +| ----------------- | ----- | -------- | -------- | -------- | + +# CSLIP device client (initial) + +This example mirrors the SLIP custom netif example, but prepares for CSLIP (Van Jacobson TCP/IP header compression over SLIP). In this initial version, the data path behaves like plain SLIP while exposing a minimal CSLIP-facing API and structure so compression can be added incrementally. + +It demonstrates creating a custom network interface and attaching it to `esp_netif` with an external component (`cslip_modem`) similar to the `slip_modem` used in the SLIP example. + +Notes: +- Compression is not yet implemented; packets are forwarded as standard SLIP. +- The structure allows later integration of VJ compression/decompression. + +## How to use + +Hardware and setup are identical to the SLIP example. You can point a Linux host to the serial port and use `slattach` with `-p slip` for initial testing. + +Build and flash: +- idf.py set-target +- idf.py menuconfig (configure Example Configuration) +- idf.py -p flash monitor + +Troubleshooting and behavior should match the SLIP example until compression is added. diff --git a/examples/esp_netif/cslip_custom_netif/components/cslip_modem/CMakeLists.txt b/examples/esp_netif/cslip_custom_netif/components/cslip_modem/CMakeLists.txt new file mode 100644 index 0000000000..511d2be757 --- /dev/null +++ b/examples/esp_netif/cslip_custom_netif/components/cslip_modem/CMakeLists.txt @@ -0,0 +1,7 @@ +# CSLIP Modem Component (initial, pass-through) + +idf_component_register( + SRCS "library/cslip_modem.c" "library/cslip_modem_netif.c" + INCLUDE_DIRS "include" + REQUIRES esp_netif driver +) diff --git a/examples/esp_netif/cslip_custom_netif/components/cslip_modem/include/cslip_modem.h b/examples/esp_netif/cslip_custom_netif/components/cslip_modem/include/cslip_modem.h new file mode 100644 index 0000000000..fe5d7aaae5 --- /dev/null +++ b/examples/esp_netif/cslip_custom_netif/components/cslip_modem/include/cslip_modem.h @@ -0,0 +1,72 @@ +/* + * SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include +#include + +#include "esp_netif.h" +#include "driver/uart.h" + +/** + * Minimal CSLIP-facing default netif config (mirrors SLIP example) + */ +#define ESP_NETIF_INHERENT_DEFAULT_CSLIP() \ + { \ + ESP_COMPILER_DESIGNATED_INIT_AGGREGATE_TYPE_EMPTY(mac) \ + ESP_COMPILER_DESIGNATED_INIT_AGGREGATE_TYPE_EMPTY(ip_info) \ + .get_ip_event = 0, \ + .lost_ip_event = 0, \ + .if_key = "CSLP_DEF", \ + .if_desc = "cslip", \ + .route_prio = 16, \ + .bridge_info = NULL \ +} + +extern const esp_netif_netstack_config_t *netstack_default_cslip; + +typedef struct cslip_modem *cslip_modem_handle; + +// Optional filter for application-specific serial messages in the stream +typedef bool cslip_rx_filter_cb_t(cslip_modem_handle cslip, uint8_t *data, uint32_t len); + +// Minimal CSLIP config stub per requirements (values currently unused) +typedef struct { + bool enable; // default true + uint8_t vj_slots; // default 16 + bool slotid_compression; // default true + bool safe_mode; // default true +} esp_cslip_config_t; + +// Configuration structure for CSLIP modem interface (extends SLIP modem config) +typedef struct { + uart_port_t uart_dev; /* UART device for reading/writing */ + + int uart_tx_pin; /* UART TX pin number */ + int uart_rx_pin; /* UART RX pin number */ + + uint32_t uart_baud; /* UART baud rate */ + + uint32_t rx_buffer_len; /* RX buffer length */ + + cslip_rx_filter_cb_t *rx_filter; /* Optional RX filter */ + esp_ip6_addr_t *ipv6_addr; /* IPv6 address */ + + esp_cslip_config_t cslip; /* CSLIP options (currently informational) */ +} cslip_modem_config_t; + +// Create a CSLIP modem (initially pass-through SLIP behavior) +cslip_modem_handle cslip_modem_create(esp_netif_t *cslip_netif, const cslip_modem_config_t *modem_config); + +// Destroy a CSLIP modem +esp_err_t cslip_modem_destroy(cslip_modem_handle cslip); + +// Get configured IPv6 address +esp_ip6_addr_t cslip_modem_get_ipv6_address(cslip_modem_handle cslip); + +// Raw write out the interface +void cslip_modem_raw_write(cslip_modem_handle cslip, void *buffer, size_t len); diff --git a/examples/esp_netif/cslip_custom_netif/components/cslip_modem/library/cslip_modem.c b/examples/esp_netif/cslip_custom_netif/components/cslip_modem/library/cslip_modem.c new file mode 100644 index 0000000000..be38f858cb --- /dev/null +++ b/examples/esp_netif/cslip_custom_netif/components/cslip_modem/library/cslip_modem.c @@ -0,0 +1,176 @@ +/* + * SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include "cslip_modem.h" + +#include "esp_netif.h" +#include "cslip_modem_netif.h" +#include "esp_event.h" +#include "esp_log.h" + +#define CSLIP_RX_TASK_PRIORITY 10 +#define CSLIP_RX_TASK_STACK_SIZE (4 * 1024) + +static const char *TAG = "cslip-modem"; + +typedef struct { + uart_port_t uart_dev; + uint32_t uart_baud; + int uart_tx_pin; + int uart_rx_pin; + QueueHandle_t uart_queue; + TaskHandle_t uart_rx_task; +} esp_cslip_uart_t; + +struct cslip_modem { + esp_netif_driver_base_t base; + esp_cslip_uart_t uart; + uint8_t *buffer; + uint32_t buffer_len; + cslip_rx_filter_cb_t *rx_filter; + bool running; + esp_ip6_addr_t addr; + esp_cslip_config_t cslip_cfg; // currently informational +}; + +static void cslip_modem_uart_rx_task(void *arg); +static esp_err_t cslip_modem_post_attach(esp_netif_t *esp_netif, void *args); + +cslip_modem_handle cslip_modem_create(esp_netif_t *cslip_netif, const cslip_modem_config_t *modem_config) +{ + if (cslip_netif == NULL || modem_config == NULL) { + ESP_LOGE(TAG, "invalid parameters"); + return NULL; + } + ESP_LOGI(TAG, "%s: Creating cslip modem (netif: %p)", __func__, cslip_netif); + + cslip_modem_handle modem = calloc(1, sizeof(struct cslip_modem)); + if (!modem) { + ESP_LOGE(TAG, "create netif glue failed"); + return NULL; + } + + modem->base.post_attach = cslip_modem_post_attach; + modem->base.netif = cslip_netif; + + modem->buffer_len = modem_config->rx_buffer_len; + modem->rx_filter = modem_config->rx_filter; + + modem->uart.uart_dev = modem_config->uart_dev; + modem->uart.uart_baud = modem_config->uart_baud; + modem->uart.uart_rx_pin = modem_config->uart_rx_pin; + modem->uart.uart_tx_pin = modem_config->uart_tx_pin; + modem->addr = *modem_config->ipv6_addr; + modem->cslip_cfg = modem_config->cslip; + + return modem; +} + +static esp_err_t esp_cslip_driver_start(cslip_modem_handle modem) +{ + if (modem->buffer == NULL) { + modem->buffer = malloc(modem->buffer_len); + } + if (modem->buffer == NULL) { + ESP_LOGE(TAG, "error allocating rx buffer"); + return ESP_ERR_NO_MEM; + } + + uart_config_t uart_config = { + .baud_rate = modem->uart.uart_baud, + .data_bits = UART_DATA_8_BITS, + .parity = UART_PARITY_DISABLE, + .stop_bits = UART_STOP_BITS_1, + .flow_ctrl = UART_HW_FLOWCTRL_DISABLE, + }; + + ESP_ERROR_CHECK(uart_param_config(modem->uart.uart_dev, &uart_config)); + ESP_ERROR_CHECK(uart_set_pin(modem->uart.uart_dev, modem->uart.uart_tx_pin, modem->uart.uart_rx_pin, 0, 0)); + ESP_ERROR_CHECK(uart_driver_install(modem->uart.uart_dev, modem->buffer_len, modem->buffer_len, 10, &modem->uart.uart_queue, 0)); + + modem->running = true; + xTaskCreate(cslip_modem_uart_rx_task, "cslip_modem_uart_rx_task", CSLIP_RX_TASK_STACK_SIZE, modem, CSLIP_RX_TASK_PRIORITY, &modem->uart.uart_rx_task); + + esp_netif_action_start(modem->base.netif, 0, 0, 0); + ESP_ERROR_CHECK(cslip_modem_netif_start(modem->base.netif, &modem->addr)); + return ESP_OK; +} + +esp_err_t cslip_modem_destroy(cslip_modem_handle modem) +{ + if (modem != NULL) { + esp_netif_action_stop(modem->base.netif, 0, 0, 0); + ESP_ERROR_CHECK(cslip_modem_netif_stop(modem->base.netif)); + vTaskDelete(modem->uart.uart_rx_task); + uart_driver_delete(modem->uart.uart_dev); + free(modem); + } + + return ESP_OK; +} + +static esp_err_t cslip_modem_transmit(void *driver, void *buffer, size_t len) +{ + cslip_modem_handle modem = (cslip_modem_handle)driver; + int32_t res = uart_write_bytes(modem->uart.uart_dev, (char *)buffer, len); + if (res < 0) { + ESP_LOGE(TAG, "%s: uart_write_bytes error %" PRId32, __func__, res); + return ESP_FAIL; + } + return ESP_OK; +} + +static esp_err_t cslip_modem_post_attach(esp_netif_t *esp_netif, void *args) +{ + cslip_modem_handle modem = (cslip_modem_handle) args; + + const esp_netif_driver_ifconfig_t driver_ifconfig = { + .driver_free_rx_buffer = NULL, + .transmit = cslip_modem_transmit, + .handle = modem, + }; + + modem->base.netif = esp_netif; + ESP_ERROR_CHECK(esp_netif_set_driver_config(esp_netif, &driver_ifconfig)); + + esp_cslip_driver_start(modem); + return ESP_OK; +} + +static void cslip_modem_uart_rx_task(void *arg) +{ + if (arg == NULL) { + vTaskDelete(NULL); + } + cslip_modem_handle modem = (cslip_modem_handle) arg; + + ESP_LOGD(TAG, "Start CSLIP modem RX task (filter: %p)", modem->rx_filter); + + while (modem->running == true) { + int len = uart_read_bytes(modem->uart.uart_dev, modem->buffer, modem->buffer_len, 1 / portTICK_PERIOD_MS); + + if (len > 0) { + modem->buffer[len] = '\0'; + if ((modem->rx_filter != NULL) && modem->rx_filter(modem, modem->buffer, len)) { + continue; + } + esp_netif_receive(modem->base.netif, modem->buffer, len, NULL); + } + vTaskDelay(1 * portTICK_PERIOD_MS); + } +} + +esp_ip6_addr_t cslip_modem_get_ipv6_address(cslip_modem_handle modem) +{ + return modem->addr; +} + +void cslip_modem_raw_write(cslip_modem_handle modem, void *buffer, size_t len) +{ + cslip_modem_netif_raw_write(modem->base.netif, buffer, len); +} diff --git a/examples/esp_netif/cslip_custom_netif/components/cslip_modem/library/cslip_modem_netif.c b/examples/esp_netif/cslip_custom_netif/components/cslip_modem/library/cslip_modem_netif.c new file mode 100644 index 0000000000..490794636c --- /dev/null +++ b/examples/esp_netif/cslip_custom_netif/components/cslip_modem/library/cslip_modem_netif.c @@ -0,0 +1,164 @@ +/* + * SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include "esp_netif.h" +#include "esp_log.h" +#include "esp_netif_net_stack.h" +#include "lwip/esp_netif_net_stack.h" +#include "lwip/dns.h" +#include "lwip/ip6_addr.h" +#include "lwip/netif.h" +#include "netif/slipif.h" +#include "lwip/sio.h" + +static const char *TAG = "cslip-modem-netif"; + +esp_err_t cslip_modem_netif_stop(esp_netif_t *esp_netif) +{ + struct netif *netif = esp_netif_get_netif_impl(esp_netif); + ESP_LOGI(TAG, "%s: Stopped CSLIP connection: lwip netif:%p", __func__, netif); + netif_set_link_down(netif); + return ESP_OK; +} + +esp_err_t cslip_modem_netif_start(esp_netif_t *esp_netif, esp_ip6_addr_t *addr) +{ + struct netif *netif = esp_netif_get_netif_impl(esp_netif); + ESP_LOGI(TAG, "%s: Starting CSLIP interface: lwip netif:%p", __func__, netif); + netif_set_up(netif); + netif_set_link_up(netif); +#if CONFIG_LWIP_IPV6 + int8_t addr_index = 0; + netif_ip6_addr_set(netif, addr_index, (ip6_addr_t *)addr); + netif_ip6_addr_set_state(netif, addr_index, IP6_ADDR_VALID); +#endif + return ESP_OK; +} + +void esp_netif_lwip_cslip_input(void *h, void *buffer, unsigned int len, void *eb) +{ + struct netif *netif = h; + + ESP_LOGD(TAG, "%s", __func__); + ESP_LOG_BUFFER_HEXDUMP(TAG, buffer, len, ESP_LOG_DEBUG); + + const int max_batch = 255; + int sent = 0; + while (sent < len) { + int batch = (len - sent) > max_batch ? max_batch : (len - sent); + slipif_received_bytes(netif, buffer + sent, batch); + sent += batch; + } + + for (int i = 0; i < len; i++) { + slipif_process_rxqueue(netif); + } +} + +void cslip_modem_netif_raw_write(esp_netif_t *netif, void *buffer, size_t len) +{ + struct netif *lwip_netif = esp_netif_get_netif_impl(netif); + ESP_LOGD(TAG, "%s", __func__); + + struct pbuf p = { + .next = NULL, + .payload = buffer, + .tot_len = len, + .len = len, + }; + +#if CONFIG_LWIP_IPV6 + lwip_netif->output_ip6(lwip_netif, &p, NULL); +#else + lwip_netif->output(lwip_netif, &p, NULL); +#endif +} + +static esp_netif_t *get_netif_with_esp_index(int index) +{ + esp_netif_t *netif = NULL; + int counter = 0; +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 2, 0) + while ((netif = esp_netif_next_unsafe(netif)) != NULL) { +#else + while ((netif = esp_netif_next(netif)) != NULL) { +#endif + if (counter == index) { + return netif; + } + counter++; + } + return NULL; +} + +static int get_esp_netif_index(esp_netif_t *esp_netif) +{ + esp_netif_t *netif = NULL; + int counter = 0; +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 2, 0) + while ((netif = esp_netif_next_unsafe(netif)) != NULL) { +#else + while ((netif = esp_netif_next(netif)) != NULL) { +#endif + if (esp_netif == netif) { + return counter; + } + counter++; + } + return -1; +} + +static err_t esp_cslipif_init(struct netif *netif) +{ + if (netif == NULL) { + return ERR_IF; + } + esp_netif_t *esp_netif = netif->state; + int esp_index = get_esp_netif_index(esp_netif); + if (esp_index < 0) { + return ERR_IF; + } + + netif->state = (void *)esp_index; + + return slipif_init(netif); +} + +const struct esp_netif_netstack_config s_netif_config_cslip = { + .lwip = { + .init_fn = esp_cslipif_init, + .input_fn = esp_netif_lwip_cslip_input, + } +}; + +const esp_netif_netstack_config_t *netstack_default_cslip = &s_netif_config_cslip; + +sio_fd_t sio_open(uint8_t devnum) +{ + ESP_LOGD(TAG, "Opening device: %d\r\n", devnum); + + esp_netif_t *esp_netif = get_netif_with_esp_index(devnum); + if (!esp_netif) { + ESP_LOGE(TAG, "didn't find esp-netif with index=%d\n", devnum); + return NULL; + } + + return esp_netif; +} + +void sio_send(uint8_t c, sio_fd_t fd) +{ + esp_netif_t *esp_netif = fd; + + ESP_LOGD(TAG, "%s", __func__); + ESP_LOG_BUFFER_HEX_LEVEL(TAG, &c, 1, ESP_LOG_DEBUG); + + esp_err_t ret = esp_netif_transmit(esp_netif, &c, 1); + if (ret != ESP_OK) { + ESP_LOGD(TAG, "%s: uart_write_bytes error %i", __func__, ret); + } +} diff --git a/examples/esp_netif/cslip_custom_netif/components/cslip_modem/library/cslip_modem_netif.h b/examples/esp_netif/cslip_custom_netif/components/cslip_modem/library/cslip_modem_netif.h new file mode 100644 index 0000000000..8cde5b2db6 --- /dev/null +++ b/examples/esp_netif/cslip_custom_netif/components/cslip_modem/library/cslip_modem_netif.h @@ -0,0 +1,22 @@ +/* + * SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +/** + * Stop the esp cslip netif + */ +esp_err_t cslip_modem_netif_stop(esp_netif_t *esp_netif); + +/** + * Start the esp cslip netif + */ +esp_err_t cslip_modem_netif_start(esp_netif_t *esp_netif, esp_ip6_addr_t *addr); + +/** + * Write raw packet out the interface + */ +void cslip_modem_netif_raw_write(esp_netif_t *netif, void *buffer, size_t len); diff --git a/examples/esp_netif/cslip_custom_netif/main/CMakeLists.txt b/examples/esp_netif/cslip_custom_netif/main/CMakeLists.txt new file mode 100644 index 0000000000..5a33bb6723 --- /dev/null +++ b/examples/esp_netif/cslip_custom_netif/main/CMakeLists.txt @@ -0,0 +1,7 @@ +# CSLIP client example + +idf_component_register( + SRCS "cslip_client_main.c" + INCLUDE_DIRS "." + REQUIRES esp_netif cslip_modem driver +) diff --git a/examples/esp_netif/cslip_custom_netif/main/Kconfig.projbuild b/examples/esp_netif/cslip_custom_netif/main/Kconfig.projbuild new file mode 100644 index 0000000000..047ba12e2f --- /dev/null +++ b/examples/esp_netif/cslip_custom_netif/main/Kconfig.projbuild @@ -0,0 +1,38 @@ +menu "Example Configuration" + + menu "UART Configuration" + config EXAMPLE_UART_TX_PIN + int "TXD Pin Number" + default 25 + range 0 36 + help + Pin number of UART TX. + + config EXAMPLE_UART_RX_PIN + int "RXD Pin Number" + default 26 + range 0 36 + help + Pin number of UART RX. + + config EXAMPLE_UART_BAUD + int "UART baud rate" + default 115200 + help + Baud rate for UART communication + + endmenu + + config EXAMPLE_UDP_PORT + int "Port for UDP echo server" + default 5678 + help + Port for UDP echo server in example + + config EXAMPLE_IPV4 + bool "Test with IPv4 address" + default n + help + Test interface using IPv4 + +endmenu diff --git a/examples/esp_netif/cslip_custom_netif/main/cslip_client_main.c b/examples/esp_netif/cslip_custom_netif/main/cslip_client_main.c new file mode 100644 index 0000000000..ce957c616e --- /dev/null +++ b/examples/esp_netif/cslip_custom_netif/main/cslip_client_main.c @@ -0,0 +1,174 @@ +/* + * SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ + +/* + * CSLIP Client Example (initial, pass-through SLIP) +*/ +#include +#include + +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "esp_system.h" +#include "esp_log.h" +#include "esp_event.h" +#include "esp_netif.h" + +#include "cslip_modem.h" + +static const char *TAG = "cslip-example"; + +#define STACK_SIZE (10 * 1024) +#define PRIORITY 10 + +static void udp_rx_tx_task(void *arg) +{ + char addr_str[128]; + uint8_t rx_buff[1024]; + + int sock = (int)arg; + + struct sockaddr_storage source_addr; + socklen_t socklen = sizeof(source_addr); + + + ESP_LOGI(TAG, "Starting UDP echo task"); + + while (1) { + int len = recvfrom(sock, rx_buff, sizeof(rx_buff) - 1, 0, (struct sockaddr *)&source_addr, &socklen); + if (len < 0) { + ESP_LOGE(TAG, "recvfrom failed: errno %d", errno); + break; + } + + if (source_addr.ss_family == PF_INET) { + inet_ntoa_r(((struct sockaddr_in *)&source_addr)->sin_addr.s_addr, addr_str, sizeof(addr_str) - 1); + } else if (source_addr.ss_family == PF_INET6) { + inet6_ntoa_r(((struct sockaddr_in6 *)&source_addr)->sin6_addr, addr_str, sizeof(addr_str) - 1); + } + + rx_buff[len] = 0; + ESP_LOGI(TAG, "Received '%s' from '%s'", rx_buff, addr_str); + + int err = sendto(sock, rx_buff, len, 0, (struct sockaddr *)&source_addr, socklen); + if (err < 0) { + ESP_LOGE(TAG, "sendto failed: errno %d", errno); + break; + } + } + + vTaskDelete(NULL); +} + +static esp_err_t udp_rx_tx_start(void) +{ + struct sockaddr_in6 dest_addr; +#if CONFIG_EXAMPLE_IPV4 + sa_family_t family = AF_INET; + int ip_protocol = IPPROTO_IP; + struct sockaddr_in *dest_addr_ip4 = (struct sockaddr_in *)&dest_addr; + dest_addr_ip4->sin_addr.s_addr = htonl(INADDR_ANY); + dest_addr_ip4->sin_family = AF_INET; + dest_addr_ip4->sin_port = htons(CONFIG_EXAMPLE_UDP_PORT); + ip_protocol = IPPROTO_IP; +#else + sa_family_t family = AF_INET6; + int ip_protocol = IPPROTO_IPV6; + bzero(&dest_addr.sin6_addr.un, sizeof(dest_addr.sin6_addr.un)); + dest_addr.sin6_family = family; + dest_addr.sin6_port = htons(CONFIG_EXAMPLE_UDP_PORT); +#endif + + int sock = socket(family, SOCK_DGRAM, ip_protocol); + if (sock < 0) { + ESP_LOGE(TAG, "Unable to create socket: errno %d", errno); + return ESP_FAIL; + } + + int opt = 1; + setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); +#if !CONFIG_EXAMPLE_IPV4 + setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, &opt, sizeof(opt)); +#endif + + int err = bind(sock, (struct sockaddr *)&dest_addr, sizeof(dest_addr)); + if (err < 0) { + ESP_LOGE(TAG, "Socket unable to bind: errno %d", errno); + return ESP_FAIL; + } + ESP_LOGI(TAG, "Socket bound, port %d", CONFIG_EXAMPLE_UDP_PORT); + + xTaskCreate(udp_rx_tx_task, "udp_rx_tx", STACK_SIZE, (void *)sock, PRIORITY, NULL); + + return ESP_OK; +} + +#if CONFIG_EXAMPLE_IPV4 +static const esp_netif_ip_info_t s_cslip_ip4 = { + .ip = { .addr = ESP_IP4TOADDR(10, 0, 0, 2) }, +}; +#endif + +// Initialise the CSLIP-like interface (currently pass-through SLIP) +esp_netif_t *cslip_if_init(void) +{ + ESP_LOGI(TAG, "Initialising CSLIP interface (pass-through)"); + + esp_netif_inherent_config_t base_cfg = ESP_NETIF_INHERENT_DEFAULT_CSLIP(); +#if CONFIG_EXAMPLE_IPV4 + base_cfg.ip_info = &s_cslip_ip4; +#endif + esp_netif_config_t cfg = { .base = &base_cfg, .driver = NULL, .stack = netstack_default_cslip }; + + esp_netif_t *cslip_netif = esp_netif_new(&cfg); + + esp_ip6_addr_t local_addr; /* Local IP6 address */ + IP6_ADDR(&local_addr, + lwip_htonl(0xfd0000), + lwip_htonl(0x00000000), + lwip_htonl(0x00000000), + lwip_htonl(0x00000001) + ); + + ESP_LOGI(TAG, "Initialising CSLIP modem"); + + cslip_modem_config_t modem_cfg = { + .uart_dev = UART_NUM_1, + .uart_tx_pin = CONFIG_EXAMPLE_UART_TX_PIN, + .uart_rx_pin = CONFIG_EXAMPLE_UART_RX_PIN, + .uart_baud = CONFIG_EXAMPLE_UART_BAUD, + .rx_buffer_len = 1024, + .rx_filter = NULL, // optional: same behavior as SLIP + .ipv6_addr = &local_addr, + .cslip = { + .enable = true, + .vj_slots = 16, + .slotid_compression = true, + .safe_mode = true, + }, + }; + + void *cslip_modem = cslip_modem_create(cslip_netif, &modem_cfg); + assert(cslip_modem); + ESP_ERROR_CHECK(esp_netif_attach(cslip_netif, cslip_modem)); + + ESP_LOGI(TAG, "CSLIP init complete"); + + return cslip_netif; +} + +void app_main(void) +{ + esp_netif_init(); + ESP_ERROR_CHECK(esp_event_loop_create_default()); + + // Setup cslip interface (pass-through) + esp_netif_t *esp_netif = cslip_if_init(); + assert(esp_netif); + + // Start the UDP user application + udp_rx_tx_start(); +} diff --git a/examples/esp_netif/cslip_custom_netif/pytest_cslip.py b/examples/esp_netif/cslip_custom_netif/pytest_cslip.py new file mode 100644 index 0000000000..3080c7b753 --- /dev/null +++ b/examples/esp_netif/cslip_custom_netif/pytest_cslip.py @@ -0,0 +1,96 @@ +# SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD +# SPDX-License-Identifier: Unlicense OR CC0-1.0 +from __future__ import print_function, unicode_literals + +import subprocess +import time +from threading import Event, Thread + +import netifaces + + +def is_esp32(port): + """ + Check if the given port is connected to an ESP32 using esptool. + """ + try: + result = subprocess.run( + ['esptool.py', '--port', port, 'chip_id'], + stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=True, text=True + ) + return 'ESP32' in result.stdout + except subprocess.CalledProcessError: + return False + + +def run_server(server_stop, port): + print('Launching CSLIP(netif=slip) on port: {}'.format(port)) + try: + arg_list = ['sudo', 'slattach', '-v', '-L', '-s', '115200', '-p', 'slip', port] + p = subprocess.Popen(arg_list, stdout=subprocess.PIPE, bufsize=1) + while not server_stop.is_set(): + if p.poll() is not None: + if p.poll() == 16: + print('[SLIP:] Terminated: hang-up received') + break + else: + raise ValueError( + 'ENV_TEST_FAILURE: SLIP terminated unexpectedly with {}' + .format(p.poll())) + time.sleep(0.1) + except Exception as e: + print(e) + raise ValueError('ENV_TEST_FAILURE: Error running SLIP netif') + finally: + p.terminate() + print('SLIP netif stopped') + + +def test_examples_protocol_cslip(dut): + # Find a host serial port not used by DUT and not another ESP32 + server_port = None + for i in ['/dev/ttyUSB0', '/dev/ttyUSB1', '/dev/ttyUSB2']: + if i == dut.serial.port: + print(f'DUT port: {i}') + elif is_esp32(i): + print(f'Some other ESP32: {i}') + else: + print(f'Port for SLIP: {i}') + server_port = i + if server_port is None: + print('ENV_TEST_FAILURE: Cannot locate SLIP port') + raise + + server_stop = Event() + t = Thread(target=run_server, args=(server_stop, server_port)) + t.start() + try: + # Wait for sl0 to appear + ppp_server_timeout = time.time() + 30 + while 'sl0' not in netifaces.interfaces(): + print( + "SLIP netif hasn't appear yet, active netifs:{}" + .format(netifaces.interfaces())) + time.sleep(0.5) + if time.time() > ppp_server_timeout: + raise ValueError( + 'ENV_TEST_FAILURE: SLIP netif failed to setup sl0 interface within timeout' + ) + # Configure host side addresses + cmd = ['sudo', 'ifconfig', 'sl0', '10.0.0.1', 'dstaddr', '10.0.0.2'] + try: + subprocess.run(cmd, check=True) + print('SLIP interface configured successfully.') + except subprocess.CalledProcessError as e: + print(f'Failed to configure SLIP interface: {e}') + # Ping the DUT IP + cmd = ['ping', '10.0.0.2', '-c', '5', '-W', '10'] + try: + subprocess.run(cmd, check=True) + print('Pinging CSLIP interface successfully.') + except subprocess.CalledProcessError as e: + print(f'Failed to ping CSLIP interface: {e}') + raise + finally: + server_stop.set() + t.join() diff --git a/examples/esp_netif/cslip_custom_netif/sdkconfig.ci b/examples/esp_netif/cslip_custom_netif/sdkconfig.ci new file mode 100644 index 0000000000..c7cee07789 --- /dev/null +++ b/examples/esp_netif/cslip_custom_netif/sdkconfig.ci @@ -0,0 +1,6 @@ +CONFIG_IDF_TARGET="esp32c3" +CONFIG_LWIP_PPP_SUPPORT=y +CONFIG_LWIP_SLIP_SUPPORT=y +CONFIG_EXAMPLE_IPV4=y +CONFIG_EXAMPLE_UART_TX_PIN=6 +CONFIG_EXAMPLE_UART_RX_PIN=7 diff --git a/examples/esp_netif/cslip_custom_netif/sdkconfig.defaults b/examples/esp_netif/cslip_custom_netif/sdkconfig.defaults new file mode 100644 index 0000000000..3e0a7a3451 --- /dev/null +++ b/examples/esp_netif/cslip_custom_netif/sdkconfig.defaults @@ -0,0 +1,4 @@ +# Enable SLIP in lwIP (CSLIP currently pass-through) +CONFIG_LWIP_SLIP_SUPPORT=y +# Workaround: Enable PPP so esp_netif accounts for lwIP netif->state usage +CONFIG_LWIP_PPP_SUPPORT=y