Commit ca015f1a authored by Vsevolod Novikov's avatar Vsevolod Novikov Committed by Daniel Stenberg
Browse files

asynch resolvers: unified

Introducing an internal API for handling of different async resolver
backends.
parent 722f286f
Loading
Loading
Loading
Loading
+6 −13
Original line number Diff line number Diff line
@@ -269,12 +269,10 @@ CURLcode curl_global_init(long flags)
  idna_init();
#endif

#ifdef CARES_HAVE_ARES_LIBRARY_INIT
  if(ares_library_init(ARES_LIB_INIT_ALL)) {
    DEBUGF(fprintf(stderr, "Error: ares_library_init failed\n"));
  if( Curl_resolver_global_init() != CURLE_OK ) {
    DEBUGF(fprintf(stderr, "Error: resolver_global_init failed\n"));
    return CURLE_FAILED_INIT;
  }
#endif

#if defined(USE_LIBSSH2) && defined(HAVE_LIBSSH2_INIT)
  if(libssh2_init(0)) {
@@ -340,9 +338,7 @@ void curl_global_cleanup(void)
  if(init_flags & CURL_GLOBAL_SSL)
    Curl_ssl_cleanup();

#ifdef CARES_HAVE_ARES_LIBRARY_CLEANUP
  ares_library_cleanup();
#endif
  Curl_resolver_global_cleanup();

  if(init_flags & CURL_GLOBAL_WIN32)
    win32_cleanup();
@@ -676,12 +672,9 @@ CURL *curl_easy_duphandle(CURL *incurl)
    outcurl->change.referer_alloc = TRUE;
  }

#ifdef USE_ARES
  /* If we use ares, we clone the ares channel for the new handle */
  if(ARES_SUCCESS != ares_dup(&outcurl->state.areschannel,
                              data->state.areschannel))
  /* Clone the resolver handle, if present, for the new handle */
  if( Curl_resolver_duphandle(&outcurl->state.resolver, data->state.resolver) != CURLE_OK )
   goto fail;
#endif

  Curl_convert_setup(outcurl);

+199 −28
Original line number Diff line number Diff line
@@ -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
       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);
    }

+0 −75
Original line number Diff line number Diff line
@@ -72,20 +72,6 @@
 **********************************************************************/
#ifdef CURLRES_ASYNCH

/*
 * Cancel all possibly still on-going resolves for this connection.
 */
void Curl_async_cancel(struct connectdata *conn)
{
  /* If we have a "half" response already received, we first clear that off
     so that nothing is tempted to use it */
  if(conn->async.temp_ai) {
    Curl_freeaddrinfo(conn->async.temp_ai);
    conn->async.temp_ai = NULL;
  }
}


/*
 * Curl_addrinfo_callback() gets called by ares, gethostbyname_thread()
 * or getaddrinfo_thread() when we got the name resolved (or not!).
@@ -109,24 +95,6 @@ CURLcode Curl_addrinfo_callback(struct connectdata *conn,
    if(ai) {
      struct SessionHandle *data = conn->data;

#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;

      /* make sure the temp pointer is cleared and isn't pointing to something
         we take care of below */
      conn->async.temp_ai = NULL;
#endif
      if(data->share)
        Curl_share_lock(data, CURL_LOCK_DATA_DNS, CURL_LOCK_ACCESS_SINGLE);

@@ -143,52 +111,9 @@ 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);
          conn->async.temp_ai = NULL;
        }
        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);

        /* make sure the temp pointer is cleared and isn't pointing to
           something we've taken care of already */
        conn->async.temp_ai = NULL;
      }
  }
#endif

  conn->async.dns = dns;

+48 −31
Original line number Diff line number Diff line
@@ -35,14 +35,6 @@
#define in_addr_t unsigned long
#endif

/*
 * Comfortable CURLRES_* definitions are included from setup.h
 */

#ifdef USE_ARES
#include <ares_version.h>
#endif

