diff options
Diffstat (limited to 'lib/hostares.c')
-rw-r--r-- | lib/hostares.c | 227 |
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); } |