From 7ffd2320a7fcdef699c12760a14bc4a6a3b5faef Mon Sep 17 00:00:00 2001 From: Sly Gryphon Date: Fri, 29 Mar 2024 13:16:57 +1000 Subject: [PATCH 1/5] feat(example/protocols): Add DNS fuction to check IPv6 first if relevant If we have an IPv6 global scope address (on any interface), then check IPv6 first, and if that fails fall back to IPv4. Global scope includes ULA (if ULA is configured, similar to private address ranges, DNS is expected). If we don't have a global IPv6, then only check IPv4. --- .../protocol_examples_common/connect.c | 44 +++++++++++++++++++ .../include/protocol_examples_common.h | 9 ++++ 2 files changed, 53 insertions(+) diff --git a/examples/common_components/protocol_examples_common/connect.c b/examples/common_components/protocol_examples_common/connect.c index f6aa9bbee8..9e62db1387 100644 --- a/examples/common_components/protocol_examples_common/connect.c +++ b/examples/common_components/protocol_examples_common/connect.c @@ -18,6 +18,7 @@ #include "freertos/event_groups.h" #include "lwip/err.h" #include "lwip/sys.h" +#include "lwip/netdb.h" static const char *TAG = "example_common"; @@ -136,3 +137,46 @@ esp_err_t example_disconnect(void) #endif return ESP_OK; } + +esp_err_t example_getaddrinfo(const char *nodename, const char *servname, struct addrinfo **res) +{ +#if CONFIG_EXAMPLE_CONNECT_IPV6 + // Iterate over active interfaces, and find if we have any global scope IPv6 + bool has_global_scope_ipv6 = false; + esp_netif_t *netif = NULL; + while ((netif = esp_netif_next_unsafe(netif)) != NULL) { + esp_ip6_addr_t ip6[MAX_IP6_ADDRS_PER_NETIF]; + int ip6_addrs = esp_netif_get_all_ip6(netif, ip6); + for (int j = 0; j < ip6_addrs; ++j) { + // Both global and unique local addresses have global scope. + // ULA assumes either private DNS or NAT66 (same assumpation as IPv4 private address ranges). + esp_ip6_addr_type_t ipv6_type = esp_netif_ip6_get_addr_type(&(ip6[j])); + if (ipv6_type == ESP_IP6_ADDR_IS_GLOBAL || ipv6_type == ESP_IP6_ADDR_IS_UNIQUE_LOCAL) { + has_global_scope_ipv6 = true; + break; + } + } + if (has_global_scope_ipv6) break; + } + + if (has_global_scope_ipv6) { + const struct addrinfo hints6 = { + .ai_family = AF_INET6, + .ai_socktype = SOCK_STREAM, + }; + ESP_LOGI(TAG, "IPv6 DNS lookup"); + int err6 = getaddrinfo(nodename, servname, &hints6, res); + if(err6 == 0) return err6; + ESP_LOGI(TAG, "- IPv6 DNS lookup failed, trying IPv4 lookup"); + } +#endif + + const struct addrinfo hints4 = { + .ai_family = AF_INET, + .ai_socktype = SOCK_STREAM, + }; + ESP_LOGI(TAG, "IPv4 DNS lookup"); + int err4 = getaddrinfo(nodename, servname, &hints4, res); + + return err4; +} diff --git a/examples/common_components/protocol_examples_common/include/protocol_examples_common.h b/examples/common_components/protocol_examples_common/include/protocol_examples_common.h index fc2e54cd35..7db336302b 100644 --- a/examples/common_components/protocol_examples_common/include/protocol_examples_common.h +++ b/examples/common_components/protocol_examples_common/include/protocol_examples_common.h @@ -17,6 +17,7 @@ #include "esp_eth.h" #endif #endif // !CONFIG_IDF_TARGET_LINUX +#include "lwip/netdb.h" #ifdef __cplusplus extern "C" { @@ -77,6 +78,14 @@ esp_err_t example_disconnect(void); */ esp_err_t example_configure_stdin_stdout(void); +/** + * @brief Resolve best destination address based on available source addresses + * + * If a global IPv6 address is available, this tries to get an IPv6 address, + * otherwise it falls back to IPv4. + */ +esp_err_t example_getaddrinfo(const char *nodename, const char *servname, struct addrinfo **res); + /** * @brief Returns esp-netif pointer created by example_connect() described by * the supplied desc field From 5752122050c9f93385334890cac02343d661e193 Mon Sep 17 00:00:00 2001 From: Sly Gryphon Date: Fri, 29 Mar 2024 13:16:57 +1000 Subject: [PATCH 2/5] feat(example/protocols): Enable full IPv6 stack; default wait for any address Enable full IPv6 networking (SLAAC, RDNSS, DHCPv6), so that the example will work properly with IPv6 (and IPv4) if available. However by default wait for any (either IPv4 or IPv6) global address, not both, so the example runs on the first address received. This allows the example to run in IPv4-only, IPv6-only, and dual-stack networks, as we have no way to know which network the user will run the example in we need to support all of them. Config settings can still be used so these defaults can be turned off if you want to run single stack, e.g. Matter devices only require IPv6 so you can save space and effort by using IPv6-only. --- .../Kconfig.projbuild | 26 ++++++++++++++----- .../protocol_examples_common/wifi_connect.c | 24 ++++++++++++++++- .../protocols/http_request/sdkconfig.defaults | 3 +++ 3 files changed, 45 insertions(+), 8 deletions(-) create mode 100644 examples/protocols/http_request/sdkconfig.defaults diff --git a/examples/common_components/protocol_examples_common/Kconfig.projbuild b/examples/common_components/protocol_examples_common/Kconfig.projbuild index a5b654d50e..649e8d611c 100644 --- a/examples/common_components/protocol_examples_common/Kconfig.projbuild +++ b/examples/common_components/protocol_examples_common/Kconfig.projbuild @@ -384,16 +384,29 @@ menu "Example Connection Configuration" select LWIP_IPV6 select LWIP_PPP_ENABLE_IPV6 if EXAMPLE_CONNECT_PPP help - By default, examples will wait until IPv4 and IPv6 local link addresses are obtained. - Disable this option if the network does not support IPv6. - Choose the preferred IPv6 address type if the connection code should wait until other than - the local link address gets assigned. - Consider enabling IPv6 stateless address autoconfiguration (SLAAC) in the LWIP component. + By default, examples will wait until any IP preferred (usually global) address is obtained, + allowing flexible deployment to IPv4-only, IPv6-only, or dual-stack networks. + + Enables SLAAC (State-Less Address Auto-Configuration) to get IPv6 + address prefixes from Router Advertisement (RA) messages, and both + RA RDNSS (Recursive DNS Server) and DHCPv6 Stateless mode to get IPv6 DNS + server details from whichever is available on the network. if EXAMPLE_CONNECT_IPV6 + config EXAMPLE_CONNECT_PREF_ANY + bool "Obtain any preferred address" + default y + help + By default, examples will wait until any IP preferred address is obtained, either an IPv4 or + IPv6 global scope addresses. + This enables flexible deployment into any network type. + Disable this option to wait for both address types, although this will break single-stack networks. + Choose the preferred IPv6 address type if the connection code should wait until other than + an IPv6 global address. + choice EXAMPLE_CONNECT_PREFERRED_IPV6 prompt "Preferred IPv6 Type" - default EXAMPLE_CONNECT_IPV6_PREF_LOCAL_LINK + default EXAMPLE_CONNECT_IPV6_PREF_GLOBAL help Select which kind of IPv6 address the connect logic waits for. @@ -421,5 +434,4 @@ menu "Example Connection Configuration" endif - endmenu diff --git a/examples/common_components/protocol_examples_common/wifi_connect.c b/examples/common_components/protocol_examples_common/wifi_connect.c index e4e1468688..0376080c18 100644 --- a/examples/common_components/protocol_examples_common/wifi_connect.c +++ b/examples/common_components/protocol_examples_common/wifi_connect.c @@ -23,8 +23,11 @@ static const char *TAG = "example_connect"; static esp_netif_t *s_example_sta_netif = NULL; static SemaphoreHandle_t s_semph_get_ip_addrs = NULL; #if CONFIG_EXAMPLE_CONNECT_IPV6 +#if CONFIG_EXAMPLE_CONNECT_PREF_ANY +#else static SemaphoreHandle_t s_semph_get_ip6_addrs = NULL; #endif +#endif #if CONFIG_EXAMPLE_WIFI_SCAN_METHOD_FAST #define EXAMPLE_WIFI_SCAN_METHOD WIFI_FAST_SCAN @@ -71,9 +74,12 @@ static void example_handler_on_wifi_disconnect(void *arg, esp_event_base_t event xSemaphoreGive(s_semph_get_ip_addrs); } #if CONFIG_EXAMPLE_CONNECT_IPV6 +#if CONFIG_EXAMPLE_CONNECT_PREF_ANY +#else if (s_semph_get_ip6_addrs) { xSemaphoreGive(s_semph_get_ip6_addrs); } +#endif #endif return; } @@ -102,6 +108,7 @@ static void example_handler_on_sta_got_ip(void *arg, esp_event_base_t event_base return; } ESP_LOGI(TAG, "Got IPv4 event: Interface \"%s\" address: " IPSTR, esp_netif_get_desc(event->esp_netif), IP2STR(&event->ip_info.ip)); + if (s_semph_get_ip_addrs) { xSemaphoreGive(s_semph_get_ip_addrs); } else { @@ -122,10 +129,15 @@ static void example_handler_on_sta_got_ipv6(void *arg, esp_event_base_t event_ba IPV62STR(event->ip6_info.ip), example_ipv6_addr_types_to_str[ipv6_type]); if (ipv6_type == EXAMPLE_CONNECT_PREFERRED_IPV6_TYPE) { +#if CONFIG_EXAMPLE_CONNECT_PREF_ANY + if (s_semph_get_ip_addrs) { + xSemaphoreGive(s_semph_get_ip_addrs); +#else if (s_semph_get_ip6_addrs) { xSemaphoreGive(s_semph_get_ip6_addrs); +#endif } else { - ESP_LOGI(TAG, "- IPv6 address: " IPV6STR ", type: %s", IPV62STR(event->ip6_info.ip), example_ipv6_addr_types_to_str[ipv6_type]); + ESP_LOGI(TAG, "- IPv6 address: " IPV6STR ", type: %s", IPV62STR(event->ip6_info.ip), example_ipv6_addr_types_to_str[ipv6_type]); } } } @@ -172,11 +184,14 @@ esp_err_t example_wifi_sta_do_connect(wifi_config_t wifi_config, bool wait) return ESP_ERR_NO_MEM; } #if CONFIG_EXAMPLE_CONNECT_IPV6 +#if CONFIG_EXAMPLE_CONNECT_PREF_ANY +#else s_semph_get_ip6_addrs = xSemaphoreCreateBinary(); if (s_semph_get_ip6_addrs == NULL) { vSemaphoreDelete(s_semph_get_ip_addrs); return ESP_ERR_NO_MEM; } +#endif #endif } s_retry_num = 0; @@ -196,11 +211,15 @@ esp_err_t example_wifi_sta_do_connect(wifi_config_t wifi_config, bool wait) } if (wait) { ESP_LOGI(TAG, "Waiting for IP(s)"); +#if CONFIG_EXAMPLE_CONNECT_PREF_ANY + xSemaphoreTake(s_semph_get_ip_addrs, portMAX_DELAY); +#else #if CONFIG_EXAMPLE_CONNECT_IPV4 xSemaphoreTake(s_semph_get_ip_addrs, portMAX_DELAY); #endif #if CONFIG_EXAMPLE_CONNECT_IPV6 xSemaphoreTake(s_semph_get_ip6_addrs, portMAX_DELAY); +#endif #endif if (s_retry_num > CONFIG_EXAMPLE_WIFI_CONN_MAX_RETRY) { return ESP_FAIL; @@ -221,9 +240,12 @@ esp_err_t example_wifi_sta_do_disconnect(void) vSemaphoreDelete(s_semph_get_ip_addrs); } #if CONFIG_EXAMPLE_CONNECT_IPV6 +#if CONFIG_EXAMPLE_CONNECT_PREF_ANY +#else if (s_semph_get_ip6_addrs) { vSemaphoreDelete(s_semph_get_ip6_addrs); } +#endif #endif return esp_wifi_disconnect(); } diff --git a/examples/protocols/http_request/sdkconfig.defaults b/examples/protocols/http_request/sdkconfig.defaults new file mode 100644 index 0000000000..5120259f7e --- /dev/null +++ b/examples/protocols/http_request/sdkconfig.defaults @@ -0,0 +1,3 @@ +CONFIG_LWIP_IPV6_AUTOCONFIG=y +CONFIG_LWIP_IPV6_DHCP6=y +CONFIG_LWIP_IPV6_RDNSS_MAX_DNS_SERVERS=2 From f4cf43525545a82fb1e65b784b05c68d58a89c35 Mon Sep 17 00:00:00 2001 From: Sly Gryphon Date: Fri, 29 Mar 2024 13:16:57 +1000 Subject: [PATCH 3/5] feat(example/protocols): Update http_request to support all networks --- examples/protocols/README.md | 24 +- examples/protocols/http_request/README.md | 270 +++++++++++++----- .../main/http_request_example_main.c | 38 ++- 3 files changed, 249 insertions(+), 83 deletions(-) diff --git a/examples/protocols/README.md b/examples/protocols/README.md index c8088b0259..a79277b911 100644 --- a/examples/protocols/README.md +++ b/examples/protocols/README.md @@ -20,6 +20,26 @@ When connecting using Wi-Fi, enter SSID and password of your Wi-Fi access point When connecting using Ethernet, set up PHY type and configuration in the provided fields. If using Ethernet for the first time, it is recommended to start with the [Ethernet example readme](../ethernet/basic/README.md), which contains instructions for connecting and configuring the PHY. Once Ethernet example obtains IP address successfully, proceed to the protocols example and set the same configuration options. -### Disabling IPv6 +### IP protocol handling -By default, `example_connect()` function waits until Wi-Fi or Ethernet connection is established, and IPv4 address and IPv6 link-local address are obtained. In network environments where IPv6 link-local address cannot be obtained, disable "Obtain IPv6 link-local address" option found in "Example Connection Configuration" menu. +By default the examples support any network configuration: IPv4 only, IPv6 only, and dual stack. + +The `example_connect()` function waits until Wi-Fi or Ethernet connection is established, and either an IPv4 address or an IPv6 global scope address is obtained. By waiting for the first (of either), this allows it to work in any network. + +The behaviour is based on the available network: + +| Network | Addresses | DNS | IPv4 destination | IPv6 destination | Dual-stack destination +| -- | -- | -- | -- | -- | -- +| IPv4 only | IPv4, IPv6 link-local | IPv4 | Yes | Not possible | Uses IPv4 +| IPv6 only | IPv6 global, IPv6 link-local | IPv6 | NAT64 if available | Yes | Uses IPv6 +| Dual-stack | IPv6 global (*1), IPv4, IPv6 link-local | IPv6, then IPv4 | Yes | Yes | Uses IPv6 + +(*1) There may be a delay waiting for an IPv6 global address, in which case IPv4 may be used for the first test request (with IPv4 DNS) + +#### Disabling protocol versions + +Where the target network is unknown, such as this sample, using an adaptable configuration that supports all networks (whatever is available) is best. + +However in some cases you may want to restrict network configuration. + +For example the Matter standard requires IPv6, so you can reduce application size and simplify development and testing complexity by completely disabling IPv4 (`LWIP_IPV4`), as it is never needed for a Matter device. diff --git a/examples/protocols/http_request/README.md b/examples/protocols/http_request/README.md index ce6795556d..430eed1152 100644 --- a/examples/protocols/http_request/README.md +++ b/examples/protocols/http_request/README.md @@ -22,6 +22,10 @@ idf.py menuconfig ``` Open the project configuration menu (`idf.py menuconfig`) to configure Wi-Fi or Ethernet. See "Establishing Wi-Fi or Ethernet Connection" section in [examples/protocols/README.md](../../README.md) for more details. +The default configuration (`sdkconfig.defaults`) fully enables both IPv6 and IPv4: it enables SLAAC (State-Less Address Auto-Configuration) to get IPv6 address prefixes from Router Advertisement (RA) messages, and both IPv6 RA RDNSS (Recursive DNS Server) and DHCPv6 Stateless mode to get IPv6 DNS server details from whichever is available on the network. + +The application is written to work on whatever network is available: dual-stack, IPv4-only, and IPv6-only networks. + ### Build and Flash Build the project and flash it to the board, then run monitor tool to view serial output: @@ -38,77 +42,207 @@ See the Getting Started Guide for full steps to configure and use ESP-IDF to bui ## Example Output +### Dual-stack network connecting to dual-stack server + +Default configuration on a dual-stack network (Astral) connecting to a dual-stack server (`v4v6.ipv6-test.com`), starts the connection test as soon as any address, e.g. an IPv4 address (`192.168.1.146`, private range) is received. So the first connection does an IPv4 DNS lookup (`51.75.78.103`) and uses that address. + +Note that the server responds with the NAT44 public address it sees for the client (`220.240.255.134`). + +When an IPv6 prefix (`2407:8800:bc61:1340::`) is then receive via RA (router advertisement) a global IPv6 address is configured, so the second connection test does a DNS lookup for IPv6 first, and uses that address (`2001:41D0:701:1100::29C8`). + +The server then responds with the IPv6 address seen: `2407:8800:bc61:1340:0a3a:f2ff:fe65:db28` + ``` -I (10557) example_connect: - IPv4 address: 192.168.194.219 -I (10557) example_connect: - IPv6 address: fe80:0000:0000:0000:266f:28ff:fe80:2c74, type: ESP_IP6_ADDR_IS_LINK_LOCAL -W (10577) wifi:idx:0 (ifx:0, ee:6d:19:60:f6:0e), tid:0, ssn:3, winSize:64 -I (10587) example: DNS lookup succeeded. IP=93.184.216.34 -I (10587) example: ... allocated socket -I (10917) example: ... connected -I (10917) example: ... socket send success -I (10927) example: ... set socket receiving timeout success -HTTP/1.0 200 OK -Age: 317271 -Cache-Control: max-age=604800 -Content-Type: text/html; charset=UTF-8 -Date: Mon, 06 Sep 2021 08:09:49 GMT -Etag: "3147526947+ident" -Expires: Mon, 13 Sep 2021 08:09:49 GMT -Last-Modified: Thu, 17 Oct 2019 07:18:26 GMT -Server: ECS (nyb/1D2B) +I (764) example_connect: Connecting to Astral... +I (764) example_connect: Waiting for IP(s) +I (3174) wifi:new:<11,0>, old:<1,0>, ap:<255,255>, sta:<11,0>, prof:1 +I (3424) wifi:state: init -> auth (b0) +I (3434) wifi:state: auth -> assoc (0) +I (3444) wifi:state: assoc -> run (10) +I (3464) wifi:connected with Astral, aid = 8, channel 11, BW20, bssid = f4:92:bf:a8:db:d1 +I (3464) wifi:security: WPA2-PSK, phy: bgn, rssi: -70 +I (3464) wifi:pm start, type: 1 + +I (3464) wifi:dp: 1, bi: 102400, li: 3, scale listen interval from 307200 us to 307200 us +I (3494) wifi:AP's beacon interval = 102400 us, DTIM period = 1 +I (3514) wifi:idx:0 (ifx:0, f4:92:bf:a8:db:d1), tid:6, ssn:1, winSize:64 +I (4474) esp_netif_handlers: example_netif_sta ip: 192.168.1.146, mask: 255.255.255.0, gw: 192.168.1.1 +I (4474) example_connect: Got IPv4 event: Interface "example_netif_sta" address: 192.168.1.146 +I (4484) example_common: Connected to example_netif_sta +I (4484) example_common: - IPv4 address: 192.168.1.146, +I (4494) example_common: - IPv6 address: fe80:0000:0000:0000:0a3a:f2ff:fe65:db28, type: ESP_IP6_ADDR_IS_LINK_LOCAL +I (4504) example: DNS lookup v4v6.ipv6-test.com (port 80) +I (4514) example_common: IPv4 DNS lookup +I (4514) main_task: Returned from app_main() +I (4544) wifi:idx:1 (ifx:0, f4:92:bf:a8:db:d1), tid:0, ssn:1, winSize:64 +I (4554) example: DNS lookup succeeded. (2) IP=51.75.78.103 +I (4554) example: ... allocated socket +I (4564) example_connect: Got IPv6 event: Interface "example_netif_sta" address: fe80:0000:0000:0000:0a3a:f2ff:fe65:db28, type: ESP_IP6_ADDR_IS_LINK_LOCAL +I (5044) example: ... connected +I (5044) example: ... socket send success +I (5054) example: ... set socket receiving timeout success +HTTP/1.1 200 OK +Date: Sat, 24 Feb 2024 05:33:08 GMT +Server: Apache/2.4.25 (Debian) Vary: Accept-Encoding -X-Cache: HIT -Content-Length: 1256 Connection: close +Content-Type: text/html; charset=UTF-8 - - - - Example Domain +220.240.255.134 +I (5464) example: ... done reading from socket. Last read return=0 errno=128. +I (5474) example: 10... +I (6474) example: 9... +I (7474) example: 8... +I (7564) example_connect: Got IPv6 event: Interface "example_netif_sta" address: 2407:8800:bc61:1340:0a3a:f2ff:fe65:db28, type: ESP_IP6_ADDR_IS_GLOBAL +I (7564) example_connect: Got IPv6 event: Interface "example_netif_sta" address: fd7c:e25e:67e8:0040:0a3a:f2ff:fe65:db28, type: ESP_IP6_ADDR_IS_UNIQUE_LOCAL +I (8474) example: 7... +I (9474) example: 6... +I (10474) example: 5... +I (11474) example: 4... +I (12474) example: 3... +I (13474) example: 2... +I (14474) example: 1... +I (15474) example: 0... +I (16474) example: Starting again! +I (16474) example: DNS lookup v4v6.ipv6-test.com (port 80) +I (16474) example_common: IPv6 DNS lookup +I (16514) example: DNS lookup succeeded. (10) IP=2001:41D0:701:1100::29C8 +I (16514) example: ... allocated socket +I (16924) example: ... connected +I (16924) example: ... socket send success +I (16924) example: ... set socket receiving timeout success +HTTP/1.1 200 OK +Date: Sat, 24 Feb 2024 05:33:19 GMT +Server: Apache/2.4.25 (Debian) +Vary: Accept-Encoding +Connection: close +Content-Type: text/html; charset=UTF-8 - - - - - - - -
-

