Home | History | Annotate | Download | only in lib
      1 /***************************************************************************
      2  *                                  _   _ ____  _
      3  *  Project                     ___| | | |  _ \| |
      4  *                             / __| | | | |_) | |
      5  *                            | (__| |_| |  _ <| |___
      6  *                             \___|\___/|_| \_\_____|
      7  *
      8  * Copyright (C) 1998 - 2015, Daniel Stenberg, <daniel (at) haxx.se>, et al.
      9  *
     10  * This software is licensed as described in the file COPYING, which
     11  * you should have received as part of this distribution. The terms
     12  * are also available at http://curl.haxx.se/docs/copyright.html.
     13  *
     14  * You may opt to use, copy, modify, merge, publish, distribute and/or sell
     15  * copies of the Software, and permit persons to whom the Software is
     16  * furnished to do so, under the terms of the COPYING file.
     17  *
     18  * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
     19  * KIND, either express or implied.
     20  *
     21  ***************************************************************************/
     22 
     23 #include "curl_setup.h"
     24 
     25 #ifdef HAVE_LIMITS_H
     26 #include <limits.h>
     27 #endif
     28 #ifdef HAVE_NETINET_IN_H
     29 #include <netinet/in.h>
     30 #endif
     31 #ifdef HAVE_NETDB_H
     32 #include <netdb.h>
     33 #endif
     34 #ifdef HAVE_ARPA_INET_H
     35 #include <arpa/inet.h>
     36 #endif
     37 #ifdef __VMS
     38 #include <in.h>
     39 #include <inet.h>
     40 #endif
     41 
     42 #ifdef HAVE_PROCESS_H
     43 #include <process.h>
     44 #endif
     45 
     46 #if (defined(NETWARE) && defined(__NOVELL_LIBC__))
     47 #undef in_addr_t
     48 #define in_addr_t unsigned long
     49 #endif
     50 
     51 /***********************************************************************
     52  * Only for ares-enabled builds
     53  * And only for functions that fulfill the asynch resolver backend API
     54  * as defined in asyn.h, nothing else belongs in this file!
     55  **********************************************************************/
     56 
     57 #ifdef CURLRES_ARES
     58 
     59 #include "urldata.h"
     60 #include "sendf.h"
     61 #include "hostip.h"
     62 #include "hash.h"
     63 #include "share.h"
     64 #include "strerror.h"
     65 #include "url.h"
     66 #include "multiif.h"
     67 #include "inet_pton.h"
     68 #include "connect.h"
     69 #include "select.h"
     70 #include "progress.h"
     71 #include "curl_printf.h"
     72 
     73 #  if defined(CURL_STATICLIB) && !defined(CARES_STATICLIB) && \
     74      (defined(WIN32) || defined(_WIN32) || defined(__SYMBIAN32__))
     75 #    define CARES_STATICLIB
     76 #  endif
     77 #  include <ares.h>
     78 #  include <ares_version.h> /* really old c-ares didn't include this by
     79                                itself */
     80 
     81 #if ARES_VERSION >= 0x010500
     82 /* c-ares 1.5.0 or later, the callback proto is modified */
     83 #define HAVE_CARES_CALLBACK_TIMEOUTS 1
     84 #endif
     85 
     86 #include "curl_memory.h"
     87 /* The last #include file should be: */
     88 #include "memdebug.h"
     89 
     90 struct ResolverResults {
     91   int num_pending; /* number of ares_gethostbyname() requests */
     92   Curl_addrinfo *temp_ai; /* intermediary result while fetching c-ares parts */
     93   int last_status;
     94 };
     95 
     96 /*
     97  * Curl_resolver_global_init() - the generic low-level asynchronous name
     98  * resolve API.  Called from curl_global_init() to initialize global resolver
     99  * environment.  Initializes ares library.
    100  */
    101 int Curl_resolver_global_init(void)
    102 {
    103 #ifdef CARES_HAVE_ARES_LIBRARY_INIT
    104   if(ares_library_init(ARES_LIB_INIT_ALL)) {
    105     return CURLE_FAILED_INIT;
    106   }
    107 #endif
    108   return CURLE_OK;
    109 }
    110 
    111 /*
    112  * Curl_resolver_global_cleanup()
    113  *
    114  * Called from curl_global_cleanup() to destroy global resolver environment.
    115  * Deinitializes ares library.
    116  */
    117 void Curl_resolver_global_cleanup(void)
    118 {
    119 #ifdef CARES_HAVE_ARES_LIBRARY_CLEANUP
    120   ares_library_cleanup();
    121 #endif
    122 }
    123 
    124 /*
    125  * Curl_resolver_init()
    126  *
    127  * Called from curl_easy_init() -> Curl_open() to initialize resolver
    128  * URL-state specific environment ('resolver' member of the UrlState
    129  * structure).  Fills the passed pointer by the initialized ares_channel.
    130  */
    131 CURLcode Curl_resolver_init(void **resolver)
    132 {
    133   int status = ares_init((ares_channel*)resolver);
    134   if(status != ARES_SUCCESS) {
    135     if(status == ARES_ENOMEM)
    136       return CURLE_OUT_OF_MEMORY;
    137     else
    138       return CURLE_FAILED_INIT;
    139   }
    140   return CURLE_OK;
    141   /* make sure that all other returns from this function should destroy the
    142      ares channel before returning error! */
    143 }
    144 
    145 /*
    146  * Curl_resolver_cleanup()
    147  *
    148  * Called from curl_easy_cleanup() -> Curl_close() to cleanup resolver
    149  * URL-state specific environment ('resolver' member of the UrlState
    150  * structure).  Destroys the ares channel.
    151  */
    152 void Curl_resolver_cleanup(void *resolver)
    153 {
    154   ares_destroy((ares_channel)resolver);
    155 }
    156 
    157 /*
    158  * Curl_resolver_duphandle()
    159  *
    160  * Called from curl_easy_duphandle() to duplicate resolver URL-state specific
    161  * environment ('resolver' member of the UrlState structure).  Duplicates the
    162  * 'from' ares channel and passes the resulting channel to the 'to' pointer.
    163  */
    164 int Curl_resolver_duphandle(void **to, void *from)
    165 {
    166   /* Clone the ares channel for the new handle */
    167   if(ARES_SUCCESS != ares_dup((ares_channel*)to, (ares_channel)from))
    168     return CURLE_FAILED_INIT;
    169   return CURLE_OK;
    170 }
    171 
    172 static void destroy_async_data (struct Curl_async *async);
    173 
    174 /*
    175  * Cancel all possibly still on-going resolves for this connection.
    176  */
    177 void Curl_resolver_cancel(struct connectdata *conn)
    178 {
    179   if(conn->data && conn->data->state.resolver)
    180     ares_cancel((ares_channel)conn->data->state.resolver);
    181   destroy_async_data(&conn->async);
    182 }
    183 
    184 /*
    185  * destroy_async_data() cleans up async resolver data.
    186  */
    187 static void destroy_async_data (struct Curl_async *async)
    188 {
    189   free(async->hostname);
    190 
    191   if(async->os_specific) {
    192     struct ResolverResults *res = (struct ResolverResults *)async->os_specific;
    193     if(res) {
    194       if(res->temp_ai) {
    195         Curl_freeaddrinfo(res->temp_ai);
    196         res->temp_ai = NULL;
    197       }
    198       free(res);
    199     }
    200     async->os_specific = NULL;
    201   }
    202 
    203   async->hostname = NULL;
    204 }
    205 
    206 /*
    207  * Curl_resolver_getsock() is called when someone from the outside world
    208  * (using curl_multi_fdset()) wants to get our fd_set setup and we're talking
    209  * with ares. The caller must make sure that this function is only called when
    210  * we have a working ares channel.
    211  *
    212  * Returns: sockets-in-use-bitmap
    213  */
    214 
    215 int Curl_resolver_getsock(struct connectdata *conn,
    216                           curl_socket_t *socks,
    217                           int numsocks)
    218 
    219 {
    220   struct timeval maxtime;
    221   struct timeval timebuf;
    222   struct timeval *timeout;
    223   long milli;
    224   int max = ares_getsock((ares_channel)conn->data->state.resolver,
    225                          (ares_socket_t *)socks, numsocks);
    226 
    227   maxtime.tv_sec = CURL_TIMEOUT_RESOLVE;
    228   maxtime.tv_usec = 0;
    229 
    230   timeout = ares_timeout((ares_channel)conn->data->state.resolver, &maxtime,
    231                          &timebuf);
    232   milli = (timeout->tv_sec * 1000) + (timeout->tv_usec/1000);
    233   if(milli == 0)
    234     milli += 10;
    235   Curl_expire_latest(conn->data, milli);
    236 
    237   return max;
    238 }
    239 
    240 /*
    241  * waitperform()
    242  *
    243  * 1) Ask ares what sockets it currently plays with, then
    244  * 2) wait for the timeout period to check for action on ares' sockets.
    245  * 3) tell ares to act on all the sockets marked as "with action"
    246  *
    247  * return number of sockets it worked on
    248  */
    249 
    250 static int waitperform(struct connectdata *conn, int timeout_ms)
    251 {
    252   struct SessionHandle *data = conn->data;
    253   int nfds;
    254   int bitmask;
    255   ares_socket_t socks[ARES_GETSOCK_MAXNUM];
    256   struct pollfd pfd[ARES_GETSOCK_MAXNUM];
    257   int i;
    258   int num = 0;
    259 
    260   bitmask = ares_getsock((ares_channel)data->state.resolver, socks,
    261                          ARES_GETSOCK_MAXNUM);
    262 
    263   for(i=0; i < ARES_GETSOCK_MAXNUM; i++) {
    264     pfd[i].events = 0;
    265     pfd[i].revents = 0;
    266     if(ARES_GETSOCK_READABLE(bitmask, i)) {
    267       pfd[i].fd = socks[i];
    268       pfd[i].events |= POLLRDNORM|POLLIN;
    269     }
    270     if(ARES_GETSOCK_WRITABLE(bitmask, i)) {
    271       pfd[i].fd = socks[i];
    272       pfd[i].events |= POLLWRNORM|POLLOUT;
    273     }
    274     if(pfd[i].events != 0)
    275       num++;
    276     else
    277       break;
    278   }
    279 
    280   if(num)
    281     nfds = Curl_poll(pfd, num, timeout_ms);
    282   else
    283     nfds = 0;
    284 
    285   if(!nfds)
    286     /* Call ares_process() unconditonally here, even if we simply timed out
    287        above, as otherwise the ares name resolve won't timeout! */
    288     ares_process_fd((ares_channel)data->state.resolver, ARES_SOCKET_BAD,
    289                     ARES_SOCKET_BAD);
    290   else {
    291     /* move through the descriptors and ask for processing on them */
    292     for(i=0; i < num; i++)
    293       ares_process_fd((ares_channel)data->state.resolver,
    294                       pfd[i].revents & (POLLRDNORM|POLLIN)?
    295                       pfd[i].fd:ARES_SOCKET_BAD,
    296                       pfd[i].revents & (POLLWRNORM|POLLOUT)?
    297                       pfd[i].fd:ARES_SOCKET_BAD);
    298   }
    299   return nfds;
    300 }
    301 
    302 /*
    303  * Curl_resolver_is_resolved() is called repeatedly to check if a previous
    304  * name resolve request has completed. It should also make sure to time-out if
    305  * the operation seems to take too long.
    306  *
    307  * Returns normal CURLcode errors.
    308  */
    309 CURLcode Curl_resolver_is_resolved(struct connectdata *conn,
    310                                    struct Curl_dns_entry **dns)
    311 {
    312   struct SessionHandle *data = conn->data;
    313   struct ResolverResults *res = (struct ResolverResults *)
    314     conn->async.os_specific;
    315   CURLcode result = CURLE_OK;
    316 
    317   *dns = NULL;
    318 
    319   waitperform(conn, 0);
    320 
    321   if(res && !res->num_pending) {
    322     (void)Curl_addrinfo_callback(conn, res->last_status, res->temp_ai);
    323     /* temp_ai ownership is moved to the connection, so we need not free-up
    324        them */
    325     res->temp_ai = NULL;
    326     if(!conn->async.dns) {
    327       failf(data, "Could not resolve: %s (%s)",
    328             conn->async.hostname, ares_strerror(conn->async.status));
    329       result = conn->bits.proxy?CURLE_COULDNT_RESOLVE_PROXY:
    330         CURLE_COULDNT_RESOLVE_HOST;
    331     }
    332     else
    333       *dns = conn->async.dns;
    334 
    335     destroy_async_data(&conn->async);
    336   }
    337 
    338   return result;
    339 }
    340 
    341 /*
    342  * Curl_resolver_wait_resolv()
    343  *
    344  * waits for a resolve to finish. This function should be avoided since using
    345  * this risk getting the multi interface to "hang".
    346  *
    347  * If 'entry' is non-NULL, make it point to the resolved dns entry
    348  *
    349  * Returns CURLE_COULDNT_RESOLVE_HOST if the host was not resolved, and
    350  * CURLE_OPERATION_TIMEDOUT if a time-out occurred.
    351  */
    352 CURLcode Curl_resolver_wait_resolv(struct connectdata *conn,
    353                                    struct Curl_dns_entry **entry)
    354 {
    355   CURLcode result = CURLE_OK;
    356   struct SessionHandle *data = conn->data;
    357   long timeout;
    358   struct timeval now = Curl_tvnow();
    359   struct Curl_dns_entry *temp_entry;
    360 
    361   timeout = Curl_timeleft(data, &now, TRUE);
    362   if(!timeout)
    363     timeout = CURL_TIMEOUT_RESOLVE * 1000; /* default name resolve timeout */
    364 
    365   /* Wait for the name resolve query to complete. */
    366   for(;;) {
    367     struct timeval *tvp, tv, store;
    368     long timediff;
    369     int itimeout;
    370     int timeout_ms;
    371 
    372     itimeout = (timeout > (long)INT_MAX) ? INT_MAX : (int)timeout;
    373 
    374     store.tv_sec = itimeout/1000;
    375     store.tv_usec = (itimeout%1000)*1000;
    376 
    377     tvp = ares_timeout((ares_channel)data->state.resolver, &store, &tv);
    378 
    379     /* use the timeout period ares returned to us above if less than one
    380        second is left, otherwise just use 1000ms to make sure the progress
    381        callback gets called frequent enough */
    382     if(!tvp->tv_sec)
    383       timeout_ms = (int)(tvp->tv_usec/1000);
    384     else
    385       timeout_ms = 1000;
    386 
    387     waitperform(conn, timeout_ms);
    388     Curl_resolver_is_resolved(conn, &temp_entry);
    389 
    390     if(conn->async.done)
    391       break;
    392 
    393     if(Curl_pgrsUpdate(conn)) {
    394       result = CURLE_ABORTED_BY_CALLBACK;
    395       timeout = -1; /* trigger the cancel below */
    396     }
    397     else {
    398       struct timeval now2 = Curl_tvnow();
    399       timediff = Curl_tvdiff(now2, now); /* spent time */
    400       timeout -= timediff?timediff:1; /* always deduct at least 1 */
    401       now = now2; /* for next loop */
    402     }
    403 
    404     if(timeout < 0) {
    405       /* our timeout, so we cancel the ares operation */
    406       ares_cancel((ares_channel)data->state.resolver);
    407       break;
    408     }
    409   }
    410 
    411   /* Operation complete, if the lookup was successful we now have the entry
    412      in the cache. */
    413   if(entry)
    414     *entry = conn->async.dns;
    415 
    416   if(result)
    417     /* close the connection, since we can't return failure here without
    418        cleaning up this connection properly.
    419        TODO: remove this action from here, it is not a name resolver decision.
    420     */
    421     connclose(conn, "c-ares resolve failed");
    422 
    423   return result;
    424 }
    425 
    426 /* Connects results to the list */
    427 static void compound_results(struct ResolverResults *res,
    428                              Curl_addrinfo *ai)
    429 {
    430   Curl_addrinfo *ai_tail;
    431   if(!ai)
    432     return;
    433   ai_tail = ai;
    434 
    435   while(ai_tail->ai_next)
    436     ai_tail = ai_tail->ai_next;
    437 
    438   /* Add the new results to the list of old results. */
    439   ai_tail->ai_next = res->temp_ai;
    440   res->temp_ai = ai;
    441 }
    442 
    443 /*
    444  * ares_query_completed_cb() is the callback that ares will call when
    445  * the host query initiated by ares_gethostbyname() from Curl_getaddrinfo(),
    446  * when using ares, is completed either successfully or with failure.
    447  */
    448 static void query_completed_cb(void *arg,  /* (struct connectdata *) */
    449                                int status,
    450 #ifdef HAVE_CARES_CALLBACK_TIMEOUTS
    451                                int timeouts,
    452 #endif
    453                                struct hostent *hostent)
    454 {
    455   struct connectdata *conn = (struct connectdata *)arg;
    456   struct ResolverResults *res;
    457 
    458 #ifdef HAVE_CARES_CALLBACK_TIMEOUTS
    459   (void)timeouts; /* ignored */
    460 #endif
    461 
    462   if(ARES_EDESTRUCTION == status)
    463     /* when this ares handle is getting destroyed, the 'arg' pointer may not
    464        be valid so only defer it when we know the 'status' says its fine! */
    465     return;
    466 
    467   res = (struct ResolverResults *)conn->async.os_specific;
    468   res->num_pending--;
    469 
    470   if(CURL_ASYNC_SUCCESS == status) {
    471     Curl_addrinfo *ai = Curl_he2ai(hostent, conn->async.port);
    472     if(ai) {
    473       compound_results(res, ai);
    474     }
    475   }
    476   /* A successful result overwrites any previous error */
    477   if(res->last_status != ARES_SUCCESS)
    478     res->last_status = status;
    479 }
    480 
    481 /*
    482  * Curl_resolver_getaddrinfo() - when using ares
    483  *
    484  * Returns name information about the given hostname and port number. If
    485  * successful, the 'hostent' is returned and the forth argument will point to
    486  * memory we need to free after use. That memory *MUST* be freed with
    487  * Curl_freeaddrinfo(), nothing else.
    488  */
    489 Curl_addrinfo *Curl_resolver_getaddrinfo(struct connectdata *conn,
    490                                          const char *hostname,
    491                                          int port,
    492                                          int *waitp)
    493 {
    494   char *bufp;
    495   struct SessionHandle *data = conn->data;
    496   struct in_addr in;
    497   int family = PF_INET;
    498 #ifdef ENABLE_IPV6 /* CURLRES_IPV6 */
    499   struct in6_addr in6;
    500 #endif /* CURLRES_IPV6 */
    501 
    502   *waitp = 0; /* default to synchronous response */
    503 
    504   /* First check if this is an IPv4 address string */
    505   if(Curl_inet_pton(AF_INET, hostname, &in) > 0) {
    506     /* This is a dotted IP address 123.123.123.123-style */
    507     return Curl_ip2addr(AF_INET, &in, hostname, port);
    508   }
    509 
    510 #ifdef ENABLE_IPV6 /* CURLRES_IPV6 */
    511   /* Otherwise, check if this is an IPv6 address string */
    512   if(Curl_inet_pton (AF_INET6, hostname, &in6) > 0)
    513     /* This must be an IPv6 address literal.  */
    514     return Curl_ip2addr(AF_INET6, &in6, hostname, port);
    515 
    516   switch(conn->ip_version) {
    517   default:
    518 #if ARES_VERSION >= 0x010601
    519     family = PF_UNSPEC; /* supported by c-ares since 1.6.1, so for older
    520                            c-ares versions this just falls through and defaults
    521                            to PF_INET */
    522     break;
    523 #endif
    524   case CURL_IPRESOLVE_V4:
    525     family = PF_INET;
    526     break;
    527   case CURL_IPRESOLVE_V6:
    528     family = PF_INET6;
    529     break;
    530   }
    531 #endif /* CURLRES_IPV6 */
    532 
    533   bufp = strdup(hostname);
    534   if(bufp) {
    535     struct ResolverResults *res = NULL;
    536     free(conn->async.hostname);
    537     conn->async.hostname = bufp;
    538     conn->async.port = port;
    539     conn->async.done = FALSE;   /* not done */
    540     conn->async.status = 0;     /* clear */
    541     conn->async.dns = NULL;     /* clear */
    542     res = calloc(sizeof(struct ResolverResults), 1);
    543     if(!res) {
    544       free(conn->async.hostname);
    545       conn->async.hostname = NULL;
    546       return NULL;
    547     }
    548     conn->async.os_specific = res;
    549 
    550     /* initial status - failed */
    551     res->last_status = ARES_ENOTFOUND;
    552 #ifdef ENABLE_IPV6 /* CURLRES_IPV6 */
    553     if(family == PF_UNSPEC) {
    554       if(Curl_ipv6works()) {
    555         res->num_pending = 2;
    556 
    557         /* areschannel is already setup in the Curl_open() function */
    558         ares_gethostbyname((ares_channel)data->state.resolver, hostname,
    559                             PF_INET, query_completed_cb, conn);
    560         ares_gethostbyname((ares_channel)data->state.resolver, hostname,
    561                             PF_INET6, query_completed_cb, conn);
    562       }
    563       else {
    564         res->num_pending = 1;
    565 
    566         /* areschannel is already setup in the Curl_open() function */
    567         ares_gethostbyname((ares_channel)data->state.resolver, hostname,
    568                             PF_INET, query_completed_cb, conn);
    569       }
    570     }
    571     else
    572 #endif /* CURLRES_IPV6 */
    573     {
    574       res->num_pending = 1;
    575 
    576       /* areschannel is already setup in the Curl_open() function */
    577       ares_gethostbyname((ares_channel)data->state.resolver, hostname, family,
    578                          query_completed_cb, conn);
    579     }
    580 
    581     *waitp = 1; /* expect asynchronous response */
    582   }
    583   return NULL; /* no struct yet */
    584 }
    585 
    586 CURLcode Curl_set_dns_servers(struct SessionHandle *data,
    587                               char *servers)
    588 {
    589   CURLcode result = CURLE_NOT_BUILT_IN;
    590   int ares_result;
    591 
    592   /* If server is NULL or empty, this would purge all DNS servers
    593    * from ares library, which will cause any and all queries to fail.
    594    * So, just return OK if none are configured and don't actually make
    595    * any changes to c-ares.  This lets c-ares use it's defaults, which
    596    * it gets from the OS (for instance from /etc/resolv.conf on Linux).
    597    */
    598   if(!(servers && servers[0]))
    599     return CURLE_OK;
    600 
    601 #if (ARES_VERSION >= 0x010704)
    602   ares_result = ares_set_servers_csv(data->state.resolver, servers);
    603   switch(ares_result) {
    604   case ARES_SUCCESS:
    605     result = CURLE_OK;
    606     break;
    607   case ARES_ENOMEM:
    608     result = CURLE_OUT_OF_MEMORY;
    609     break;
    610   case ARES_ENOTINITIALIZED:
    611   case ARES_ENODATA:
    612   case ARES_EBADSTR:
    613   default:
    614     result = CURLE_BAD_FUNCTION_ARGUMENT;
    615     break;
    616   }
    617 #else /* too old c-ares version! */
    618   (void)data;
    619   (void)(ares_result);
    620 #endif
    621   return result;
    622 }
    623 
    624 CURLcode Curl_set_dns_interface(struct SessionHandle *data,
    625                                 const char *interf)
    626 {
    627 #if (ARES_VERSION >= 0x010704)
    628   if(!interf)
    629     interf = "";
    630 
    631   ares_set_local_dev((ares_channel)data->state.resolver, interf);
    632 
    633   return CURLE_OK;
    634 #else /* c-ares version too old! */
    635   (void)data;
    636   (void)interf;
    637   return CURLE_NOT_BUILT_IN;
    638 #endif
    639 }
    640 
    641 CURLcode Curl_set_dns_local_ip4(struct SessionHandle *data,
    642                                 const char *local_ip4)
    643 {
    644 #if (ARES_VERSION >= 0x010704)
    645   struct in_addr a4;
    646 
    647   if((!local_ip4) || (local_ip4[0] == 0)) {
    648     a4.s_addr = 0; /* disabled: do not bind to a specific address */
    649   }
    650   else {
    651     if(Curl_inet_pton(AF_INET, local_ip4, &a4) != 1) {
    652       return CURLE_BAD_FUNCTION_ARGUMENT;
    653     }
    654   }
    655 
    656   ares_set_local_ip4((ares_channel)data->state.resolver, ntohl(a4.s_addr));
    657 
    658   return CURLE_OK;
    659 #else /* c-ares version too old! */
    660   (void)data;
    661   (void)local_ip4;
    662   return CURLE_NOT_BUILT_IN;
    663 #endif
    664 }
    665 
    666 CURLcode Curl_set_dns_local_ip6(struct SessionHandle *data,
    667                                 const char *local_ip6)
    668 {
    669 #if (ARES_VERSION >= 0x010704) && defined(ENABLE_IPV6)
    670   unsigned char a6[INET6_ADDRSTRLEN];
    671 
    672   if((!local_ip6) || (local_ip6[0] == 0)) {
    673     /* disabled: do not bind to a specific address */
    674     memset(a6, 0, sizeof(a6));
    675   }
    676   else {
    677     if(Curl_inet_pton(AF_INET6, local_ip6, a6) != 1) {
    678       return CURLE_BAD_FUNCTION_ARGUMENT;
    679     }
    680   }
    681 
    682   ares_set_local_ip6((ares_channel)data->state.resolver, a6);
    683 
    684   return CURLE_OK;
    685 #else /* c-ares version too old! */
    686   (void)data;
    687   (void)local_ip6;
    688   return CURLE_NOT_BUILT_IN;
    689 #endif
    690 }
    691 #endif /* CURLRES_ARES */
    692