IPv6-on-windows: find DNS servers correctly
authorDavid Stuart <dstuart@counterpath.com>
Tue, 17 May 2011 09:53:13 +0000 (11:53 +0200)
committerDaniel Stenberg <daniel@haxx.se>
Tue, 17 May 2011 09:53:13 +0000 (11:53 +0200)
ares_init.c
ares_library_init.c
ares_library_init.h
ares_private.h
ares_process.c
ares_send.c

index 75064e965295059966266cdfb5a061efef075d69..4ee31ef6620d894b885278cb6b52174a64661da3 100644 (file)
@@ -69,6 +69,7 @@
 #include "ares_nowarn.h"
 #include "ares_platform.h"
 #include "ares_private.h"
+#include "inet_ntop.h"
 
 #ifdef ANDROID
 #include <sys/system_properties.h>
@@ -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 */
index 1a875da1504ba91e4303dd1d1758a4a2daa45b80..f0137a1828e0f226879b87f612a9994f0ca1ef63 100644 (file)
@@ -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
index 29d5c9e73ff73c253274884cb26af1c0f3a3decc..fe27d8048c5aa65779cd6980cd8ffb1ce6e82684 100644 (file)
 
 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 */
 
index 0739ed658899c71c30e0fba2f79e4b28a5e03309..ff45ba7e5ac10ee79cae4563ab0d0f66d2dcd8b6 100644 (file)
@@ -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;
index a4bf1d2c3de2d8fa40e0f73a50ba893a954bb3e5..f5e7d259ba71b54f71692bf1a563cbff8a9868aa 100644 (file)
@@ -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,
index 9f24d339a8a0044b1f96631361699e1b75077960..37b0704579c63219aa15c25f04e30677fb2a77ed 100644 (file)
@@ -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. */