-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

This patch allows the use of IPv6 addresses for nameserves in both
/etc/resolv.conf and by using the ares_set_nameservers() API.

I know that some important parts are still missing (notably ares_dup()
must support this and also the ares_set_nameserves() public call must be
documented) but I wanted someone from upstream to review the approach
first..if it is deemed OK I will continue and fix the remaining issues.

Thanks also to Kamil Dudka for taking a preliminary look at the patch.

        Jakub
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.10 (GNU/Linux)
Comment: Using GnuPG with Fedora - http://enigmail.mozdev.org/

iEYEARECAAYFAktoegcACgkQHsardTLnvCVhhwCeI5l/b11seLZn6oY5d3SoinAu
I5oAniywQ/mcx0tzFCFQzULmhO4Ut7cQ
=79UX
-----END PGP SIGNATURE-----
From 7d070840e21b9db26347e60a3515bca09891812b Mon Sep 17 00:00:00 2001
From: Jakub Hrozek <jhro...@redhat.com>
Date: Mon, 1 Feb 2010 21:23:59 +0100
Subject: [PATCH] Allow the use of IPv6 nameservers

This patch allows the use of IPv6 addresses for nameserves in both
/etc/resolv.conf and by using the ares_set_nameservers() API.
---
 ares.h         |    4 +
 ares_destroy.c |   10 +--
 ares_init.c    |  232 ++++++++++++++++++++++++++++++++++++++++++++++----------
 ares_private.h |    6 +-
 ares_process.c |   84 +++++++++++++++------
 5 files changed, 264 insertions(+), 72 deletions(-)

diff --git a/ares.h b/ares.h
index b1c2c22..ad5b17f 100644
--- a/ares.h
+++ b/ares.h
@@ -318,6 +318,10 @@ CARES_EXTERN void ares_set_socket_callback(ares_channel channel,
                                            ares_sock_create_callback callback,
                                            void *user_data);
 
+CARES_EXTERN int ares_set_nameservers(ares_channel channel,
+                                      struct sockaddr_storage *servers,
+                                      int num_servers);
+
 CARES_EXTERN void ares_send(ares_channel channel,
                             const unsigned char *qbuf,
                             int qlen,
diff --git a/ares_destroy.c b/ares_destroy.c
index 0044a71..4040971 100644
--- a/ares_destroy.c
+++ b/ares_destroy.c
@@ -67,15 +67,7 @@ void ares_destroy(ares_channel channel)
     }
 #endif
 
