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