Example Domain

-

This domain is for use in illustrative examples in documents. You may use this - domain in literature without prior coordination or asking for permission.

-

More information...

-
- - -I (11467) example: ... done reading from socket. Last read return=0 errno=128. -I (11477) example: 10... -I (12477) example: 9... -I (13477) example: 8... +2407:8800:bc61:1340:a3a:f2ff:fe65:db28 +I (17334) example: ... done reading from socket. Last read return=0 errno=128. +I (17344) example: 10... +``` + +On a dual-stack network when connecting to an IPv4-only server, then (with an IPv6 global address) an IPv6 lookup is still attempted first. If DNS64/NAT64 is not available, then the IPv6 DNS lookup will fail, and the code will fall back to an IPv4 DNS lookup. + +``` +I (17034) example: DNS lookup v4.ipv6-test.com (port 80) +I (17034) example_common: IPv6 DNS lookup +I (17074) example_common: - IPv6 DNS lookup failed, trying IPv4 lookup +I (17074) example_common: IPv4 DNS lookup +I (17074) example: DNS lookup succeeded. (2) IP=51.75.78.103 +I (17084) example: ... allocated socket +I (17694) example: ... connected +I (17694) example: ... socket send success +I (17694) example: ... set socket receiving timeout success +HTTP/1.1 200 OK +Date: Sat, 24 Feb 2024 06:10:00 GMT +Server: Apache/2.4.25 (Debian) +Vary: Accept-Encoding +Connection: close +Content-Type: text/html; charset=UTF-8 + +220.240.255.134 +I (18314) example: ... done reading from socket. Last read return=0 errno=128. +I (18314) example: 10... +``` + +### IPv4-only network connecting to dual-stack server + +This shows an IPv4 only network (Shadow) connecting to a dual-stack server (`v4v6.ipv6-test.com`). + +The connection attempt starts as soon as the IPv4 address is received, and as the device has no global IPv6 address, the DNS lookup and connection will only be IPv4 (`51.75.78.103`). + +Note that, although the device has an IPv6 link-local address, it is ignored because it cannot be used to connect to a public address returned from DNS. + +Although we only get a private IPv4 address, the assumption is that IPv4 DNS lookups are still usable, either for private DNS or via NAT44, e.g. we see the server responds with the NAT44 address `220.240.255.134`. (A similar assumption is made for IPv6 Unique Local Addresses) + +``` +I (764) example_connect: Connecting to Shadow... +I (764) example_connect: Waiting for IP(s) +I (3174) wifi:new:<1,0>, old:<1,0>, ap:<255,255>, sta:<1,0>, prof:1 +I (3424) wifi:state: init -> auth (b0) +I (3524) wifi:state: auth -> assoc (0) +I (3554) wifi:state: assoc -> run (10) +I (3584) wifi:connected with Shadow, aid = 1, channel 1, BW20, bssid = 06:25:9c:13:92:ab +I (3584) wifi:security: WPA2-PSK, phy: bgn, rssi: -56 +I (3584) wifi:pm start, type: 1 + +I (3594) wifi:dp: 1, bi: 102400, li: 3, scale listen interval from 307200 us to 307200 us +I (3634) wifi:dp: 2, bi: 102400, li: 4, scale listen interval from 307200 us to 409600 us +I (3634) wifi:AP's beacon interval = 102400 us, DTIM period = 2 +I (5104) esp_netif_handlers: example_netif_sta ip: 192.168.5.146, mask: 255.255.255.0, gw: 192.168.5.1 +I (5104) example_connect: Got IPv4 event: Interface "example_netif_sta" address: 192.168.5.146 +I (5114) example_common: Connected to example_netif_sta +I (5114) example_common: - IPv4 address: 192.168.5.146, +I (5124) example_common: - IPv6 address: fe80:0000:0000:0000:0a3a:f2ff:fe65:db28, type: ESP_IP6_ADDR_IS_LINK_LOCAL +I (5134) example: DNS lookup v4v6.ipv6-test.com (port 80) +I (5144) example_common: IPv4 DNS lookup +I (5144) main_task: Returned from app_main() +I (5564) example_connect: Got IPv6 event: Interface "example_netif_sta" address: fe80:0000:0000:0000:0a3a:f2ff:fe65:db28, type: ESP_IP6_ADDR_IS_LINK_LOCAL +I (5584) example: DNS lookup succeeded. (2) IP=51.75.78.103 +I (5584) example: ... allocated socket +I (5914) example: ... connected +I (5914) example: ... socket send success +I (5914) example: ... set socket receiving timeout success +HTTP/1.1 200 OK +Date: Sat, 24 Feb 2024 05:54:34 GMT +Server: Apache/2.4.25 (Debian) +Vary: Accept-Encoding +Connection: close +Content-Type: text/html; charset=UTF-8 + +220.240.255.134 +I (6414) example: ... done reading from socket. Last read return=0 errno=128. +I (6424) example: 10... +``` + +### IPv6-only network, with NAT64, connecting to an IPv4 server + +This connects to an IPv6-only network (Wildspace), and waits to receive an IPv6 global scope address before starting the connection test. The default configurations waits for the first public usable address, either IPv4 or IPv6, so works in any network. + +The DNS lookup for an IPv4-only server (`v4.ipv6-test.com`) returns a DNS64 address (`64:FF9B::334B:4E67`), which is then used for the HTTP connection. + +Note that the server responds with the NAT64 public IPv4 address it sees for the client (`220.240.255.134`). + +``` +I (764) example_connect: Connecting to Wildspace... +I (764) example_connect: Waiting for IP(s) +I (3174) wifi:new:<11,0>, old:<1,0>, ap:<255,255>, sta:<11,0>, prof:1 +I (3424) wifi:state: init -> auth (b0) +I (3444) wifi:state: auth -> assoc (0) +I (3464) wifi:state: assoc -> run (10) +I (3504) wifi:connected with Wildspace, aid = 1, channel 11, BW20, bssid = ea:63:da:bd:5a:09 +I (3514) wifi:security: WPA2-PSK, phy: bgn, rssi: -80 +I (3514) wifi:pm start, type: 1 + +I (3514) wifi:dp: 1, bi: 102400, li: 3, scale listen interval from 307200 us to 307200 us +I (3634) wifi:AP's beacon interval = 102400 us, DTIM period = 1 +I (4564) example_connect: Got IPv6 event: Interface "example_netif_sta" address: fe80:0000:0000:0000:0a3a:f2ff:fe65:db28, type: ESP_IP6_ADDR_IS_LINK_LOCAL +I (5584) wifi:idx:0 (ifx:0, ea:63:da:bd:5a:09), tid:0, ssn:0, winSize:64 +I (7564) example_connect: Got IPv6 event: Interface "example_netif_sta" address: 2407:8800:bc61:1300:0a3a:f2ff:fe65:db28, type: ESP_IP6_ADDR_IS_GLOBAL +I (7564) example_connect: Got IPv6 event: Interface "example_netif_sta" address: fd7c:e25e:67e8:0000:0a3a:f2ff:fe65:db28, type: ESP_IP6_ADDR_IS_UNIQUE_LOCAL +I (7574) example_common: Connected to example_netif_sta +I (7584) example_common: - IPv4 address: 0.0.0.0, +I (7594) example_common: - IPv6 address: fe80:0000:0000:0000:0a3a:f2ff:fe65:db28, type: ESP_IP6_ADDR_IS_LINK_LOCAL +I (7604) example_common: - IPv6 address: 2407:8800:bc61:1300:0a3a:f2ff:fe65:db28, type: ESP_IP6_ADDR_IS_GLOBAL +I (7614) example_common: - IPv6 address: fd7c:e25e:67e8:0000:0a3a:f2ff:fe65:db28, type: ESP_IP6_ADDR_IS_UNIQUE_LOCAL +I (7624) example: DNS lookup v4.ipv6-test.com (port 80) +I (7624) example_common: IPv6 DNS lookup +I (7634) main_task: Returned from app_main() +I (8584) example: DNS lookup succeeded. (10) IP=64:FF9B::334B:4E67 +I (8584) example: ... allocated socket +I (9064) example: ... connected +I (9064) example: ... socket send success +I (9064) example: ... set socket receiving timeout success +HTTP/1.1 200 OK +Date: Sat, 24 Feb 2024 05:45:33 GMT +Server: Apache/2.4.25 (Debian) +Vary: Accept-Encoding +Connection: close +Content-Type: text/html; charset=UTF-8 + +220.240.255.134 +I (9474) example: ... done reading from socket. Last read return=0 errno=128. +I (9484) example: 10... ``` diff --git a/examples/protocols/http_request/main/http_request_example_main.c b/examples/protocols/http_request/main/http_request_example_main.c index fdcefda9e6..2c989bc346 100644 --- a/examples/protocols/http_request/main/http_request_example_main.c +++ b/examples/protocols/http_request/main/http_request_example_main.c @@ -24,9 +24,14 @@ #include "sdkconfig.h" /* Constants that aren't configurable in menuconfig */ -#define WEB_SERVER "example.com" +// Default example will work on all networks: IPv4 only, IPv6 only, and dual stack +#define WEB_SERVER "v4v6.ipv6-test.com" +// Alternative tests for IPv4 only and IPv6 only destinations +//#define WEB_SERVER "v4.ipv6-test.com" +//#define WEB_SERVER "v6.ipv6-test.com" #define WEB_PORT "80" -#define WEB_PATH "/" +// This will reflect the IP address the server sees, e.g. NAT64 will return the public IPv4 +#define WEB_PATH "/api/myip.php" static const char *TAG = "example"; @@ -37,29 +42,35 @@ static const char *REQUEST = "GET " WEB_PATH " HTTP/1.0\r\n" static void http_get_task(void *pvParameters) { - const struct addrinfo hints = { - .ai_family = AF_INET, - .ai_socktype = SOCK_STREAM, - }; struct addrinfo *res; - struct in_addr *addr; + void *addr; int s, r; char recv_buf[64]; while(1) { - int err = getaddrinfo(WEB_SERVER, WEB_PORT, &hints, &res); - + ESP_LOGI(TAG, "DNS lookup %s (port %s)", WEB_SERVER, WEB_PORT); + int err = example_getaddrinfo(WEB_SERVER, WEB_PORT, &res); if(err != 0 || res == NULL) { ESP_LOGE(TAG, "DNS lookup failed err=%d res=%p", err, res); vTaskDelay(1000 / portTICK_PERIOD_MS); continue; } - /* Code to print the resolved IP. - - Note: inet_ntoa is non-reentrant, look at ipaddr_ntoa_r for "real" code */ + /* Code to print the resolved IP. */ +#if LWIP_IPV4 && LWIP_IPV6 + if (res->ai_family == AF_INET6) { + addr = &((struct sockaddr_in6 *)res->ai_addr)->sin6_addr; + } else { + addr = &((struct sockaddr_in *)res->ai_addr)->sin_addr; + } +#elif LWIP_IPV6 + addr = &((struct sockaddr_in6 *)res->ai_addr)->sin6_addr; +#elif LWIP_IPV4 addr = &((struct sockaddr_in *)res->ai_addr)->sin_addr; - ESP_LOGI(TAG, "DNS lookup succeeded. IP=%s", inet_ntoa(*addr)); +#endif + char ipStr[128]; + inet_ntop(res->ai_family, addr, ipStr, sizeof(ipStr)); + ESP_LOGI(TAG, "DNS lookup succeeded. (%i) IP=%s", res->ai_family, ipStr); s = socket(res->ai_family, res->ai_socktype, 0); if(s < 0) { @@ -109,6 +120,7 @@ static void http_get_task(void *pvParameters) putchar(recv_buf[i]); } } while(r > 0); + putchar('\n'); ESP_LOGI(TAG, "... done reading from socket. Last read return=%d errno=%d.", r, errno); close(s); From 3b8596c862dd4ce5de09c18d7bc8c43f63137839 Mon Sep 17 00:00:00 2001 From: Sly Gryphon Date: Fri, 29 Mar 2024 13:16:57 +1000 Subject: [PATCH 4/5] feat(example/protocols): Add default wait for any address to eth_connect --- .../protocol_examples_common/eth_connect.c | 21 +++++++++++++++++++ .../protocol_examples_common/ppp_connect.c | 1 + 2 files changed, 22 insertions(+) diff --git a/examples/common_components/protocol_examples_common/eth_connect.c b/examples/common_components/protocol_examples_common/eth_connect.c index 72cfa8987b..7046cb4af2 100644 --- a/examples/common_components/protocol_examples_common/eth_connect.c +++ b/examples/common_components/protocol_examples_common/eth_connect.c @@ -22,8 +22,11 @@ static const char *TAG = "ethernet_connect"; static SemaphoreHandle_t s_semph_get_ip_addrs = NULL; #if CONFIG_EXAMPLE_CONNECT_IPV6 +#if CONFIG_EXAMPLE_CONNECT_PREF_ANY +#else static SemaphoreHandle_t s_semph_get_ip6_addrs = NULL; #endif +#endif static esp_netif_t *eth_start(void); static void eth_stop(void); @@ -55,7 +58,11 @@ static void eth_on_got_ipv6(void *arg, esp_event_base_t event_base, ESP_LOGI(TAG, "Got IPv6 event: Interface \"%s\" address: " IPV6STR ", type: %s", esp_netif_get_desc(event->esp_netif), IPV62STR(event->ip6_info.ip), example_ipv6_addr_types_to_str[ipv6_type]); if (ipv6_type == EXAMPLE_CONNECT_PREFERRED_IPV6_TYPE) { +#if CONFIG_EXAMPLE_CONNECT_PREF_ANY + xSemaphoreGive(s_semph_get_ip_addrs); +#else xSemaphoreGive(s_semph_get_ip6_addrs); +#endif } } @@ -206,14 +213,23 @@ void example_ethernet_shutdown(void) vSemaphoreDelete(s_semph_get_ip_addrs); s_semph_get_ip_addrs = NULL; #if CONFIG_EXAMPLE_CONNECT_IPV6 +#if CONFIG_EXAMPLE_CONNECT_PREF_ANY +#else vSemaphoreDelete(s_semph_get_ip6_addrs); s_semph_get_ip6_addrs = NULL; +#endif #endif eth_stop(); } esp_err_t example_ethernet_connect(void) { +#if CONFIG_EXAMPLE_CONNECT_PREF_ANY + s_semph_get_ip_addrs = xSemaphoreCreateBinary(); + if (s_semph_get_ip_addrs == NULL) { + return ESP_ERR_NO_MEM; + } +#else #if CONFIG_EXAMPLE_CONNECT_IPV4 s_semph_get_ip_addrs = xSemaphoreCreateBinary(); if (s_semph_get_ip_addrs == NULL) { @@ -226,14 +242,19 @@ esp_err_t example_ethernet_connect(void) vSemaphoreDelete(s_semph_get_ip_addrs); return ESP_ERR_NO_MEM; } +#endif #endif eth_start(); ESP_LOGI(TAG, "Waiting for IP(s)."); +#if CONFIG_EXAMPLE_CONNECT_PREF_ANY + xSemaphoreTake(s_semph_get_ip_addrs, portMAX_DELAY); +#else #if CONFIG_EXAMPLE_CONNECT_IPV4 xSemaphoreTake(s_semph_get_ip_addrs, portMAX_DELAY); #endif #if CONFIG_EXAMPLE_CONNECT_IPV6 xSemaphoreTake(s_semph_get_ip6_addrs, portMAX_DELAY); +#endif #endif return ESP_OK; } diff --git a/examples/common_components/protocol_examples_common/ppp_connect.c b/examples/common_components/protocol_examples_common/ppp_connect.c index 090a3ed69c..7cec0ec03b 100644 --- a/examples/common_components/protocol_examples_common/ppp_connect.c +++ b/examples/common_components/protocol_examples_common/ppp_connect.c @@ -230,6 +230,7 @@ esp_err_t example_ppp_connect(void) #endif // CONNECT_PPP_DEVICE ESP_LOGI(TAG, "Waiting for IP address"); + // Note: CONFIG_EXAMPLE_CONNECT_PREF_ANY is ignored; PPP always waits for ANY (does not wait for all) EventBits_t bits = xEventGroupWaitBits(s_event_group, CONNECT_BITS, pdFALSE, pdFALSE, portMAX_DELAY); if (bits & CONNECTION_FAILED) { ESP_LOGE(TAG, "Connection failed!"); From 55880ab766521eb8615aee85456224931c63b664 Mon Sep 17 00:00:00 2001 From: Sly Gryphon Date: Fri, 29 Mar 2024 13:16:57 +1000 Subject: [PATCH 5/5] fix(ppp): Only call peer DNS function if configured The ppp_set_usepeerdns macro is only defined if both flags are enabled, so breaks compilation trying to call it if not. Fix is to add the same flags around the call, so the code now compiles. --- components/esp_netif/lwip/esp_netif_lwip_ppp.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/components/esp_netif/lwip/esp_netif_lwip_ppp.c b/components/esp_netif/lwip/esp_netif_lwip_ppp.c index db5e9f44f5..083ee4bdf1 100644 --- a/components/esp_netif/lwip/esp_netif_lwip_ppp.c +++ b/components/esp_netif/lwip/esp_netif_lwip_ppp.c @@ -230,7 +230,11 @@ netif_related_data_t * esp_netif_new_ppp(esp_netif_t *esp_netif, const esp_netif #if PPP_NOTIFY_PHASE ppp_set_notify_phase_callback(ppp_obj->ppp, on_ppp_notify_phase); #endif +#if PPP_IPV4_SUPPORT +#if LWIP_DNS ppp_set_usepeerdns(ppp_obj->ppp, 1); +#endif /* LWIP_DNS */ +#endif /* PPP_IPV4_SUPPORT */ return (netif_related_data_t *)ppp_obj; }