Hi,

this is a patch to let hostares.c request both IPv4 and IPv6
addresses when IPv6 is enabled.

There are a few quirks, like adding num_pending and temp_ai
to Curl_async, that I'm not proud of and could probably be
improved. Also, failure handling could use a review.

Currently, c-ares returns IPv4 address when there are
no IPv6 addresses, and in those cases the Curl DNS cache will
contain duplicate addresses.

There are no ordering guarantees on which address family
comes first in the DNS cache. Is that needed?


-- 
Tommie


Patch the c-ares resolver binding to support asking for both
IPv4 and IPv6 addresses when IPv6 is enabled.

This is a workaround for the missing ares_getaddrinfo() and is a lot
easier to implement.

Note that as long as c-ares returns IPv4 addresses when IPv6 addresses
were requested but missing, this will cause a host's IPv4 addresses
to occur twice in the DNS cache.
---
 lib/hostares.c |   29 +++++++++++++++++++++++------
 lib/hostasyn.c |   56 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 lib/urldata.h  |    2 ++
 3 files changed, 81 insertions(+), 6 deletions(-)

diff --git a/lib/hostares.c b/lib/hostares.c
index e31a677..85c91ab 100644
--- a/lib/hostares.c
+++ b/lib/hostares.c
@@ -401,13 +401,30 @@ Curl_addrinfo *Curl_getaddrinfo(struct connectdata *conn,
     Curl_safefree(conn->async.hostname);
     conn->async.hostname = bufp;
     conn->async.port = port;
-    conn->async.done = FALSE; /* not done */
-    conn->async.status = 0;   /* clear */
-    conn->async.dns = NULL;   /* clear */
+    conn->async.done = FALSE;   /* not done */
+    conn->async.status = 0;     /* clear */
+    conn->async.dns = NULL;     /* clear */
+    conn->async.temp_ai = NULL; /* clear */
 
-    /* areschannel is already setup in the Curl_open() function */
-    ares_gethostbyname(data->state.areschannel, hostname, family,
-                       (ares_host_callback)ares_query_completed_cb, conn);
+#ifdef ENABLE_IPV6 /* CURLRES_IPV6 */
+    if(family == PF_UNSPEC) {
+      conn->async.num_pending = 2;
+
+      /* areschannel is already setup in the Curl_open() function */
+      ares_gethostbyname(data->state.areschannel, hostname, PF_INET,
+                         ares_query_completed_cb, conn);
+      ares_gethostbyname(data->state.areschannel, hostname, PF_INET6,
+                         ares_query_completed_cb, conn);
+    }
+    else
+#endif /* CURLRES_IPV6 */
+    {
+      conn->async.num_pending = 1;
+
+      /* areschannel is already setup in the Curl_open() function */
+      ares_gethostbyname(data->state.areschannel, hostname, family,
+                         ares_query_completed_cb, conn);
+    }
 
     *waitp = 1; /* expect asynchronous response */
   }
diff --git a/lib/hostasyn.c b/lib/hostasyn.c
index 127b8d3..f889fe0 100644
--- a/lib/hostasyn.c
+++ b/lib/hostasyn.c
@@ -93,6 +93,21 @@ CURLcode Curl_addrinfo_callback(struct connectdata * conn,
 
   if(CURL_ASYNC_SUCCESS == status) {
     if(ai) {
+#if defined(ENABLE_IPV6) && defined(CURLRES_ARES) /* CURLRES_IPV6 */
+      Curl_addrinfo *ai_tail = ai;
+
+      while (ai_tail->ai_next)
+        ai_tail = ai_tail->ai_next;
+
+      /* Add the new results to the list of old results. */
+      ai_tail->ai_next = conn->async.temp_ai;
+      conn->async.temp_ai = ai;
+
+      if(--conn->async.num_pending > 0) {
+        /* We are not done yet. Just return. */
+        return CURLE_OK;
+      }
+#endif
       struct SessionHandle *data = conn->data;
 
       if(data->share)
@@ -111,8 +126,49 @@ CURLcode Curl_addrinfo_callback(struct connectdata * conn,
         Curl_share_unlock(data, CURL_LOCK_DATA_DNS);
     }
     else
+    {
+#if defined(ENABLE_IPV6) && defined(CURLRES_ARES) /* CURLRES_IPV6 */
+      if(--conn->async.num_pending > 0) {
+        /* We are not done yet. Clean up and return.
+	   This function will be called again. */
+        if(conn->async.temp_ai)
+          Curl_freeaddrinfo(conn->async.temp_ai);
+        return CURLE_OUT_OF_MEMORY;
+      }
+#endif
       rc = CURLE_OUT_OF_MEMORY;
+    }
+  }
+#if defined(ENABLE_IPV6) && defined(CURLRES_ARES) /* CURLRES_IPV6 */
+  else
+  {
+      if(--conn->async.num_pending > 0) {
+        /* We are not done yet. Just return. */
+        return CURLE_OK;
+      }
+
+      if(conn->async.temp_ai) {
+        /* We are done, and while this latest request
+           failed, some previous results exist. */
+        struct SessionHandle *data = conn->data;
+
+        if(data->share)
+          Curl_share_lock(data, CURL_LOCK_DATA_DNS, CURL_LOCK_ACCESS_SINGLE);
+
+        dns = Curl_cache_addr(data, conn->async.temp_ai,
+                              conn->async.hostname,
+                              conn->async.port);
+        if(!dns) {
+          /* failed to store, cleanup and return error */
+          Curl_freeaddrinfo(conn->async.temp_ai);
+          rc = CURLE_OUT_OF_MEMORY;
+        }
+
+        if(data->share)
+          Curl_share_unlock(data, CURL_LOCK_DATA_DNS);
+      }
   }
+#endif
 
   conn->async.dns = dns;
 
diff --git a/lib/urldata.h b/lib/urldata.h
index 62dabeb..df34e5b 100644
--- a/lib/urldata.h
+++ b/lib/urldata.h
@@ -468,6 +468,8 @@ struct Curl_async {
   bool done;  /* set TRUE when the lookup is complete */
   int status; /* if done is TRUE, this is the status from the callback */
   void *os_specific;  /* 'struct thread_data' for Windows */
+  int num_pending; /* number of ares_gethostbyname() requests */
+  Curl_addrinfo *temp_ai; /* intermediary result while fetching c-ares parts */
 };
 #endif
 
-- 
1.7.1

Attachment: signature.asc
Description: OpenPGP digital signature

-------------------------------------------------------------------
List admin: http://cool.haxx.se/list/listinfo/curl-library
Etiquette:  http://curl.haxx.se/mail/etiquette.html

Reply via email to