From 3ac49e92f73c02c32811d566531472fde65a2104 Mon Sep 17 00:00:00 2001 From: Sly Gryphon Date: Wed, 3 Apr 2024 22:17:56 +1000 Subject: [PATCH] fix(dns): Fix IPv6-only network, by IPv6 preference if you have public 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 | 67 ++++++++++++++++++------ 1 file changed, 50 insertions(+), 17 deletions(-) diff --git a/libraries/Network/src/NetworkManager.cpp b/libraries/Network/src/NetworkManager.cpp index 5642ae30967..7eddc2a4c46 100644 --- a/libraries/Network/src/NetworkManager.cpp +++ b/libraries/Network/src/NetworkManager.cpp @@ -15,6 +15,8 @@ NetworkManager::NetworkManager(){ } +NetworkInterface * getNetifByID(Network_Interface_ID id); + bool NetworkManager::begin(){ static bool initialized = false; if(!initialized){ @@ -45,33 +47,66 @@ bool NetworkManager::begin(){ */ int NetworkManager::hostByName(const char* aHostname, IPAddress& aResult) { + static bool hasGlobalV6 = false; err_t err = ERR_OK; + const char *servname = "0"; + struct addrinfo *res; + aResult = static_cast(0); - // 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 - static bool hasGlobalV6 = false; + // 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. + + // This checks if we have a global address assigned to one of the interfaces. + // If such address is assigned, then we trying to get V6 from DNS first. bool hasGlobalV6Now = false;//ToDo: implement this! + for (int i = 0; i < ESP_NETIF_ID_MAX; ++i){ + NetworkInterface * iface = getNetifByID((Network_Interface_ID)i); + if(iface != NULL && iface->hasGlobalIPv6()){ + hasGlobalV6Now = true; + } + 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); + if (hasGlobalV6) { + const struct addrinfo hints6 = { + .ai_family = AF_INET6, + .ai_socktype = SOCK_STREAM, + }; + err = lwip_getaddrinfo(aHostname, servname, &hints6, &res); - // First check if the host parses as a literal address - if (aResult.fromString(aHostname)) { - return 1; + 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; + } } + // **End Workaround** - const char *servname = "0"; - struct addrinfo *res; - struct addrinfo hints; - memset(&hints, 0, sizeof(hints)); - hints.ai_family = AF_UNSPEC; - hints.ai_socktype = SOCK_STREAM; - + const struct addrinfo hints = { + .ai_family = AF_UNSPEC, + .ai_socktype = SOCK_STREAM, + }; err = lwip_getaddrinfo(aHostname, servname, &hints, &res); if (err == ERR_OK) { @@ -131,8 +166,6 @@ bool NetworkManager::setHostname(const char * name) return true; } -NetworkInterface * getNetifByID(Network_Interface_ID id); - size_t NetworkManager::printTo(Print & out) const { size_t bytes = 0;