/* Allocate enough memory to hold the full name information structs and
 * everything. OSF1 is known to require at least 8872 bytes. The buffer
 * required for storing all possible aliases and IP numbers is according to
@@ -53,29 +45,13 @@
#define CURL_TIMEOUT_RESOLVE 300 /* when using asynch methods, we allow this
                                    many seconds for a name resolve */

#ifdef CURLRES_ARES
#define CURL_ASYNC_SUCCESS ARES_SUCCESS
#if ARES_VERSION >= 0x010500
/* c-ares 1.5.0 or later, the callback proto is modified */
#define HAVE_CARES_CALLBACK_TIMEOUTS 1
#endif
#else
#define CURL_ASYNC_SUCCESS CURLE_OK
#define ares_cancel(x) do {} while(0)
#define ares_destroy(x) do {} while(0)
#endif

struct addrinfo;
struct hostent;
struct SessionHandle;
struct connectdata;

#ifdef CURLRES_ASYNCH
void Curl_async_cancel(struct connectdata *conn);
#else
#define Curl_async_cancel(x) do {} while(0)
#endif

/*
 * Curl_global_host_cache_init() initializes and sets up a global DNS cache.
 * Global DNS cache is general badness. Do not use. This will be removed in
@@ -128,6 +104,45 @@ bool Curl_ipv6works(void);
 */
bool Curl_ipvalid(struct connectdata *conn);

/*
 * Curl_resolver_global_init() - the generic low-level name resolver API.
 * Called from curl_global_init() to initialize global resolver environment.
 * Returning anything else than CURLE_OK fails curl_global_init().
 */
int Curl_resolver_global_init(void);

/*
 * Curl_resolver_global_cleanup() - the generic low-level name resolver API.
 * Called from curl_global_cleanup() to destroy global resolver environment.
 */
void Curl_resolver_global_cleanup(void);

/*
 * 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).
 * Should fill the passed pointer by the initialized handler.
 * Returning anything else than CURLE_OK fails curl_easy_init() with the correspondent code.
 */
int Curl_resolver_init(void **resolver);

/*
 * 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).
 * Should destroy the handler and free all resources connected to it.
 */
void Curl_resolver_cleanup(void *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).
 * Should duplicate the 'from' handle and pass the resulting handle to the 'to' pointer.
 * Returning anything else than CURLE_OK causes failed curl_easy_duphandle() call.
 */
int Curl_resolver_duphandle(void **to, void *from);

/*
 * Curl_getaddrinfo() is the generic low-level name resolve API within this
 * source file. There are several versions of this function - for different
@@ -139,6 +154,15 @@ Curl_addrinfo *Curl_getaddrinfo(struct connectdata *conn,
                                int port,
                                int *waitp);

#ifdef CURLRES_ASYNCH
/*
 * Curl_async_cancel() is the generic low-level asynchronous name resolve API.
 * It is called from inside other functions to cancel currently performing resolver
 * request. Should also free any temporary resources allocated to perform a request.
 */
void Curl_async_cancel(struct connectdata *conn);
#endif

CURLcode Curl_is_resolved(struct connectdata *conn,
                          struct Curl_dns_entry **dns);
CURLcode Curl_wait_for_resolv(struct connectdata *conn,
@@ -209,13 +233,6 @@ struct Curl_dns_entry *
Curl_cache_addr(struct SessionHandle *data, Curl_addrinfo *addr,
                const char *hostname, int port);

/*
 * Curl_destroy_thread_data() cleans up async resolver data.
 * Complementary of ares_destroy.
 */
struct Curl_async; /* forward-declaration */
void Curl_destroy_thread_data(struct Curl_async *async);

#ifndef INADDR_NONE
#define CURL_INADDR_NONE (in_addr_t) ~0
#else
+1 −0
Original line number Diff line number Diff line
@@ -87,6 +87,7 @@ bool Curl_ipvalid(struct connectdata *conn)
}

#ifdef CURLRES_SYNCH

/*
 * Curl_getaddrinfo() - the ipv4 synchronous version.
 *
Loading