-  if (channel->servers) {
-    for (i = 0; i < channel->nservers; i++)
-      {
-        struct server_state *server = &channel->servers[i];
-        ares__close_sockets(channel, server);
-        assert(ares__is_list_empty(&(server->queries_to_server)));
-      }
-    free(channel->servers);
-  }
+  ares__free_servers(channel);
 
   if (channel->domains) {
     for (i = 0; i < channel->ndomains; i++)
diff --git a/ares_init.c b/ares_init.c
index cb541af..0835812 100644
--- a/ares_init.c
+++ b/ares_init.c
@@ -65,6 +65,7 @@
 #include <ctype.h>
 #include <time.h>
 #include <errno.h>
+#include <assert.h>
 #include "ares.h"
 #include "inet_net_pton.h"
 #include "ares_library_init.h"
@@ -88,6 +89,7 @@ static int set_search(ares_channel channel, const char *str);
 static int set_options(ares_channel channel, const char *str);
 static const char *try_option(const char *p, const char *q, const char *opt);
 static int init_id_key(rc4_key* key,int key_data_len);
+static void init_servers(ares_channel channel);
 
 #if !defined(WIN32) && !defined(WATT32)
 static int sortlist_alloc(struct apattern **sortlist, int *nsort, struct apattern *pat);
@@ -118,7 +120,6 @@ int ares_init_options(ares_channel *channelptr, struct ares_options *options,
   ares_channel channel;
   int i;
   int status = ARES_SUCCESS;
-  struct server_state *server;
   struct timeval now;
 
 #ifdef CURLDEBUG
@@ -227,6 +228,10 @@ int ares_init_options(ares_channel *channelptr, struct ares_options *options,
   if (status != ARES_SUCCESS)
     {
       /* Something failed; clean up memory we may have allocated. */
+      for (i = 0; i < channel->nservers; i++)
+        {
+          freeaddrinfo(channel->servers[i].addr);
+        }
       if (channel->servers)
         free(channel->servers);
       if (channel->domains)
@@ -248,20 +253,7 @@ int ares_init_options(ares_channel *channelptr, struct ares_options *options,
     channel->nservers = 1;
 
   /* Initialize server states. */
-  for (i = 0; i < channel->nservers; i++)
-    {
-      server = &channel->servers[i];
-      server->udp_socket = ARES_SOCKET_BAD;
-      server->tcp_socket = ARES_SOCKET_BAD;
-      server->tcp_connection_generation = ++channel->tcp_connection_generation;
-      server->tcp_lenbuf_pos = 0;
-      server->tcp_buffer = NULL;
-      server->qhead = NULL;
-      server->qtail = NULL;
-      ares__init_list_head(&(server->queries_to_server));
-      server->channel = channel;
-      server->is_broken = 0;
-    }
+  init_servers(channel);
 
   *channelptr = channel;
   return ARES_SUCCESS;
@@ -296,7 +288,6 @@ int ares_dup(ares_channel *dest, ares_channel src)
   (*dest)->sock_create_cb      = src->sock_create_cb;
   (*dest)->sock_create_cb_data = src->sock_create_cb_data;
 
-
   return ARES_SUCCESS; /* everything went fine */
 
 }
@@ -342,7 +333,17 @@ int ares_save_options(ares_channel channel, struct ares_options *options,
     if (!options->servers && channel->nservers != 0)
       return ARES_ENOMEM;
     for (i = 0; i < channel->nservers; i++)
-      options->servers[i] = channel->servers[i].addr;
+    {
+      if (channel->servers[i].addr->ai_family == AF_INET)
+        {
+            /* Because struct ares_options should stay the same
+             * only IPv4 nameservers are saved in this patch.
+             * ares_dup() works for both v4 and v6, though
+             */
+            options->servers[i] =
+                ((struct sockaddr_in *) channel->servers[i].addr->ai_addr)->sin_addr;
+        }
+    }
   }
   options->nservers = channel->nservers;
 
@@ -431,7 +432,28 @@ static int init_by_options(ares_channel channel,
           if (!channel->servers)
             return ARES_ENOMEM;
           for (i = 0; i < options->nservers; i++)
-            channel->servers[i].addr = options->servers[i];
+            {
+                /* This is a rather crude way to allow usage of
+                 * protocol-independent struct addrinfo while not
+                 * breaking the public struct ares_options which is forbidden
+                 * in favor of providing ares_set_XXX() functions
+                 */
+                channel->servers[i].addr = malloc(sizeof(struct addrinfo));
+                if (!channel->servers[i].addr)
+                  return ARES_ENOMEM;
+
+                channel->servers[i].addr->ai_addr = malloc(sizeof(struct sockaddr));
+                if (!channel->servers[i].addr->ai_addr)
+                  return ARES_ENOMEM;
+                channel->servers[i].free_addr = 1;
+
+                ((struct sockaddr_in *) channel->servers[i].addr->ai_addr)->sin_family = AF_INET;
+                ((struct sockaddr_in *) channel->servers[i].addr->ai_addr)->sin_addr = options->servers[i];
+
+                channel->servers[i].addr->ai_family = AF_INET;
+                channel->servers[i].addr->ai_next = NULL;
+                channel->servers[i].addr->ai_addrlen = sizeof(struct sockaddr_in);
+            }
         }
       channel->nservers = options->nservers;
     }
@@ -978,6 +1000,8 @@ static int init_by_defaults(ares_channel channel)
 #ifdef HAVE_GETHOSTNAME
   char *dot;
 #endif
+  struct addrinfo *res = NULL;
+  struct addrinfo hints;
 
   if (channel->flags == -1)
     channel->flags = 0;
@@ -1001,7 +1025,16 @@ static int init_by_defaults(ares_channel channel)
       rc = ARES_ENOMEM;
       goto error;
     }
-    channel->servers[0].addr.s_addr = htonl(INADDR_LOOPBACK);
+
+    memset(&hints, 0, sizeof(struct addrinfo));
+    hints.ai_flags = 0;
+    hints.ai_family = AF_UNSPEC;
+    rc = getaddrinfo(NULL, "53", &hints, &res);
+    if (rc != 0)
+      return ARES_SUCCESS;
+
+    channel->servers[0].addr = res;
+    channel->servers[0].free_addr = 0;
     channel->nservers = 1;
   }
 
@@ -1146,11 +1179,38 @@ static int config_lookup(ares_channel channel, const char *str,
 #endif  /* !WIN32 & !WATT32 */
 
 #ifndef WATT32
+static int set_nameserver(struct server_state **servers, int *nservers,
+                          char *str)
+{
+  struct server_state *newserv;
+  struct addrinfo *res = NULL;
+  struct addrinfo hints;
+  int ret;
+
+  memset(&hints, 0, sizeof(struct addrinfo));
+  hints.ai_flags = AI_NUMERICHOST; /* suppresses a lookup */
+  hints.ai_family = AF_UNSPEC;
+
+  ret = getaddrinfo(str, NULL, &hints, &res);
+  if (ret != 0)
+    return ARES_SUCCESS;
+
+  newserv = realloc(*servers, (*nservers + 1) * sizeof(struct server_state));
+  if (!newserv)
+    return ARES_ENOMEM;
+  newserv[*nservers].addr = res;
+  newserv[*nservers].free_addr = 0;
+  *servers = newserv;
+  (*nservers)++;
+
+  return ARES_SUCCESS;
+}
+
 static int config_nameserver(struct server_state **servers, int *nservers,
                              char *str)
 {
-  struct in_addr addr;
-  struct server_state *newserv;
+  int ret;
+
   /* On Windows, there may be more than one nameserver specified in the same
    * registry key, so we parse it as a space or comma seperated list.
    */
@@ -1178,15 +1238,9 @@ static int config_nameserver(struct server_state **servers, int *nservers,
     }
 
     /* This is the part that actually sets the nameserver */
-    addr.s_addr = inet_addr(begin);
-    if (addr.s_addr == INADDR_NONE)
-      continue;
-    newserv = realloc(*servers, (*nservers + 1) * sizeof(struct server_state));
-    if (!newserv)
-      return ARES_ENOMEM;
-    newserv[*nservers].addr = addr;
-    *servers = newserv;
-    (*nservers)++;
+    ret = set_nameserver(servers, nservers, begin);
+    if (ret)
+      return ret;
 
     if (!more)
       break;
@@ -1194,15 +1248,9 @@ static int config_nameserver(struct server_state **servers, int *nservers,
   }
 #else
   /* Add a nameserver entry, if this is a valid address. */
-  addr.s_addr = inet_addr(str);
-  if (addr.s_addr == INADDR_NONE)
-    return ARES_SUCCESS;
-  newserv = realloc(*servers, (*nservers + 1) * sizeof(struct server_state));
-  if (!newserv)
-    return ARES_ENOMEM;
-  newserv[*nservers].addr = addr;
-  *servers = newserv;
-  (*nservers)++;
+  ret = set_nameserver(servers, nservers, str);
+  if (ret)
+    return ret;
 #endif
   return ARES_SUCCESS;
 }
@@ -1580,3 +1628,109 @@ void ares_set_socket_callback(ares_channel channel,
   channel->sock_create_cb = cb;
   channel->sock_create_cb_data = data;
 }
+
+static void init_servers(ares_channel channel)
+{
+  struct server_state *server;
+  int i;
+
+  for (i = 0; i < channel->nservers; i++)
+    {
+      server = &channel->servers[i];
+      server->udp_socket = ARES_SOCKET_BAD;
+      server->tcp_socket = ARES_SOCKET_BAD;
+      server->tcp_connection_generation = ++channel->tcp_connection_generation;
+      server->tcp_lenbuf_pos = 0;
+      server->tcp_buffer = NULL;
+      server->qhead = NULL;
+      server->qtail = NULL;
+      ares__init_list_head(&(server->queries_to_server));
+      server->channel = channel;
+      server->is_broken = 0;
+    }
+}
+
+void ares__free_servers(ares_channel channel)
+{
+  int i;
+
+  if (channel->servers) {
+    for (i = 0; i < channel->nservers; i++)
+      {
+        struct server_state *server = &channel->servers[i];
+        ares__close_sockets(channel, server);
+        assert(ares__is_list_empty(&(server->queries_to_server)));
+        if (server->free_addr)
+          {
+            free(server->addr->ai_addr);
+            server->free_addr = 0;
+          }
+        freeaddrinfo(server->addr);
+      }
+    free(channel->servers);
+  }
+
+  channel->servers = NULL;
+  channel->nservers = -1;
+}
+
+int ares_set_nameservers(ares_channel channel,
+                         struct sockaddr_storage *servers,
+                         int num_servers)
+{
+  int i;
+  int rc = ARES_SUCCESS;
+
+  ares__free_servers(channel);
+
+  channel->nservers = num_servers;
+  channel->servers  =
+    malloc(channel->nservers * sizeof(struct server_state));
+  if (!channel->servers)
+    return ARES_ENOMEM;
+
+  for (i = 0; i < num_servers; i++)
+    {
+      struct server_state *ss = channel->servers + i;
+      memset(ss, 0, sizeof(struct server_state));
+
+      ss->channel = channel;
+      ss->addr = malloc(sizeof(struct addrinfo));
+      if (!ss->addr)
+        return ARES_ENOMEM;
+      memset(ss->addr, 0, sizeof(struct addrinfo));
+
+      if (servers[i].ss_family == AF_INET)
+        {
+          ss->addr->ai_family = AF_INET;
+          ss->addr->ai_addrlen = sizeof(struct sockaddr_in);
+          ss->addr->ai_addr   =
+            malloc(sizeof(struct sockaddr_in));
+          if (!ss->addr->ai_addr)
+            return ARES_ENOMEM;
+
+          memcpy(ss->addr->ai_addr, (struct sockaddr_in *) &servers[i],
+              sizeof(struct sockaddr_in));
+        }
+      else if (servers[i].ss_family == AF_INET6)
+        {
+          ss->addr->ai_family = AF_INET6;
+          ss->addr->ai_addrlen = sizeof(struct sockaddr_in6);
+          ss->addr->ai_addr   =
+            malloc(sizeof(struct sockaddr_in6));
+          if (!ss->addr->ai_addr)
+            return ARES_ENOMEM;
+
+          memcpy(ss->addr->ai_addr, (struct sockaddr_in6 *) &servers[i],
+              sizeof(struct sockaddr_in6));
+        }
+      else
+        {
+          /* This should never happen */
+          return ARES_EBADFAMILY;
+        }
+    }
+
+  init_servers(channel);
+  return rc;
+}
diff --git a/ares_private.h b/ares_private.h
index 4726d7a..0e0bfaf 100644
--- a/ares_private.h
+++ b/ares_private.h
@@ -137,7 +137,9 @@ struct send_request {
 };
 
 struct server_state {
-  struct in_addr addr;
+  struct addrinfo *addr;
+  int    free_addr;
+
   ares_socket_t udp_socket;
   ares_socket_t tcp_socket;
 
@@ -319,6 +321,8 @@ struct timeval ares__tvnow(void);
 int ares__expand_name_for_response(const unsigned char *encoded,
                                    const unsigned char *abuf, int alen,
                                    char **s, long *enclen);
+void ares__free_servers(ares_channel channel);
+
 #if 0 /* Not used */
 long ares__tvdiff(struct timeval t1, struct timeval t2);
 #endif
diff --git a/ares_process.c b/ares_process.c
index 71f9394..5bd16a8 100644
--- a/ares_process.c
+++ b/ares_process.c
@@ -434,7 +434,7 @@ static void read_udp_packets(ares_channel channel, fd_set *read_fds,
   ssize_t count;
   unsigned char buf[PACKETSZ + 1];
 #ifdef HAVE_RECVFROM
-  struct sockaddr_in from;
+  struct sockaddr_storage from;
   ares_socklen_t fromlen;
 #endif
 
@@ -480,16 +480,29 @@ static void read_udp_packets(ares_channel channel, fd_set *read_fds,
         if (count == -1 && try_again(SOCKERRNO))
           continue;
         else if (count <= 0)
-          handle_error(channel, i, now);
+          {
+            handle_error(channel, i, now);
+            break;
+          }
 #ifdef HAVE_RECVFROM
-        else if (from.sin_addr.s_addr != server->addr.s_addr)
-          /* Address response came from did not match the address
-           * we sent the request to.  Someone may be attempting
-           * to perform a cache poisoning attack */
-          break;
+        /* Check if address response came from did match the address
+         * we sent the request to.  Someone may be attempting
+         * to perform a cache poisoning attack */
+        else if (from.ss_family == AF_INET)
+          {
+              if (((struct sockaddr_in *) &from)->sin_addr.s_addr !=
+                  ((struct sockaddr_in *) server->addr->ai_addr)->sin_addr.s_addr)
+                break;
+          }
+        else if (from.ss_family == AF_INET6)
+          {
+              if (memcmp(((struct sockaddr_in6 *) &from)->sin6_addr.s6_addr,
+                         ((struct sockaddr_in6 *) server->addr->ai_addr)->sin6_addr.s6_addr,
+                         16)) /* FIXME - is there any portable constant? */
+                break;
+          }
 #endif
-        else
-          process_answer(channel, buf, (int)count, i, 0, now);
+        process_answer(channel, buf, (int)count, i, 0, now);
        } while (count > 0);
     }
 }
@@ -889,14 +902,36 @@ static int configure_socket(ares_socket_t s, ares_channel channel)
   return 0;
  }
 
+static int addrinfo_set_port(struct addrinfo *addr, int port)
+{
+  int rc = ARES_SUCCESS;
+  const unsigned short sh_port = (unsigned short)(port & 0xffff);
+
+  switch (addr->ai_family)
+    {
+    case AF_INET:
+      ((struct sockaddr_in *) addr->ai_addr)->sin_port = sh_port;
+      break;
+
+    case AF_INET6:
+      ((struct sockaddr_in6 *) addr->ai_addr)->sin6_port = sh_port;
+      break;
+
+    default:
+      rc = ARES_EBADFAMILY;
+      break;
+    }
+
+  return rc;
+}
+
 static int open_tcp_socket(ares_channel channel, struct server_state *server)
 {
   ares_socket_t s;
   int opt;
-  struct sockaddr_in sockin;
 
   /* Acquire a socket. */
-  s = socket(AF_INET, SOCK_STREAM, 0);
+  s = socket(server->addr->ai_family, SOCK_STREAM, 0);
   if (s == ARES_SOCKET_BAD)
     return -1;
 
@@ -923,12 +958,14 @@ static int open_tcp_socket(ares_channel channel, struct server_state *server)
     }
 #endif
 
+  if (addrinfo_set_port(server->addr, channel->tcp_port) != ARES_SUCCESS)
+    {
+       sclose(s);
+       return -1;
+    }
+
   /* Connect to the server. */
-  memset(&sockin, 0, sizeof(sockin));
-  sockin.sin_family = AF_INET;
-  sockin.sin_addr = server->addr;
-  sockin.sin_port = (unsigned short)(channel->tcp_port & 0xffff);
-  if (connect(s, (struct sockaddr *) &sockin, sizeof(sockin)) == -1)
+  if (connect(s, server->addr->ai_addr, server->addr->ai_addrlen) == -1)
     {
       int err = SOCKERRNO;
 
@@ -960,10 +997,9 @@ static int open_tcp_socket(ares_channel channel, struct server_state *server)
 static int open_udp_socket(ares_channel channel, struct server_state *server)
 {
   ares_socket_t s;
-  struct sockaddr_in sockin;
 
   /* Acquire a socket. */
-  s = socket(AF_INET, SOCK_DGRAM, 0);
+  s = socket(server->addr->ai_family, SOCK_DGRAM, 0);
   if (s == ARES_SOCKET_BAD)
     return -1;
 
@@ -974,12 +1010,14 @@ static int open_udp_socket(ares_channel channel, struct server_state *server)
        return -1;
     }
 
+  if (addrinfo_set_port(server->addr, channel->udp_port) != ARES_SUCCESS)
+    {
+       sclose(s);
+       return -1;
+    }
+
   /* Connect to the server. */
-  memset(&sockin, 0, sizeof(sockin));
-  sockin.sin_family = AF_INET;
-  sockin.sin_addr = server->addr;
-  sockin.sin_port = (unsigned short)(channel->udp_port & 0xffff);
-  if (connect(s, (struct sockaddr *) &sockin, sizeof(sockin)) == -1)
+  if (connect(s, server->addr->ai_addr, server->addr->ai_addrlen) == -1)
     {
       int err = SOCKERRNO;
 
-- 
1.6.6

Attachment: 0001-Allow-the-use-of-IPv6-nameservers.patch.sig
Description: PGP signature

Reply via email to