Skip to content

Commit

Permalink
fix(dns): Fix IPv6 only networks by preferencing IPv6 if you have add…
Browse files Browse the repository at this point in the history
…ress

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.
  • Loading branch information
sgryphon committed Apr 1, 2024
1 parent f8336de commit 8287cf8
Showing 1 changed file with 71 additions and 36 deletions.
107 changes: 71 additions & 36 deletions libraries/Network/src/NetworkManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<uint32_t>(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<uint32_t>(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);
Expand Down

0 comments on commit 8287cf8

Please sign in to comment.