VirtualBox

source: vbox/trunk/src/libs/curl-7.64.0/lib/asyn-ares.c@ 85671

Last change on this file since 85671 was 85671, checked in by vboxsync, 4 years ago

Export out internal curl copy to make it a lot simpler to build VBox (OSE) on Windows. bugref:9814

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

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette