From f75e39913919331483ab8c2375f453392edaae25 Mon Sep 17 00:00:00 2001 From: David Cermak Date: Mon, 30 Sep 2024 16:16:27 +0200 Subject: [PATCH 1/3] fix(lwip): Fix IP6 raw socket checksum in IPv6-only configuration * Update submodule: git log --oneline f150e232..c816f0ee - sockets: Make IPv6 only netconn type IPv6 aware (espressif/esp-lwip@c816f0ee) - test: Make test application scalable (espressif/esp-lwip@3ec12c3b) - dns: Handle active DNS entries before clearing cache (espressif/esp-lwip@b15cd2de) --- components/lwip/lwip | 2 +- .../icmp_echo/main/echo_example_main.c | 23 +++++++++++-- .../protocols/icmp_echo/pytest_icmp_echo.py | 33 ++++++++++++++++++- .../icmp_echo/sdkconfig.ci.ipv6_only | 6 ++++ 4 files changed, 60 insertions(+), 4 deletions(-) create mode 100644 examples/protocols/icmp_echo/sdkconfig.ci.ipv6_only diff --git a/components/lwip/lwip b/components/lwip/lwip index f150e2321ac0..c816f0ee5cd9 160000 --- a/components/lwip/lwip +++ b/components/lwip/lwip @@ -1 +1 @@ -Subproject commit f150e2321ac09bb0fd35a7fcbc1b116fbf93434e +Subproject commit c816f0ee5cd9dc15143767d0b33aafd39a1a9a3d diff --git a/examples/protocols/icmp_echo/main/echo_example_main.c b/examples/protocols/icmp_echo/main/echo_example_main.c index a300757479c6..67f68093d250 100644 --- a/examples/protocols/icmp_echo/main/echo_example_main.c +++ b/examples/protocols/icmp_echo/main/echo_example_main.c @@ -62,11 +62,16 @@ static void cmd_ping_on_ping_end(esp_ping_handle_t hdl, void *args) } else { loss = 0; } +#ifdef CONFIG_LWIP_IPV4 if (IP_IS_V4(&target_addr)) { printf("\n--- %s ping statistics ---\n", inet_ntoa(*ip_2_ip4(&target_addr))); - } else { + } +#endif +#ifdef CONFIG_LWIP_IPV6 + if (IP_IS_V6(&target_addr)) { printf("\n--- %s ping statistics ---\n", inet6_ntoa(*ip_2_ip6(&target_addr))); } +#endif printf("%" PRIu32 " packets transmitted, %" PRIu32 " received, %" PRIu32 "%% packet loss, time %" PRIu32 "ms\n", transmitted, received, loss, total_time_ms); // delete the ping sessions, so that we clean up all resources and can create a new ping session @@ -81,6 +86,7 @@ static struct { struct arg_int *count; struct arg_int *tos; struct arg_int *ttl; + struct arg_int *interface; struct arg_str *host; struct arg_end *end; } ping_args; @@ -119,6 +125,10 @@ static int do_ping_cmd(int argc, char **argv) config.ttl = (uint32_t)(ping_args.ttl->ival[0]); } + if (ping_args.interface->count > 0) { + config.interface = (uint32_t)(ping_args.interface->ival[0]); + } + // parse IP address struct sockaddr_in6 sock_addr6; ip_addr_t target_addr; @@ -136,13 +146,18 @@ static int do_ping_cmd(int argc, char **argv) printf("ping: unknown host %s\n", ping_args.host->sval[0]); return 1; } +#ifdef CONFIG_LWIP_IPV4 if (res->ai_family == AF_INET) { struct in_addr addr4 = ((struct sockaddr_in *) (res->ai_addr))->sin_addr; inet_addr_to_ip4addr(ip_2_ip4(&target_addr), &addr4); - } else { + } +#endif +#ifdef CONFIG_LWIP_IPV6 + if (res->ai_family == AF_INET6) { struct in6_addr addr6 = ((struct sockaddr_in6 *) (res->ai_addr))->sin6_addr; inet6_addr_to_ip6addr(ip_2_ip6(&target_addr), &addr6); } +#endif freeaddrinfo(res); } config.target_addr = target_addr; @@ -169,6 +184,7 @@ static void register_ping(void) ping_args.count = arg_int0("c", "count", "", "Stop after sending count packets"); ping_args.tos = arg_int0("Q", "tos", "", "Set Type of Service related bits in IP datagrams"); ping_args.ttl = arg_int0("T", "ttl", "", "Set Time to Live related bits in IP datagrams"); + ping_args.interface = arg_int0("I", "interface", "", "Set Interface number"); ping_args.host = arg_str1(NULL, NULL, "", "Host address"); ping_args.end = arg_end(1); const esp_console_cmd_t ping_cmd = { @@ -232,6 +248,9 @@ void app_main(void) /* automatic connection per menuconfig */ ESP_ERROR_CHECK(example_connect()); #endif + struct ifreq ifr; + ESP_ERROR_CHECK(esp_netif_get_netif_impl_name(EXAMPLE_INTERFACE, ifr.ifr_name)); + printf("Connected on interface: %s (%d)", ifr.ifr_name, esp_netif_get_netif_impl_index(EXAMPLE_INTERFACE)); /* register command `ping` */ register_ping(); diff --git a/examples/protocols/icmp_echo/pytest_icmp_echo.py b/examples/protocols/icmp_echo/pytest_icmp_echo.py index d9a73635e136..298e894537de 100644 --- a/examples/protocols/icmp_echo/pytest_icmp_echo.py +++ b/examples/protocols/icmp_echo/pytest_icmp_echo.py @@ -1,5 +1,6 @@ -# SPDX-FileCopyrightText: 2021-2023 Espressif Systems (Shanghai) CO LTD +# SPDX-FileCopyrightText: 2021-2024 Espressif Systems (Shanghai) CO LTD # SPDX-License-Identifier: Apache-2.0 +import logging import os import pytest @@ -56,3 +57,33 @@ def test_protocols_icmp_echo(dut: Dut) -> None: ) def test_protocols_icmp_echo_esp32c2_26mhz(dut: Dut) -> None: _run_test(dut) + + +@pytest.mark.esp32 +@pytest.mark.wifi_router +@pytest.mark.parametrize('config', ['ipv6_only',], indirect=True) +def test_protocols_icmp_echo_ipv6_only(dut: Dut) -> None: + # Parse IP address of STA + logging.info('Waiting to connect with AP') + if dut.app.sdkconfig.get('EXAMPLE_WIFI_SSID_PWD_FROM_STDIN') is True: + dut.expect('Please input ssid password:') + env_name = 'wifi_router' + ap_ssid = get_env_config_variable(env_name, 'ap_ssid') + ap_password = get_env_config_variable(env_name, 'ap_password') + dut.write(f'{ap_ssid} {ap_password}') + # expect all 8 octets from IPv6 (assumes it's printed in the long form) + ipv6_r = r':'.join((r'[0-9a-fA-F]{4}',) * 8) + ipv6 = dut.expect(ipv6_r, timeout=30)[0].decode() + logging.info(f'Connected AP with IPv6={ipv6}') + interface_nr = dut.expect(r'Connected on interface: [a-z]{2}\d \((\d+)\)', timeout=30)[1].decode() + + # ping our own address to simplify things + dut.write('ping -I {} {} -c 5'.format(interface_nr, ipv6)) + + # expect at least two packets (there could be lost packets) + ip = dut.expect(r'64 bytes from ([0-9a-fA-F:]+) icmp_seq=\d ttl=\d+ time=\d+ ms')[1].decode() + dut.expect(fr'64 bytes from {ip} icmp_seq=[2-5] ttl=\d+ time=') + + dut.expect(r'5 packets transmitted, [2-5] received, \d{1,3}% packet loss') + dut.write('') + dut.expect('esp>') diff --git a/examples/protocols/icmp_echo/sdkconfig.ci.ipv6_only b/examples/protocols/icmp_echo/sdkconfig.ci.ipv6_only new file mode 100644 index 000000000000..4d475d3f8df6 --- /dev/null +++ b/examples/protocols/icmp_echo/sdkconfig.ci.ipv6_only @@ -0,0 +1,6 @@ +CONFIG_IDF_TARGET="esp32" +CONFIG_EXAMPLE_WIFI_SSID_PWD_FROM_STDIN=y +CONFIG_EXAMPLE_CONNECT_IPV4=n +CONFIG_EXAMPLE_CONNECT_IPV6=y +CONFIG_LWIP_IPV4=n +CONFIG_LWIP_IPV6=y From e390c17f99c5ab57e40c337dec63d99610c82905 Mon Sep 17 00:00:00 2001 From: David Cermak Date: Tue, 1 Oct 2024 19:33:52 +0200 Subject: [PATCH 2/3] fix(common_connect): Fix example's stdin/out to setup UART interrupt once Function example_configure_stdin_stdout() was used for simple UART I/O operation in CI to enter test env configuration (wifi ssid, IPs, etc). It could be called multiple times, but didn't handle the situation where we install UART interrupt from multiple source (e.g. in ICMP tests, where we first need to enter wifi credentials of test AP and then we start ping-cmd console to handle ping commands) --- .../common_components/protocol_examples_common/stdin_out.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/examples/common_components/protocol_examples_common/stdin_out.c b/examples/common_components/protocol_examples_common/stdin_out.c index 9f3a5163f305..57c61fb8f8fb 100644 --- a/examples/common_components/protocol_examples_common/stdin_out.c +++ b/examples/common_components/protocol_examples_common/stdin_out.c @@ -15,8 +15,7 @@ esp_err_t example_configure_stdin_stdout(void) { - static bool configured = false; - if (configured) { + if (uart_is_driver_installed((uart_port_t)CONFIG_ESP_CONSOLE_UART_NUM)) { return ESP_OK; } // Initialize VFS & UART so we can use std::cout/cin @@ -29,6 +28,5 @@ esp_err_t example_configure_stdin_stdout(void) uart_vfs_dev_port_set_rx_line_endings(CONFIG_ESP_CONSOLE_UART_NUM, ESP_LINE_ENDINGS_CR); /* Move the caret to the beginning of the next line on '\n' */ uart_vfs_dev_port_set_tx_line_endings(CONFIG_ESP_CONSOLE_UART_NUM, ESP_LINE_ENDINGS_CRLF); - configured = true; return ESP_OK; } From 2822cc671a97f575afada55839238b71a8eb1091 Mon Sep 17 00:00:00 2001 From: David Cermak Date: Wed, 2 Oct 2024 11:17:06 +0200 Subject: [PATCH 3/3] fix(lwip): Fix ping_v6 receiving to accept only echo replies This fixes a bug where we ping our own IP and the request itself bounces back to the raw receive function and is incorrectly treated as reply. (this bug was discovered when fixing ICMPv6 pings with incorrect checksums, while the ping request was dropped in icmpv6.c due to wrong checksum, but was also fed to raw layers where it was treated as "correct" response, so the PINGv6 to ourselves still worked) --- components/lwip/apps/ping/ping_sock.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/components/lwip/apps/ping/ping_sock.c b/components/lwip/apps/ping/ping_sock.c index 28cce40ff7d8..fc302cfa7330 100644 --- a/components/lwip/apps/ping/ping_sock.c +++ b/components/lwip/apps/ping/ping_sock.c @@ -133,7 +133,8 @@ static int esp_ping_receive(esp_ping_t *ep) if (IP_IS_V6_VAL(recv_addr)) { // Currently we process IPv6 struct ip6_hdr *iphdr = (struct ip6_hdr *)buf; struct icmp6_echo_hdr *iecho6 = (struct icmp6_echo_hdr *)(buf + sizeof(struct ip6_hdr)); // IPv6 head length is 40 - if ((iecho6->id == ep->packet_hdr->id) && (iecho6->seqno == ep->packet_hdr->seqno)) { + if ((iecho6->type == ICMP6_TYPE_EREP) // only check the ICMPv6 echo reply types + && (iecho6->id == ep->packet_hdr->id) && (iecho6->seqno == ep->packet_hdr->seqno)) { ip_addr_copy(ep->recv_addr, recv_addr); ep->received++; ep->recv_len = IP6H_PLEN(iphdr) - sizeof(struct icmp6_echo_hdr); //The data portion of ICMPv6