From fdd86721e74ad02668771f4315f7c704560cded8 Mon Sep 17 00:00:00 2001 From: David Cermak Date: Mon, 22 Sep 2025 11:47:32 +0200 Subject: [PATCH 1/2] feat(esp_netif): Support for MTU path discovery Closes https://github.com/espressif/esp-idf/issues/11613 --- components/esp_netif/include/esp_netif.h | 39 ++++- .../esp_netif/loopback/esp_netif_loopback.c | 15 +- components/esp_netif/lwip/esp_netif_lwip.c | 40 +++++ .../protocols/icmp/pmtu_probe/CMakeLists.txt | 4 + examples/protocols/icmp/pmtu_probe/README.md | 40 +++++ .../icmp/pmtu_probe/main/CMakeLists.txt | 3 + .../icmp/pmtu_probe/main/Kconfig.projbuild | 9 + .../icmp/pmtu_probe/main/idf_component.yml | 3 + .../icmp/pmtu_probe/main/pmtu_probe_main.c | 163 ++++++++++++++++++ 9 files changed, 314 insertions(+), 2 deletions(-) create mode 100644 examples/protocols/icmp/pmtu_probe/CMakeLists.txt create mode 100644 examples/protocols/icmp/pmtu_probe/README.md create mode 100644 examples/protocols/icmp/pmtu_probe/main/CMakeLists.txt create mode 100644 examples/protocols/icmp/pmtu_probe/main/Kconfig.projbuild create mode 100644 examples/protocols/icmp/pmtu_probe/main/idf_component.yml create mode 100644 examples/protocols/icmp/pmtu_probe/main/pmtu_probe_main.c diff --git a/components/esp_netif/include/esp_netif.h b/components/esp_netif/include/esp_netif.h index 6430881f56..8a4980db4b 100644 --- a/components/esp_netif/include/esp_netif.h +++ b/components/esp_netif/include/esp_netif.h @@ -487,9 +487,46 @@ int esp_netif_get_netif_impl_index(esp_netif_t *esp_netif); * @return * - ESP_OK * - ESP_ERR_ESP_NETIF_INVALID_PARAMS -*/ + */ esp_err_t esp_netif_get_netif_impl_name(esp_netif_t *esp_netif, char* name); +/** + * @brief Set interface MTU at runtime + * + * Updates the underlying stack's MTU for the given interface. This affects + * TCP effective MSS calculation and IP fragmentation behavior for future + * transmissions on this interface. + * + * Notes: + * - Applies per-interface, not per-connection. Existing TCP connections may + * adjust naturally based on effective MSS calculations during send. + * - Some interfaces may renegotiate MTU (e.g., DHCP/PPP) and override this; + * reapply as needed after link events. + * - On stacks that do not support runtime MTU updates, returns ESP_ERR_NOT_SUPPORTED. + * + * @param[in] esp_netif Handle to esp-netif instance + * @param[in] mtu New MTU value to set (in bytes) + * @return + * - ESP_OK on success + * - ESP_ERR_ESP_NETIF_INVALID_PARAMS if parameters are invalid or interface not ready + * - ESP_ERR_NOT_SUPPORTED if not supported by the current net stack + */ +esp_err_t esp_netif_set_mtu(esp_netif_t *esp_netif, uint16_t mtu); + +/** + * @brief Get interface MTU + * + * Reads the underlying stack's MTU for the given interface. + * + * @param[in] esp_netif Handle to esp-netif instance + * @param[out] mtu Pointer to store MTU (in bytes) + * @return + * - ESP_OK on success + * - ESP_ERR_ESP_NETIF_INVALID_PARAMS if parameters are invalid or interface not ready + * - ESP_ERR_NOT_SUPPORTED if not supported by the current net stack + */ +esp_err_t esp_netif_get_mtu(esp_netif_t *esp_netif, uint16_t *mtu); + /** * @brief Enable NAPT on an interface * diff --git a/components/esp_netif/loopback/esp_netif_loopback.c b/components/esp_netif/loopback/esp_netif_loopback.c index 81e4aa7f18..2e7393e925 100644 --- a/components/esp_netif/loopback/esp_netif_loopback.c +++ b/components/esp_netif/loopback/esp_netif_loopback.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2015-2024 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2015-2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -484,4 +484,17 @@ esp_netif_t *esp_netif_get_handle_from_ifkey(const char *if_key) { return esp_netif_get_handle_from_ifkey_unsafe(if_key); } + +// MTU control is not supported in loopback build. +esp_err_t esp_netif_set_mtu(esp_netif_t *esp_netif, uint16_t mtu) +{ + ESP_LOGD(TAG, "%s esp_netif:%p mtu:%u", __func__, esp_netif, (unsigned)mtu); + return ESP_ERR_NOT_SUPPORTED; +} + +esp_err_t esp_netif_get_mtu(esp_netif_t *esp_netif, uint16_t *mtu) +{ + ESP_LOGD(TAG, "%s esp_netif:%p", __func__, esp_netif); + return ESP_ERR_NOT_SUPPORTED; +} #endif /* CONFIG_ESP_NETIF_LOOPBACK */ diff --git a/components/esp_netif/lwip/esp_netif_lwip.c b/components/esp_netif/lwip/esp_netif_lwip.c index 4fbb4f3c29..1713846e9d 100644 --- a/components/esp_netif/lwip/esp_netif_lwip.c +++ b/components/esp_netif/lwip/esp_netif_lwip.c @@ -2698,6 +2698,46 @@ esp_err_t esp_netif_get_netif_impl_name(esp_netif_t *esp_netif, char* name) return esp_netif_lwip_ipc_call(esp_netif_get_netif_impl_name_api, esp_netif, name); } +static esp_err_t esp_netif_set_mtu_api(esp_netif_api_msg_t *msg) +{ + esp_netif_t *esp_netif = msg->esp_netif; + if (esp_netif == NULL || esp_netif->lwip_netif == NULL || msg->data == NULL) { + return ESP_ERR_ESP_NETIF_INVALID_PARAMS; + } + uint16_t mtu = *(uint16_t *)msg->data; + esp_netif->lwip_netif->mtu = mtu; + return ESP_OK; +} + +esp_err_t esp_netif_set_mtu(esp_netif_t *esp_netif, uint16_t mtu) +{ + ESP_LOGD(TAG, "%s esp_netif:%p mtu:%u", __func__, esp_netif, (unsigned)mtu); + if (esp_netif == NULL || esp_netif->lwip_netif == NULL) { + return ESP_ERR_ESP_NETIF_INVALID_PARAMS; + } + return esp_netif_lwip_ipc_call(esp_netif_set_mtu_api, esp_netif, &mtu); +} + +static esp_err_t esp_netif_get_mtu_api(esp_netif_api_msg_t *msg) +{ + esp_netif_t *esp_netif = msg->esp_netif; + if (esp_netif == NULL || esp_netif->lwip_netif == NULL || msg->data == NULL) { + return ESP_ERR_ESP_NETIF_INVALID_PARAMS; + } + uint16_t *mtu_out = (uint16_t *)msg->data; + *mtu_out = (uint16_t)esp_netif->lwip_netif->mtu; + return ESP_OK; +} + +esp_err_t esp_netif_get_mtu(esp_netif_t *esp_netif, uint16_t *mtu) +{ + ESP_LOGD(TAG, "%s esp_netif:%p", __func__, esp_netif); + if (esp_netif == NULL || esp_netif->lwip_netif == NULL || mtu == NULL) { + return ESP_ERR_ESP_NETIF_INVALID_PARAMS; + } + return esp_netif_lwip_ipc_call(esp_netif_get_mtu_api, esp_netif, mtu); +} + #if IP_NAPT static esp_err_t esp_netif_napt_control_api(esp_netif_api_msg_t *msg) { diff --git a/examples/protocols/icmp/pmtu_probe/CMakeLists.txt b/examples/protocols/icmp/pmtu_probe/CMakeLists.txt new file mode 100644 index 0000000000..aa27747043 --- /dev/null +++ b/examples/protocols/icmp/pmtu_probe/CMakeLists.txt @@ -0,0 +1,4 @@ +cmake_minimum_required(VERSION 3.16) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(pmtu_probe) diff --git a/examples/protocols/icmp/pmtu_probe/README.md b/examples/protocols/icmp/pmtu_probe/README.md new file mode 100644 index 0000000000..763af3e9ef --- /dev/null +++ b/examples/protocols/icmp/pmtu_probe/README.md @@ -0,0 +1,40 @@ +| Supported Targets | ESP32 | ESP32-C2 | ESP32-C3 | ESP32-C5 | ESP32-C6 | ESP32-C61 | ESP32-H2 | ESP32-H21 | ESP32-H4 | ESP32-P4 | ESP32-S2 | ESP32-S3 | +| ----------------- | ----- | -------- | -------- | -------- | -------- | --------- | -------- | --------- | -------- | -------- | -------- | -------- | + +# Path MTU Probe (ICMP) + +This example probes a safe Path MTU (PMTU) using ICMP echo requests and applies it to the active network interface at runtime via `esp_netif_set_mtu()`. + +It performs a simple binary search over the ICMP payload size to find the largest size that receives a reply within the timeout, then estimates the interface MTU as `payload + 28` (20-byte IPv4 header + 8-byte ICMP header) and sets that MTU on the default interface. + +Notes: +- This is a pragmatic approach for constrained networks (e.g., LTE) where fragmentation or ICMP handling can be problematic. It is not a full standards PMTUD/PLPMTUD implementation. +- For IPv6 targets, lwIP’s ND6 manages per-destination PMTU internally. The interface MTU cap still bounds TCP effective MSS. +- DHCP or PPP may renegotiate MTU; re-run the probe or reapply the MTU after reconnect events if needed. + +## How to use + +1) Configure network and probe target: +- `idf.py menuconfig` → Example Connection Configuration → select Wi-Fi or Ethernet and credentials. +- `idf.py menuconfig` → Example Configuration → set "Probe Hostname/IP" (default: `www.espressif.com`). + +2) Build/flash/monitor: +- `idf.py -p PORT flash monitor` + +3) Example output: +``` +I (xxx) pmtu_probe: Connecting network... +I (xxx) pmtu_probe: Probing PMTU to host www.espressif.com (IPv4) +I (xxx) pmtu_probe: Search range payload=[0, 1472] +I (xxx) pmtu_probe: Best payload=1392 -> MTU=1420 +I (xxx) pmtu_probe: Applying MTU 1420 to default netif +``` + +## Implementation details + +- Uses `esp_ping` (ping_sock) to attempt 1 echo per payload size and waits synchronously for success/timeout. +- Bounds the search using the current interface MTU when available, otherwise falls back to `1472`. +- Applies MTU via new API: + - `esp_netif_set_mtu(esp_netif_t *netif, uint16_t mtu)` + - `esp_netif_get_mtu(esp_netif_t *netif, uint16_t *mtu)` + diff --git a/examples/protocols/icmp/pmtu_probe/main/CMakeLists.txt b/examples/protocols/icmp/pmtu_probe/main/CMakeLists.txt new file mode 100644 index 0000000000..d1a57cdaae --- /dev/null +++ b/examples/protocols/icmp/pmtu_probe/main/CMakeLists.txt @@ -0,0 +1,3 @@ +idf_component_register(SRCS "pmtu_probe_main.c" + INCLUDE_DIRS "." + REQUIRES esp_netif esp_event nvs_flash lwip protocol_examples_common) diff --git a/examples/protocols/icmp/pmtu_probe/main/Kconfig.projbuild b/examples/protocols/icmp/pmtu_probe/main/Kconfig.projbuild new file mode 100644 index 0000000000..60c94aff0f --- /dev/null +++ b/examples/protocols/icmp/pmtu_probe/main/Kconfig.projbuild @@ -0,0 +1,9 @@ +menu "Example Configuration" + + config EXAMPLE_PROBE_HOST + string "Probe Hostname/IP" + default "www.espressif.com" + help + Hostname or IPv4 address to probe for MTU. + +endmenu diff --git a/examples/protocols/icmp/pmtu_probe/main/idf_component.yml b/examples/protocols/icmp/pmtu_probe/main/idf_component.yml new file mode 100644 index 0000000000..718194867b --- /dev/null +++ b/examples/protocols/icmp/pmtu_probe/main/idf_component.yml @@ -0,0 +1,3 @@ +dependencies: + protocol_examples_common: + path: ${IDF_PATH}/examples/common_components/protocol_examples_common diff --git a/examples/protocols/icmp/pmtu_probe/main/pmtu_probe_main.c b/examples/protocols/icmp/pmtu_probe/main/pmtu_probe_main.c new file mode 100644 index 0000000000..271a4a9c9b --- /dev/null +++ b/examples/protocols/icmp/pmtu_probe/main/pmtu_probe_main.c @@ -0,0 +1,163 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ +/* Path MTU probe example using ICMP ping payload search + */ + +#include +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/semphr.h" +#include "esp_log.h" +#include "esp_check.h" +#include "nvs_flash.h" +#include "esp_event.h" +#include "protocol_examples_common.h" +#include "esp_netif.h" +#include "lwip/inet.h" +#include "lwip/netdb.h" +#include "ping/ping_sock.h" + +static const char *TAG = "pmtu_probe"; + +typedef struct { + SemaphoreHandle_t done; + bool got_reply; +} probe_ctx_t; + +static void on_ping_success(esp_ping_handle_t hdl, void *args) +{ + probe_ctx_t *ctx = (probe_ctx_t *)args; + ctx->got_reply = true; +} + +static void on_ping_timeout(esp_ping_handle_t hdl, void *args) +{ + (void)hdl; (void)args; +} + +static void on_ping_end(esp_ping_handle_t hdl, void *args) +{ + probe_ctx_t *ctx = (probe_ctx_t *)args; + if (ctx && ctx->done) { + xSemaphoreGive(ctx->done); + } +} + +static bool try_payload_once(const ip_addr_t *target, uint32_t payload) +{ + probe_ctx_t ctx = { .done = xSemaphoreCreateBinary(), .got_reply = false }; + if (!ctx.done) { + return false; + } + esp_ping_config_t cfg = ESP_PING_DEFAULT_CONFIG(); + cfg.target_addr = *target; + cfg.count = 1; + cfg.timeout_ms = 1000; // 1s timeout + cfg.interval_ms = 100; // not used for count=1 + cfg.data_size = payload; + esp_ping_callbacks_t cbs = { + .cb_args = &ctx, + .on_ping_success = on_ping_success, + .on_ping_timeout = on_ping_timeout, + .on_ping_end = on_ping_end, + }; + esp_ping_handle_t ping = NULL; + if (esp_ping_new_session(&cfg, &cbs, &ping) != ESP_OK) { + vSemaphoreDelete(ctx.done); + return false; + } + esp_ping_start(ping); + (void)xSemaphoreTake(ctx.done, pdMS_TO_TICKS(1500)); + esp_ping_delete_session(ping); + vSemaphoreDelete(ctx.done); + return ctx.got_reply; +} + +static bool resolve_host(const char *host, ip_addr_t *out) +{ + memset(out, 0, sizeof(*out)); + struct sockaddr_in6 s6; + if (inet_pton(AF_INET6, host, &s6.sin6_addr) == 1) { + ipaddr_aton(host, out); + return true; + } + struct addrinfo hint = { 0 }, *res = NULL; + if (getaddrinfo(host, NULL, &hint, &res) != 0 || !res) { + return false; + } +#if CONFIG_LWIP_IPV4 + if (res->ai_family == AF_INET) { + struct in_addr a4 = ((struct sockaddr_in *)res->ai_addr)->sin_addr; + inet_addr_to_ip4addr(ip_2_ip4(out), &a4); + freeaddrinfo(res); + return true; + } +#endif + // Prefer IPv4 for simple MTU formula; fall back to first result + ipaddr_aton(host, out); + freeaddrinfo(res); + return true; +} + +void app_main(void) +{ + ESP_ERROR_CHECK(nvs_flash_init()); + ESP_ERROR_CHECK(esp_event_loop_create_default()); + ESP_LOGI(TAG, "Connecting network..."); + ESP_ERROR_CHECK(example_connect()); + + const char *host = CONFIG_EXAMPLE_PROBE_HOST; + ip_addr_t target; + if (!resolve_host(host, &target)) { + ESP_LOGE(TAG, "Failed to resolve host: %s", host); + return; + } + + esp_netif_t *netif = get_example_netif(); + if (!netif) { + ESP_LOGE(TAG, "No default netif available"); + return; + } + + ESP_LOGI(TAG, "Probing PMTU to host %s (IPv4)"); + + uint16_t if_mtu = 0; + uint32_t lo = 0; + uint32_t hi = 1472; // fallback max payload (1500 - 28) + if (esp_netif_get_mtu(netif, &if_mtu) == ESP_OK && if_mtu >= 28) { + hi = (if_mtu >= 28) ? (if_mtu - 28) : 0; + } + ESP_LOGI(TAG, "Search range payload=[%u, %u]", (unsigned)lo, (unsigned)hi); + + uint32_t best = 0; + while (lo <= hi) { + uint32_t mid = lo + ((hi - lo) / 2); + bool ok = try_payload_once(&target, mid); + if (ok) { + best = mid; + lo = mid + 1; + } else { + if (mid == 0) { + break; // can't go lower + } + hi = mid - 1; + } + vTaskDelay(pdMS_TO_TICKS(50)); + } + + uint32_t new_mtu = best + 28; // IPv4 header (20) + ICMP (8) + if (new_mtu < 576) { + // Be conservative; typical minimum IPv4 MTU recommendation + new_mtu = 576; + } + ESP_LOGI(TAG, "Best payload=%u -> MTU=%u", (unsigned)best, (unsigned)new_mtu); + ESP_LOGI(TAG, "Applying MTU %u to default netif", (unsigned)new_mtu); + esp_err_t err = esp_netif_set_mtu(netif, (uint16_t)new_mtu); + if (err != ESP_OK) { + ESP_LOGW(TAG, "Failed to set MTU: %s", esp_err_to_name(err)); + } +} From 4c69bf826fa8cfeb172e16aab7bdba72f3870bea Mon Sep 17 00:00:00 2001 From: David Cermak Date: Mon, 22 Sep 2025 16:36:49 +0200 Subject: [PATCH 2/2] feat(esp_netif): Add support for initial MTU in netif config Closes https://github.com/espressif/esp-idf/issues/15319 --- .../esp_netif/include/esp_netif_defaults.h | 23 ++++++---- .../esp_netif/include/esp_netif_types.h | 1 + .../esp_netif/loopback/esp_netif_loopback.c | 2 - components/esp_netif/lwip/esp_netif_lwip.c | 22 ++++++++-- .../esp_netif/lwip/esp_netif_lwip_internal.h | 2 + .../main/esp_netif_test_lwip.c | 43 +++++++++++++++++++ examples/protocols/icmp/pmtu_probe/README.md | 26 ++++++++--- .../icmp/pmtu_probe/main/pmtu_probe_main.c | 9 ++-- 8 files changed, 105 insertions(+), 23 deletions(-) diff --git a/components/esp_netif/include/esp_netif_defaults.h b/components/esp_netif/include/esp_netif_defaults.h index b215aca1dc..dee5cae807 100644 --- a/components/esp_netif/include/esp_netif_defaults.h +++ b/components/esp_netif/include/esp_netif_defaults.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2015-2024 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2015-2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -52,7 +52,8 @@ extern "C" { .if_key = "WIFI_STA_DEF", \ .if_desc = "sta", \ .route_prio = 100, \ - .bridge_info = NULL \ + .bridge_info = NULL, \ + .mtu = 0 \ } \ #ifdef CONFIG_ESP_WIFI_SOFTAP_SUPPORT @@ -66,7 +67,8 @@ extern "C" { .if_key = "WIFI_AP_DEF", \ .if_desc = "ap", \ .route_prio = 10, \ - .bridge_info = NULL \ + .bridge_info = NULL, \ + .mtu = 0 \ } #endif @@ -79,7 +81,8 @@ extern "C" { .lost_ip_event = 0, \ .if_key = "WIFI_NAN_DEF", \ .if_desc = "nan", \ - .route_prio = 10 \ + .route_prio = 10, \ + .mtu = 0 \ }; #define ESP_NETIF_INHERENT_DEFAULT_ETH() \ @@ -92,7 +95,8 @@ extern "C" { .if_key = "ETH_DEF", \ .if_desc = "eth", \ .route_prio = 50, \ - .bridge_info = NULL \ + .bridge_info = NULL, \ + .mtu = 0 \ } #ifdef CONFIG_PPP_SUPPORT @@ -106,7 +110,8 @@ extern "C" { .if_key = "PPP_DEF", \ .if_desc = "ppp", \ .route_prio = 20, \ - .bridge_info = NULL \ + .bridge_info = NULL, \ + .mtu = 0 \ } #endif /* CONFIG_PPP_SUPPORT */ @@ -120,7 +125,8 @@ extern "C" { .if_key = "BR0", \ .if_desc = "br0", \ .route_prio = 70, \ - .bridge_info = NULL \ + .bridge_info = NULL, \ + .mtu = 0 \ } #define ESP_NETIF_INHERENT_DEFAULT_BR_DHCPS() \ @@ -133,7 +139,8 @@ extern "C" { .if_key = "BR1", \ .if_desc = "br1", \ .route_prio = 70, \ - .bridge_info = NULL \ + .bridge_info = NULL, \ + .mtu = 0 \ } /** diff --git a/components/esp_netif/include/esp_netif_types.h b/components/esp_netif/include/esp_netif_types.h index 7205eedcc3..850ed4e5b0 100644 --- a/components/esp_netif/include/esp_netif_types.h +++ b/components/esp_netif/include/esp_netif_types.h @@ -255,6 +255,7 @@ typedef struct esp_netif_inherent_config { A higher value of route_prio indicates a higher priority */ bridgeif_config_t *bridge_info; /*!< LwIP bridge configuration */ + uint16_t mtu; /*!< Optional initial MTU (bytes). 0 = use stack default */ } esp_netif_inherent_config_t; typedef struct esp_netif_config esp_netif_config_t; diff --git a/components/esp_netif/loopback/esp_netif_loopback.c b/components/esp_netif/loopback/esp_netif_loopback.c index 2e7393e925..af858b764c 100644 --- a/components/esp_netif/loopback/esp_netif_loopback.c +++ b/components/esp_netif/loopback/esp_netif_loopback.c @@ -488,13 +488,11 @@ esp_netif_t *esp_netif_get_handle_from_ifkey(const char *if_key) // MTU control is not supported in loopback build. esp_err_t esp_netif_set_mtu(esp_netif_t *esp_netif, uint16_t mtu) { - ESP_LOGD(TAG, "%s esp_netif:%p mtu:%u", __func__, esp_netif, (unsigned)mtu); return ESP_ERR_NOT_SUPPORTED; } esp_err_t esp_netif_get_mtu(esp_netif_t *esp_netif, uint16_t *mtu) { - ESP_LOGD(TAG, "%s esp_netif:%p", __func__, esp_netif); return ESP_ERR_NOT_SUPPORTED; } #endif /* CONFIG_ESP_NETIF_LOOPBACK */ diff --git a/components/esp_netif/lwip/esp_netif_lwip.c b/components/esp_netif/lwip/esp_netif_lwip.c index 1713846e9d..120dd3ab78 100644 --- a/components/esp_netif/lwip/esp_netif_lwip.c +++ b/components/esp_netif/lwip/esp_netif_lwip.c @@ -31,6 +31,7 @@ #include "lwip/priv/tcpip_priv.h" #include "lwip/netif.h" #include "lwip/etharp.h" +#include "lwip/prot/ip4.h" #if CONFIG_ESP_NETIF_BRIDGE_EN #include "netif/bridgeif.h" #endif // CONFIG_ESP_NETIF_BRIDGE_EN @@ -672,6 +673,9 @@ static err_t netif_mld_mac_filter_cb(struct netif *netif, const ip6_addr_t *grou static esp_err_t esp_netif_init_configuration(esp_netif_t *esp_netif, const esp_netif_config_t *cfg) { +#define MAX_MTU_SIZE 9000 /* lwip doesn't have a global maximum, 9000 is selected as a reasonable max MTU for most cases + it is possible to override this upper bound by runtime configuration esp_netif_set_mtu() */ + // Basic esp_netif and lwip is a mandatory configuration and cannot be updated after esp_netif_new() if (cfg == NULL || cfg->base == NULL || cfg->stack == NULL) { return ESP_ERR_ESP_NETIF_INVALID_PARAMS; @@ -703,6 +707,12 @@ static esp_err_t esp_netif_init_configuration(esp_netif_t *esp_netif, const esp_ if (cfg->base->route_prio) { esp_netif->route_prio = cfg->base->route_prio; } + // Store initial MTU preference (applied after netif_add()). 0 keeps stack default. + if (cfg->base->mtu >= IP_HLEN && cfg->base->mtu <= MAX_MTU_SIZE) { + esp_netif->configured_mtu = cfg->base->mtu; + } else { + esp_netif->configured_mtu = 0; + } #if CONFIG_ESP_NETIF_BRIDGE_EN // Setup bridge configuration if the interface is to be bridge @@ -1001,6 +1011,10 @@ static esp_err_t esp_netif_lwip_add(esp_netif_t *esp_netif) #if CONFIG_ESP_NETIF_BRIDGE_EN } #endif // CONFIG_ESP_NETIF_BRIDGE_EN + // Apply configured MTU (if provided) after netif has been added and initialized + if (esp_netif->configured_mtu) { + esp_netif->lwip_netif->mtu = esp_netif->configured_mtu; + } if (esp_netif->driver_set_mac_filter) { #if LWIP_IPV4 && LWIP_IGMP netif_set_igmp_mac_filter(esp_netif->lwip_netif, netif_igmp_mac_filter_cb); @@ -2701,9 +2715,6 @@ esp_err_t esp_netif_get_netif_impl_name(esp_netif_t *esp_netif, char* name) static esp_err_t esp_netif_set_mtu_api(esp_netif_api_msg_t *msg) { esp_netif_t *esp_netif = msg->esp_netif; - if (esp_netif == NULL || esp_netif->lwip_netif == NULL || msg->data == NULL) { - return ESP_ERR_ESP_NETIF_INVALID_PARAMS; - } uint16_t mtu = *(uint16_t *)msg->data; esp_netif->lwip_netif->mtu = mtu; return ESP_OK; @@ -2715,6 +2726,11 @@ esp_err_t esp_netif_set_mtu(esp_netif_t *esp_netif, uint16_t mtu) if (esp_netif == NULL || esp_netif->lwip_netif == NULL) { return ESP_ERR_ESP_NETIF_INVALID_PARAMS; } + /* Validate MTU is at least large enough for IP header */ + if (mtu < IP_HLEN) { + ESP_LOGE(TAG, "MTU is too small, must be at least %d", IP_HLEN); + return ESP_ERR_ESP_NETIF_INVALID_PARAMS; + } return esp_netif_lwip_ipc_call(esp_netif_set_mtu_api, esp_netif, &mtu); } diff --git a/components/esp_netif/lwip/esp_netif_lwip_internal.h b/components/esp_netif/lwip/esp_netif_lwip_internal.h index 332934d3b0..878c2b61bb 100644 --- a/components/esp_netif/lwip/esp_netif_lwip_internal.h +++ b/components/esp_netif/lwip/esp_netif_lwip_internal.h @@ -123,6 +123,8 @@ struct esp_netif_obj { #ifdef CONFIG_ESP_NETIF_SET_DNS_PER_DEFAULT_NETIF ip_addr_t dns[DNS_MAX_SERVERS]; #endif + // initial MTU preference to apply after netif_add(); 0 means use stack default + uint16_t configured_mtu; }; typedef enum esp_netif_set_default_state { diff --git a/components/esp_netif/test_apps/test_app_esp_netif/main/esp_netif_test_lwip.c b/components/esp_netif/test_apps/test_app_esp_netif/main/esp_netif_test_lwip.c index 9dcd8c6623..b6e007154f 100644 --- a/components/esp_netif/test_apps/test_app_esp_netif/main/esp_netif_test_lwip.c +++ b/components/esp_netif/test_apps/test_app_esp_netif/main/esp_netif_test_lwip.c @@ -658,6 +658,48 @@ TEST(esp_netif, set_get_dnsserver) } } +TEST(esp_netif, initial_mtu_config_applied) +{ + // Ensure TCP/IP stack is initialized + test_case_uses_tcpip(); + + // Minimal driver config to satisfy start-time sanity checks + esp_netif_driver_ifconfig_t driver_config = { .handle = (void*)1, .transmit = dummy_transmit }; + + // Case 1: explicit MTU configured + uint16_t mtu_out = 0; + esp_netif_inherent_config_t base1 = { .if_key = "mtu_if0" }; + base1.mtu = 1400; + esp_netif_config_t cfg1 = { + .base = &base1, + .stack = ESP_NETIF_NETSTACK_DEFAULT_WIFI_STA, + .driver = &driver_config, + }; + esp_netif_t *n1 = esp_netif_new(&cfg1); + TEST_ASSERT_NOT_NULL(n1); + esp_netif_action_start(n1, NULL, 0, NULL); + TEST_ASSERT_EQUAL(ESP_OK, esp_netif_get_mtu(n1, &mtu_out)); + TEST_ASSERT_EQUAL_UINT16(1400, mtu_out); + esp_netif_destroy(n1); + + // Case 2: default MTU (0 means use stack default, e.g., 1500) + esp_netif_inherent_config_t base2 = { .if_key = "mtu_if1" }; + // base2.mtu intentionally left 0 + esp_netif_config_t cfg2 = { + .base = &base2, + .stack = ESP_NETIF_NETSTACK_DEFAULT_WIFI_STA, + .driver = &driver_config, + }; + esp_netif_t *n2 = esp_netif_new(&cfg2); + TEST_ASSERT_NOT_NULL(n2); + esp_netif_action_start(n2, NULL, 0, NULL); + TEST_ASSERT_EQUAL(ESP_OK, esp_netif_get_mtu(n2, &mtu_out)); + TEST_ASSERT_EQUAL_UINT16(1500, mtu_out); + struct netif *netif = esp_netif_get_netif_impl(n2); + TEST_ASSERT_EQUAL_UINT16(1500, netif->mtu); + esp_netif_destroy(n2); +} + TEST_GROUP_RUNNER(esp_netif) { /** @@ -686,6 +728,7 @@ TEST_GROUP_RUNNER(esp_netif) RUN_TEST_CASE(esp_netif, dhcp_server_state_transitions_wifi_ap) RUN_TEST_CASE(esp_netif, dhcp_server_state_transitions_mesh) #endif + RUN_TEST_CASE(esp_netif, initial_mtu_config_applied) RUN_TEST_CASE(esp_netif, route_priority) RUN_TEST_CASE(esp_netif, set_get_dnsserver) RUN_TEST_CASE(esp_netif, unified_netif_status_event) diff --git a/examples/protocols/icmp/pmtu_probe/README.md b/examples/protocols/icmp/pmtu_probe/README.md index 763af3e9ef..34219d8143 100644 --- a/examples/protocols/icmp/pmtu_probe/README.md +++ b/examples/protocols/icmp/pmtu_probe/README.md @@ -23,18 +23,30 @@ Notes: 3) Example output: ``` -I (xxx) pmtu_probe: Connecting network... -I (xxx) pmtu_probe: Probing PMTU to host www.espressif.com (IPv4) -I (xxx) pmtu_probe: Search range payload=[0, 1472] -I (xxx) pmtu_probe: Best payload=1392 -> MTU=1420 -I (xxx) pmtu_probe: Applying MTU 1420 to default netif +I (8236) example_common: Connected to example_netif_sta +I (8246) example_common: - IPv4 address: 192.168.0.35, +I (8276) pmtu_probe: Probing PMTU to host www.espressif.com (IPv4) +I (8276) pmtu_probe: Search range payload=[0, 1472] +I (8286) pmtu_probe: Trying payload once with size 736 +I (8436) pmtu_probe: Trying payload once with size 1104 +I (8586) pmtu_probe: Trying payload once with size 1288 +I (8736) pmtu_probe: Trying payload once with size 1380 +I (8886) pmtu_probe: Trying payload once with size 1426 +I (9036) pmtu_probe: Trying payload once with size 1449 +I (9186) pmtu_probe: Trying payload once with size 1461 +I (9336) pmtu_probe: Trying payload once with size 1467 +I (9486) pmtu_probe: Trying payload once with size 1470 +I (9636) pmtu_probe: Trying payload once with size 1471 +I (9786) pmtu_probe: Trying payload once with size 1472 +I (9936) pmtu_probe: Best payload=1472 -> MTU=1500 +I (9936) pmtu_probe: Applying MTU 1500 to default netif +I (9936) main_task: Returned from app_main() ``` ## Implementation details - Uses `esp_ping` (ping_sock) to attempt 1 echo per payload size and waits synchronously for success/timeout. - Bounds the search using the current interface MTU when available, otherwise falls back to `1472`. -- Applies MTU via new API: +- Applies MTU via these API: - `esp_netif_set_mtu(esp_netif_t *netif, uint16_t mtu)` - `esp_netif_get_mtu(esp_netif_t *netif, uint16_t *mtu)` - diff --git a/examples/protocols/icmp/pmtu_probe/main/pmtu_probe_main.c b/examples/protocols/icmp/pmtu_probe/main/pmtu_probe_main.c index 271a4a9c9b..920e43e063 100644 --- a/examples/protocols/icmp/pmtu_probe/main/pmtu_probe_main.c +++ b/examples/protocols/icmp/pmtu_probe/main/pmtu_probe_main.c @@ -47,8 +47,9 @@ static void on_ping_end(esp_ping_handle_t hdl, void *args) } } -static bool try_payload_once(const ip_addr_t *target, uint32_t payload) +static bool try_payload_once(const ip_addr_t *target, uint32_t payload_size) { + ESP_LOGI(TAG, "Trying payload once with size %u", payload_size); probe_ctx_t ctx = { .done = xSemaphoreCreateBinary(), .got_reply = false }; if (!ctx.done) { return false; @@ -58,7 +59,7 @@ static bool try_payload_once(const ip_addr_t *target, uint32_t payload) cfg.count = 1; cfg.timeout_ms = 1000; // 1s timeout cfg.interval_ms = 100; // not used for count=1 - cfg.data_size = payload; + cfg.data_size = payload_size; esp_ping_callbacks_t cbs = { .cb_args = &ctx, .on_ping_success = on_ping_success, @@ -106,7 +107,9 @@ static bool resolve_host(const char *host, ip_addr_t *out) void app_main(void) { ESP_ERROR_CHECK(nvs_flash_init()); + ESP_ERROR_CHECK(esp_netif_init()); ESP_ERROR_CHECK(esp_event_loop_create_default()); + ESP_LOGI(TAG, "Connecting network..."); ESP_ERROR_CHECK(example_connect()); @@ -123,7 +126,7 @@ void app_main(void) return; } - ESP_LOGI(TAG, "Probing PMTU to host %s (IPv4)"); + ESP_LOGI(TAG, "Probing PMTU to host %s (IPv4)", host); uint16_t if_mtu = 0; uint32_t lo = 0;