• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /***************************************************************************
2  *                                  _   _ ____  _
3  *  Project                     ___| | | |  _ \| |
4  *                             / __| | | | |_) | |
5  *                            | (__| |_| |  _ <| |___
6  *                             \___|\___/|_| \_\_____|
7  *
8  * Copyright (C) 1998 - 2015, Daniel Stenberg, <daniel@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  */
Curl_resolver_global_init(void)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  */
Curl_resolver_global_cleanup(void)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  */
Curl_resolver_init(void ** resolver)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  */
Curl_resolver_cleanup(void * resolver)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  */
Curl_resolver_duphandle(void ** to,void * from)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  */
Curl_resolver_cancel(struct connectdata * conn)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  */
destroy_async_data(struct Curl_async * async)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 
Curl_resolver_getsock(struct connectdata * conn,curl_socket_t * socks,int numsocks)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 
waitperform(struct connectdata * conn,int timeout_ms)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  */
Curl_resolver_is_resolved(struct connectdata * conn,struct Curl_dns_entry ** dns)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  */
Curl_resolver_wait_resolv(struct connectdata * conn,struct Curl_dns_entry ** entry)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 */
compound_results(struct ResolverResults * res,Curl_addrinfo * ai)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  */
query_completed_cb(void * arg,int status,int timeouts,struct hostent * hostent)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  */
Curl_resolver_getaddrinfo(struct connectdata * conn,const char * hostname,int port,int * waitp)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 
Curl_set_dns_servers(struct SessionHandle * data,char * servers)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 
Curl_set_dns_interface(struct SessionHandle * data,const char * interf)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 
Curl_set_dns_local_ip4(struct SessionHandle * data,const char * local_ip4)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 
Curl_set_dns_local_ip6(struct SessionHandle * data,const char * local_ip6)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