From 6518b56a5e2bc9e39d720f17fa7dd322a28dc33c Mon Sep 17 00:00:00 2001 From: David Stuart Date: Tue, 17 May 2011 11:53:13 +0200 Subject: [PATCH] IPv6-on-windows: find DNS servers correctly --- ares_init.c | 239 +++++++++++++++++++++++++++++++++----------- ares_library_init.c | 10 ++ ares_library_init.h | 2 + ares_private.h | 2 +- ares_process.c | 4 +- ares_send.c | 2 +- 6 files changed, 197 insertions(+), 62 deletions(-) diff --git a/ares_init.c b/ares_init.c index 75064e96..4ee31ef6 100644 --- a/ares_init.c +++ b/ares_init.c @@ -69,6 +69,7 @@ #include "ares_nowarn.h" #include "ares_platform.h" #include "ares_private.h" +#include "inet_ntop.h" #ifdef ANDROID #include @@ -595,73 +596,195 @@ static int get_res_interfaces_nt(HKEY hKey, const char *subkey, char **obuf) return 0; } +/** + * The desired output for this method is that we set "ret_buf" to + * something like: + * + * 192.168.0.1,dns01.my.domain,fe80::200:f8ff:fe21:67cf + * + * The only ordering requirement is that primary servers are listed + * before secondary. There is no requirement that IPv4 addresses should + * necessarily be before IPv6. + * + * Note that ret_size should ideally be big enough to hold around + * 2-3 IPv4 and 2-3 IPv6 addresses. + * + * Finally, we need to return the total number of DNS servers located. + */ static int get_iphlpapi_dns_info (char *ret_buf, size_t ret_size) { - FIXED_INFO *fi, *newfi; - DWORD size = sizeof (*fi); - IP_ADDR_STRING *ipAddr; - int i, count = 0; - int debug = 0; - size_t ip_size = sizeof("255.255.255.255,")-1; - size_t left = ret_size; - char *ret = ret_buf; - HRESULT res; - - fi = malloc(size); - if (!fi) - return 0; - - res = (*ares_fpGetNetworkParams) (fi, &size); - if ((res != ERROR_BUFFER_OVERFLOW) && (res != ERROR_SUCCESS)) - goto quit; - - newfi = realloc(fi, size); - if (!newfi) - goto quit; - - fi = newfi; - res = (*ares_fpGetNetworkParams) (fi, &size); - if (res != ERROR_SUCCESS) - goto quit; - - if (debug) + const size_t ipv4_size = INET_ADDRSTRLEN + 1; /* +1 for ',' at end */ + const size_t ipv6_size = INET6_ADDRSTRLEN + 12; /* +12 for "%0123456789," at end */ + size_t left = ret_size; + char *ret = ret_buf; + int count = 0; + + /* Use the GetAdaptersAddresses method if it's available, otherwise + fall back to GetNetworkParams. */ + if (ares_fpGetAdaptersAddresses != ZERO_NULL) { - printf ("Host Name: %s\n", fi->HostName); - printf ("Domain Name: %s\n", fi->DomainName); - printf ("DNS Servers:\n" - " %s (primary)\n", fi->DnsServerList.IpAddress.String); + const ULONG working_buf_size = 15000; + IP_ADAPTER_ADDRESSES *pFirstEntry = NULL; + IP_ADAPTER_ADDRESSES *pEntry = NULL; + ULONG bufSize = 0; + ULONG result = 0; + + /* According to MSDN, the recommended way to do this is to use a temporary + buffer of 15K, to "dramatically reduce the chance that the GetAdaptersAddresses + method returns ERROR_BUFFER_OVERFLOW" */ + pFirstEntry = ( IP_ADAPTER_ADDRESSES * ) malloc( working_buf_size ); + bufSize = working_buf_size; + if( !pFirstEntry ) + return 0; + + /* Call the method one time */ + result = ( *ares_fpGetAdaptersAddresses )( AF_UNSPEC, 0, 0, pFirstEntry, &bufSize ); + if( result == ERROR_BUFFER_OVERFLOW ) + { + /* Reallocate, bufSize should now be set to the required size */ + pFirstEntry = ( IP_ADAPTER_ADDRESSES * ) realloc( pFirstEntry, bufSize ); + if( !pFirstEntry ) + return 0; + + /* Call the method a second time */ + result = ( *ares_fpGetAdaptersAddresses )( AF_UNSPEC, 0, 0, pFirstEntry, &bufSize ); + if( result == ERROR_BUFFER_OVERFLOW ) + { + /* Reallocate, bufSize should now be set to the required size */ + pFirstEntry = ( IP_ADAPTER_ADDRESSES * ) realloc( pFirstEntry, bufSize ); + if( !pFirstEntry ) + return 0; + + /* Call the method a third time. The maximum number of times we're going to do + this is 3. Three shall be the number thou shalt count, and the number of the + counting shall be three. Five is right out. */ + result = ( *ares_fpGetAdaptersAddresses )( AF_UNSPEC, 0, 0, pFirstEntry, &bufSize ); + } + } + + /* Check the current result for failure */ + if( result != ERROR_SUCCESS ) + { + free( pFirstEntry ); + return 0; + } + + /* process the results */ + for( pEntry = pFirstEntry ; pEntry != NULL ; pEntry = pEntry->Next ) + { + IP_ADAPTER_DNS_SERVER_ADDRESS* pDNSAddr = pEntry->FirstDnsServerAddress; + for( ; pDNSAddr != NULL ; pDNSAddr = pDNSAddr->Next ) + { + struct sockaddr *pGenericAddr = pDNSAddr->Address.lpSockaddr; + int stringlen = 0; + + if( pGenericAddr->sa_family == AF_INET && left > ipv4_size ) + { + /* Handle the v4 case */ + struct sockaddr_in *pIPv4Addr = ( struct sockaddr_in * ) pGenericAddr; + ares_inet_ntop( AF_INET, &pIPv4Addr->sin_addr, ret, ipv4_size - 1 ); /* -1 for comma */ + + /* Append a comma to the end, THEN NULL. Should be OK because we + already tested the size at the top of the if statement. */ + stringlen = strlen( ret ); + ret[ stringlen ] = ','; + ret[ stringlen + 1 ] = '\0'; + ret += stringlen + 1; + left -= ret - ret_buf; + ++count; + } + else if( pGenericAddr->sa_family == AF_INET6 && left > ipv6_size ) + { + /* Handle the v6 case */ + struct sockaddr_in6 *pIPv6Addr = ( struct sockaddr_in6 * ) pGenericAddr; + ares_inet_ntop( AF_INET6, &pIPv6Addr->sin6_addr, ret, ipv6_size - 1 ); /* -1 for comma */ + + /* Append a comma to the end, THEN NULL. Should be OK because we + already tested the size at the top of the if statement. */ + stringlen = strlen( ret ); + ret[ stringlen ] = ','; + ret[ stringlen + 1 ] = '\0'; + ret += stringlen + 1; + left -= ret - ret_buf; + ++count; + + /* NB on Windows this also returns stuff in the fec0::/10 range, + seems to be hard-coded somehow. Do we need to ignore them? */ + } + } + } + + if( pFirstEntry ) + free( pFirstEntry ); + if (ret > ret_buf) + ret[-1] = '\0'; + return count; } - if (strlen(fi->DnsServerList.IpAddress.String) > 0 && - inet_addr(fi->DnsServerList.IpAddress.String) != INADDR_NONE && - left > ip_size) + else { - ret += sprintf (ret, "%s,", fi->DnsServerList.IpAddress.String); - left -= ret - ret_buf; - count++; - } + FIXED_INFO *fi, *newfi; + DWORD size = sizeof (*fi); + IP_ADDR_STRING *ipAddr; + int i; + int debug = 0; + HRESULT res; + + fi = malloc(size); + if (!fi) + return 0; + + res = (*ares_fpGetNetworkParams) (fi, &size); + if ((res != ERROR_BUFFER_OVERFLOW) && (res != ERROR_SUCCESS)) + goto quit; + + newfi = realloc(fi, size); + if (!newfi) + goto quit; + + fi = newfi; + res = (*ares_fpGetNetworkParams) (fi, &size); + if (res != ERROR_SUCCESS) + goto quit; - for (i = 0, ipAddr = fi->DnsServerList.Next; ipAddr && left > ip_size; - ipAddr = ipAddr->Next, i++) - { - if (inet_addr(ipAddr->IpAddress.String) != INADDR_NONE) + if (debug) { - ret += sprintf (ret, "%s,", ipAddr->IpAddress.String); - left -= ret - ret_buf; - count++; + printf ("Host Name: %s\n", fi->HostName); + printf ("Domain Name: %s\n", fi->DomainName); + printf ("DNS Servers:\n" + " %s (primary)\n", fi->DnsServerList.IpAddress.String); + } + if (strlen(fi->DnsServerList.IpAddress.String) > 0 && + inet_addr(fi->DnsServerList.IpAddress.String) != INADDR_NONE && + left > ipv4_size) + { + ret += sprintf (ret, "%s,", fi->DnsServerList.IpAddress.String); + left -= ret - ret_buf; + ++count; + } + + for (i = 0, ipAddr = fi->DnsServerList.Next; ipAddr && left > ipv4_size; + ipAddr = ipAddr->Next, i++) + { + if (inet_addr(ipAddr->IpAddress.String) != INADDR_NONE) + { + ret += sprintf (ret, "%s,", ipAddr->IpAddress.String); + left -= ret - ret_buf; + ++count; + } + if (debug) + printf (" %s (secondary %d)\n", ipAddr->IpAddress.String, i+1); } - if (debug) - printf (" %s (secondary %d)\n", ipAddr->IpAddress.String, i+1); - } quit: - if (fi) - free(fi); - - if (debug && left <= ip_size) - printf ("Too many nameservers. Truncating to %d addressess", count); - if (ret > ret_buf) - ret[-1] = '\0'; - return count; + if (fi) + free(fi); + + if (debug && left <= ipv4_size) + printf ("Too many nameservers. Truncating to %d addressess", count); + if (ret > ret_buf) + ret[-1] = '\0'; + return count; + } } #endif @@ -704,7 +827,7 @@ DhcpNameServer DWORD data_type; DWORD bytes; DWORD result; - char buf[256]; + char buf[512]; win_platform platform; if (channel->nservers > -1) /* don't override ARES_OPT_SERVER */ diff --git a/ares_library_init.c b/ares_library_init.c index 1a875da1..f0137a18 100644 --- a/ares_library_init.c +++ b/ares_library_init.c @@ -26,6 +26,7 @@ #ifdef USE_WINSOCK fpGetNetworkParams_t ares_fpGetNetworkParams = ZERO_NULL; fpSystemFunction036_t ares_fpSystemFunction036 = ZERO_NULL; +fpGetAdaptersAddresses_t ares_fpGetAdaptersAddresses = ZERO_NULL; #endif /* library-private global vars with source visibility restricted to this file */ @@ -56,6 +57,15 @@ static int ares_win32_init(void) return ARES_EADDRGETNETWORKPARAMS; } + ares_fpGetAdaptersAddresses = (fpGetAdaptersAddresses_t) + GetProcAddress(hnd_iphlpapi, "GetAdaptersAddresses"); + if (!ares_fpGetAdaptersAddresses) + { + /* This can happen on clients before WinXP, I don't + think it should be an error, unless we don't want to + support Windows 2000 anymore */ + } + /* * When advapi32.dll is unavailable or advapi32.dll has no SystemFunction036, * also known as RtlGenRandom, which is the case for Windows versions prior diff --git a/ares_library_init.h b/ares_library_init.h index 29d5c9e7..fe27d804 100644 --- a/ares_library_init.h +++ b/ares_library_init.h @@ -26,12 +26,14 @@ typedef DWORD (WINAPI *fpGetNetworkParams_t) (FIXED_INFO*, DWORD*); typedef BOOLEAN (APIENTRY *fpSystemFunction036_t) (void*, ULONG); +typedef ULONG (WINAPI *fpGetAdaptersAddresses_t) ( ULONG, ULONG, void*, IP_ADAPTER_ADDRESSES*, ULONG* ); /* Forward-declaration of variables defined in ares_library_init.c */ /* that are global and unique instances for whole c-ares library. */ extern fpGetNetworkParams_t ares_fpGetNetworkParams; extern fpSystemFunction036_t ares_fpSystemFunction036; +extern fpGetAdaptersAddresses_t ares_fpGetAdaptersAddresses; #endif /* USE_WINSOCK */ diff --git a/ares_private.h b/ares_private.h index 0739ed65..ff45ba7e 100644 --- a/ares_private.h +++ b/ares_private.h @@ -203,7 +203,7 @@ struct query { void *arg; /* Query status */ - int try; /* Number of times we tried this query already. */ + int try_count; /* Number of times we tried this query already. */ int server; /* Server this query has last been sent to. */ struct query_server_info *server_info; /* per-server state */ int using_tcp; diff --git a/ares_process.c b/ares_process.c index a4bf1d2c..f5e7d259 100644 --- a/ares_process.c +++ b/ares_process.c @@ -684,7 +684,7 @@ static void next_server(ares_channel channel, struct query *query, * servers to try. In total, we need to do channel->nservers * channel->tries * attempts. Use query->try to remember how many times we already attempted * this query. Use modular arithmetic to find the next server to try. */ - while (++(query->try) < (channel->nservers * channel->tries)) + while (++(query->try_count) < (channel->nservers * channel->tries)) { struct server_state *server; @@ -789,7 +789,7 @@ void ares__send_query(ares_channel channel, struct query *query, return; } } - timeplus = channel->timeout << (query->try / channel->nservers); + timeplus = channel->timeout << (query->try_count / channel->nservers); timeplus = (timeplus * (9 + (rand () & 7))) / 16; query->timeout = *now; ares__timeadd(&query->timeout, diff --git a/ares_send.c b/ares_send.c index 9f24d339..37b07045 100644 --- a/ares_send.c +++ b/ares_send.c @@ -96,7 +96,7 @@ void ares_send(ares_channel channel, const unsigned char *qbuf, int qlen, query->arg = arg; /* Initialize query status. */ - query->try = 0; + query->try_count = 0; /* Choose the server to send the query to. If rotation is enabled, keep track * of the next server we want to use. */