From 8287cf8b97f0d795b2fc597fe3fc889641ce7a99 Mon Sep 17 00:00:00 2001 From: Sly Gryphon Date: Mon, 1 Apr 2024 18:09:20 +1000 Subject: [PATCH] fix(dns): Fix IPv6 only networks by preferencing IPv6 if you have address Work around because AF_UNSPEC does not check available addresses when determining result. If you have a global scope IPv6 address, then first check for IPv6 DNS result; if you don't have an IPv6, or there is no IPv6 result, then check IPv4. This allows IPv6-only networks to connect to dual-stack destinations, as they will get the IPv6 address (rather than the unusable IPv4). It also means a dual-stack host to a dual-stack destination will preference IPv6. There is no effect if you are on an IPv4-only network, or it is an IPv4-only destination. --- libraries/Network/src/NetworkManager.cpp | 107 +++++++++++++++-------- 1 file changed, 71 insertions(+), 36 deletions(-) diff --git a/libraries/Network/src/NetworkManager.cpp b/libraries/Network/src/NetworkManager.cpp index dde8372af24..502949f9e00 100644 --- a/libraries/Network/src/NetworkManager.cpp +++ b/libraries/Network/src/NetworkManager.cpp @@ -47,55 +47,90 @@ int NetworkManager::hostByName(const char* aHostname, IPAddress& aResult, bool p { // IDEA: Rename to getAddressInfo() ? - err_t err = ERR_OK; - - // This should generally check if we have a global address assigned to one of the interfaces. - // If such address is not assigned, there is no point in trying to get V6 from DNS as we will not be able to reach it. - // That is of course, if 'preferV6' is not set to true + err_t err = -1; + const char *servname = "0"; + struct addrinfo *res; static bool hasGlobalV6 = false; - bool hasGlobalV6Now = false;//ToDo: implement this! + + aResult = static_cast(0); + + // First check if the host parses as a literal address + if (aResult.fromString(aHostname)) { + return 1; + } + + // **Workaround** + // LWIP AF_UNSPEC always prefers IPv4 and doesn't check what network is + // available. See https://github.com/espressif/esp-idf/issues/13255 + // Until that is fixed, as a work around if we have a global scope IPv6, + // then we check IPv6 only first. + + // Iterate over active interfaces, and find if we have any global scope IPv6 + bool hasGlobalV6Now = false; + esp_netif_t *netif = NULL; + while ((netif = esp_netif_next_unsafe(netif)) != NULL) { + esp_ip6_addr_t ip6[CONFIG_LWIP_IPV6_NUM_ADDRESSES]; + 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 assumption 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) { + hasGlobalV6Now = true; + break; + } + } + if (hasGlobalV6Now) break; + } + + // Clear DNS cache if the flag has changed if(hasGlobalV6 != hasGlobalV6Now){ hasGlobalV6 = hasGlobalV6Now; dns_clear_cache(); log_d("Clearing DNS cache"); } - aResult = static_cast(0); - - // First check if the host parses as a literal address - if (!aResult.fromString(aHostname)) { - const char *servname = "0"; - struct addrinfo *res; - const struct addrinfo hints = { - .ai_family = AF_UNSPEC, + if (hasGlobalV6) { + const struct addrinfo hints6 = { + .ai_family = AF_INET6, .ai_socktype = SOCK_STREAM, }; - err = lwip_getaddrinfo(aHostname, servname, &hints, &res); - if (err == 0) - { - if (res->ai_family == AF_INET6) - { - struct sockaddr_in6 *ipv6 = (struct sockaddr_in6 *)res->ai_addr; - // As an array of u8_t - aResult = IPAddress(IPv6, ipv6->sin6_addr.s6_addr); - log_d("DNS found IPv6 %s", aResult.toString().c_str()); - } - else if (res->ai_family == AF_INET) - { - struct sockaddr_in *ipv4 = (struct sockaddr_in *)res->ai_addr; - // As a single u32_t - aResult = IPAddress(ipv4->sin_addr.s_addr); - log_d("DNS found IPv4 %s", aResult.toString().c_str()); - } - else - { - err = -1; - } + err = lwip_getaddrinfo(aHostname, servname, &hints6, &res); + if (err == ERR_OK) + { + struct sockaddr_in6 *ipv6 = (struct sockaddr_in6 *)res->ai_addr; + // As an array of u8_t + aResult = IPAddress(IPv6, ipv6->sin6_addr.s6_addr); + log_d("DNS found first IPv6 %s", aResult.toString().c_str()); lwip_freeaddrinfo(res); + return 1; } } - if (err == ERR_OK) { + // **End Workaround** + + const struct addrinfo hints = { + .ai_family = AF_UNSPEC, + .ai_socktype = SOCK_STREAM, + }; + err = lwip_getaddrinfo(aHostname, servname, &hints, &res); + if (err == ERR_OK) + { + if (res->ai_family == AF_INET6) + { + struct sockaddr_in6 *ipv6 = (struct sockaddr_in6 *)res->ai_addr; + // As an array of u8_t + aResult = IPAddress(IPv6, ipv6->sin6_addr.s6_addr); + log_d("DNS found any IPv6 %s", aResult.toString().c_str()); + } + else + { + struct sockaddr_in *ipv4 = (struct sockaddr_in *)res->ai_addr; + // As a single u32_t + aResult = IPAddress(ipv4->sin_addr.s_addr); + log_d("DNS found any IPv4 %s", aResult.toString().c_str()); + } + lwip_freeaddrinfo(res); return 1; } log_e("DNS Failed for '%s' with error '%d'", aHostname, err);