aboutsummaryrefslogtreecommitdiff
path: root/lib/hostares.c
diff options
context:
space:
mode:
Diffstat (limited to 'lib/hostares.c')
-rw-r--r--lib/hostares.c227
1 files changed, 199 insertions, 28 deletions
diff --git a/lib/hostares.c b/lib/hostares.c
index a165cb91f..1b6978d51 100644
--- a/lib/hostares.c
+++ b/lib/hostares.c
@@ -60,6 +60,12 @@
#define in_addr_t unsigned long
#endif
+/***********************************************************************
+ * Only for ares-enabled builds
+ **********************************************************************/
+
+#ifdef CURLRES_ARES
+
#include "urldata.h"
#include "sendf.h"
#include "hostip.h"
@@ -76,15 +82,132 @@
#define _MPRINTF_REPLACE /* use our functions only */
#include <curl/mprintf.h>
+# if defined(CURL_STATICLIB) && !defined(CARES_STATICLIB) && \
+ (defined(WIN32) || defined(_WIN32) || defined(__SYMBIAN32__))
+# define CARES_STATICLIB
+# endif
+# include <ares.h>
+
+#if ARES_VERSION >= 0x010500
+/* c-ares 1.5.0 or later, the callback proto is modified */
+#define HAVE_CARES_CALLBACK_TIMEOUTS 1
+#endif
+
#include "curl_memory.h"
/* The last #include file should be: */
#include "memdebug.h"
-/***********************************************************************
- * Only for ares-enabled builds
- **********************************************************************/
+struct ResolverResults {
+ int num_pending; /* number of ares_gethostbyname() requests */
+ Curl_addrinfo *temp_ai; /* intermediary result while fetching c-ares parts */
+ int last_status;
+};
-#ifdef CURLRES_ARES
+/*
+ * Curl_resolver_global_init() - the generic low-level asynchronous name resolve API.
+ * Called from curl_global_init() to initialize global resolver environment.
+ * Initializes ares library.
+ */
+int Curl_resolver_global_init()
+{
+#ifdef CARES_HAVE_ARES_LIBRARY_INIT
+ if(ares_library_init(ARES_LIB_INIT_ALL)) {
+ return CURLE_FAILED_INIT;
+ }
+#endif
+ return CURLE_OK;
+}
+
+/*
+ * Curl_resolver_global_cleanup() - the generic low-level asynchronous name resolve API.
+ * Called from curl_global_cleanup() to destroy global resolver environment.
+ * Deinitializes ares library.
+ */
+void Curl_resolver_global_cleanup()
+{
+#ifdef CARES_HAVE_ARES_LIBRARY_CLEANUP
+ ares_library_cleanup();
+#endif
+}
+
+/*
+ * Curl_resolver_init() - the generic low-level name resolve API.
+ * Called from curl_easy_init() -> Curl_open() to initialize resolver URL-state specific environment
+ * ('resolver' member of the UrlState structure).
+ * Fills the passed pointer by the initialized ares_channel.
+ */
+int Curl_resolver_init(void **resolver)
+{
+ int status = ares_init((ares_channel*)resolver);
+ if(status != ARES_SUCCESS) {
+ if(status == ARES_ENOMEM)
+ return CURLE_OUT_OF_MEMORY;
+ else
+ return CURLE_FAILED_INIT;
+ }
+ return CURLE_OK;
+ /* make sure that all other returns from this function should destroy the
+ ares channel before returning error! */
+}
+
+/*
+ * Curl_resolver_cleanup() - the generic low-level name resolve API.
+ * Called from curl_easy_cleanup() -> Curl_close() to cleanup resolver URL-state specific environment
+ * ('resolver' member of the UrlState structure).
+ * Destroys the ares channel.
+ */
+void Curl_resolver_cleanup(void *resolver)
+{
+ ares_destroy((ares_channel)resolver);
+}
+
+/*
+ * Curl_resolver_duphandle() - the generic low-level name resolve API.
+ * Called from curl_easy_duphandle() to duplicate resolver URL-state specific environment
+ * ('resolver' member of the UrlState structure).
+ * Duplicates the 'from' ares channel and passes the resulting channel to the 'to' pointer.
+ */
+int Curl_resolver_duphandle(void **to, void *from)
+{
+ /* Clone the ares channel for the new handle */
+ if(ARES_SUCCESS != ares_dup((ares_channel*)to,(ares_channel)from))
+ return CURLE_FAILED_INIT;
+ return CURLE_OK;
+}
+
+static void destroy_async_data (struct Curl_async *async);
+/*
+ * Cancel all possibly still on-going resolves for this connection.
+ */
+void Curl_async_cancel(struct connectdata *conn)
+{
+ if( conn && conn->data && conn->data->state.resolver )
+ ares_cancel((ares_channel)conn->data->state.resolver);
+ destroy_async_data(&conn->async);
+}
+
+/*
+ * destroy_async_data() cleans up async resolver data.
+ */
+static void destroy_async_data (struct Curl_async *async)
+{
+ if(async->hostname)
+ free(async->hostname);
+
+ if(async->os_specific) {
+ struct ResolverResults *res = (struct ResolverResults *)async->os_specific;
+ if( res ) {
+ if( res->temp_ai ) {
+ Curl_freeaddrinfo(res->temp_ai);
+ res->temp_ai = NULL;
+ }
+ free(res);
+ }
+ async->os_specific = NULL;
+ }
+
+ async->hostname = NULL;
+}
/*
* Curl_resolv_fdset() is called when someone from the outside world (using
@@ -103,14 +226,14 @@ int Curl_resolv_getsock(struct connectdata *conn,
struct timeval maxtime;
struct timeval timebuf;
struct timeval *timeout;
- int max = ares_getsock(conn->data->state.areschannel,
+ int max = ares_getsock((ares_channel)conn->data->state.resolver,
(ares_socket_t *)socks, numsocks);
maxtime.tv_sec = CURL_TIMEOUT_RESOLVE;
maxtime.tv_usec = 0;
- timeout = ares_timeout(conn->data->state.areschannel, &maxtime, &timebuf);
+ timeout = ares_timeout((ares_channel)conn->data->state.resolver, &maxtime, &timebuf);
Curl_expire(conn->data,
(timeout->tv_sec * 1000) + (timeout->tv_usec/1000));
@@ -138,7 +261,7 @@ static int waitperform(struct connectdata *conn, int timeout_ms)
int i;
int num = 0;
- bitmask = ares_getsock(data->state.areschannel, socks, ARES_GETSOCK_MAXNUM);
+ bitmask = ares_getsock((ares_channel)data->state.resolver, socks, ARES_GETSOCK_MAXNUM);
for(i=0; i < ARES_GETSOCK_MAXNUM; i++) {
pfd[i].events = 0;
@@ -165,11 +288,11 @@ static int waitperform(struct connectdata *conn, int timeout_ms)
if(!nfds)
/* Call ares_process() unconditonally here, even if we simply timed out
above, as otherwise the ares name resolve won't timeout! */
- ares_process_fd(data->state.areschannel, ARES_SOCKET_BAD, ARES_SOCKET_BAD);
+ ares_process_fd((ares_channel)data->state.resolver, ARES_SOCKET_BAD, ARES_SOCKET_BAD);
else {
/* move through the descriptors and ask for processing on them */
for(i=0; i < num; i++)
- ares_process_fd(data->state.areschannel,
+ ares_process_fd((ares_channel)data->state.resolver,
pfd[i].revents & (POLLRDNORM|POLLIN)?
pfd[i].fd:ARES_SOCKET_BAD,
pfd[i].revents & (POLLWRNORM|POLLOUT)?
@@ -189,13 +312,17 @@ CURLcode Curl_is_resolved(struct connectdata *conn,
struct Curl_dns_entry **dns)
{
struct SessionHandle *data = conn->data;
+ struct ResolverResults *res = (struct ResolverResults *)conn->async.os_specific;
*dns = NULL;
waitperform(conn, 0);
- if(conn->async.done) {
- /* we're done, kill the ares handle */
+ if( res && !res->num_pending ) {
+ (void)Curl_addrinfo_callback(conn, res->last_status, res->temp_ai);
+ /* temp_ai ownership is moved to the connection, so we need not free-up them */
+ res->temp_ai = NULL;
+ destroy_async_data(&conn->async);
if(!conn->async.dns) {
failf(data, "Could not resolve host: %s (%s)", conn->host.dispname,
ares_strerror(conn->async.status));
@@ -223,6 +350,7 @@ CURLcode Curl_wait_for_resolv(struct connectdata *conn,
struct SessionHandle *data = conn->data;
long timeout;
struct timeval now = Curl_tvnow();
+ struct Curl_dns_entry *temp_entry;
timeout = Curl_timeleft(data, &now, TRUE);
if(!timeout)
@@ -240,7 +368,7 @@ CURLcode Curl_wait_for_resolv(struct connectdata *conn,
store.tv_sec = itimeout/1000;
store.tv_usec = (itimeout%1000)*1000;
- tvp = ares_timeout(data->state.areschannel, &store, &tv);
+ tvp = ares_timeout((ares_channel)data->state.resolver, &store, &tv);
/* use the timeout period ares returned to us above if less than one
second is left, otherwise just use 1000ms to make sure the progress
@@ -251,6 +379,7 @@ CURLcode Curl_wait_for_resolv(struct connectdata *conn,
timeout_ms = 1000;
waitperform(conn, timeout_ms);
+ Curl_is_resolved(conn,&temp_entry);
if(conn->async.done)
break;
@@ -267,7 +396,7 @@ CURLcode Curl_wait_for_resolv(struct connectdata *conn,
}
if(timeout < 0) {
/* our timeout, so we cancel the ares operation */
- ares_cancel(data->state.areschannel);
+ ares_cancel((ares_channel)data->state.resolver);
break;
}
}
@@ -313,6 +442,22 @@ CURLcode Curl_wait_for_resolv(struct connectdata *conn,
return rc;
}
+/* Connects results to the list */
+static void ares_compound_results(struct ResolverResults *res, Curl_addrinfo *ai)
+{
+ Curl_addrinfo *ai_tail;
+ if( !ai )
+ return;
+ 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 = res->temp_ai;
+ res->temp_ai = ai;
+}
+
/*
* ares_query_completed_cb() is the callback that ares will call when
* the host query initiated by ares_gethostbyname() from Curl_getaddrinfo(),
@@ -326,26 +471,44 @@ static void ares_query_completed_cb(void *arg, /* (struct connectdata *) */
struct hostent *hostent)
{
struct connectdata *conn = (struct connectdata *)arg;
- struct Curl_addrinfo * ai = NULL;
+ struct ResolverResults *res = (struct ResolverResults *)conn->async.os_specific;
+
+ if( !conn->data ) {
+ /* Immediately return just because the handle is destroying */
+ return;
+ }
+
+ if( !conn->data->magic ) {
+ /* Immediately return just because the handle is destroying */
+ return;
+ }
+
+ if( !res ) {
+ /* Immediately return just because the results are destroyed for some reason */
+ return;
+ }
#ifdef HAVE_CARES_CALLBACK_TIMEOUTS
(void)timeouts; /* ignored */
#endif
+ res->num_pending--;
+
switch(status) {
case CURL_ASYNC_SUCCESS:
- ai = Curl_he2ai(hostent, conn->async.port);
+ ares_compound_results(res,Curl_he2ai(hostent, conn->async.port));
break;
- case ARES_EDESTRUCTION:
- /* this ares handle is getting destroyed, the 'arg' pointer may not be
+ /* this ares handle is getting destroyed, the 'arg' pointer may not be
valid! */
- return;
+ /* conn->magic check instead
+ case ARES_EDESTRUCTION:
+ return; */
default:
- /* do nothing */
break;
}
-
- (void)Curl_addrinfo_callback(arg, status, ai);
+ /* The successfull result empties any error */
+ if( res->last_status != ARES_SUCCESS )
+ res->last_status = status;
}
/*
@@ -402,33 +565,41 @@ Curl_addrinfo *Curl_getaddrinfo(struct connectdata *conn,
#endif /* CURLRES_IPV6 */
bufp = strdup(hostname);
-
if(bufp) {
+ struct ResolverResults *res = NULL;
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.temp_ai = NULL; /* clear */
+ res = (struct ResolverResults *)calloc(sizeof(struct ResolverResults),1);
+ if( !res ) {
+ Curl_safefree(conn->async.hostname);
+ conn->async.hostname = NULL;
+ return NULL;
+ }
+ conn->async.os_specific = res;
+ /* initial status - failed */
+ res->last_status = ARES_ENOTFOUND;
#ifdef ENABLE_IPV6 /* CURLRES_IPV6 */
if(family == PF_UNSPEC) {
- conn->async.num_pending = 2;
+ res->num_pending = 2;
/* areschannel is already setup in the Curl_open() function */
- ares_gethostbyname(data->state.areschannel, hostname, PF_INET,
+ ares_gethostbyname((ares_channel)data->state.resolver, hostname, PF_INET,
ares_query_completed_cb, conn);
- ares_gethostbyname(data->state.areschannel, hostname, PF_INET6,
+ ares_gethostbyname((ares_channel)data->state.resolver, hostname, PF_INET6,
ares_query_completed_cb, conn);
}
else
#endif /* CURLRES_IPV6 */
{
- conn->async.num_pending = 1;
+ res->num_pending = 1;
/* areschannel is already setup in the Curl_open() function */
- ares_gethostbyname(data->state.areschannel, hostname, family,
+ ares_gethostbyname((ares_channel)data->state.resolver, hostname, family,
ares_query_completed_cb, conn);
}