1/***************************************************************************
2 * _ _ ____ _
3 * Project ___| | | | _ \| |
4 * / __| | | | |_) | |
5 * | (__| |_| | _ <| |___
6 * \___|\___/|_| \_\_____|
7 *
8 * Copyright (C) 1998 - 2019, 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 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#include "socketpair.h"
25
26/***********************************************************************
27 * Only for threaded name resolves builds
28 **********************************************************************/
29#ifdef CURLRES_THREADED
30
31#ifdef HAVE_NETINET_IN_H
32#include <netinet/in.h>
33#endif
34#ifdef HAVE_NETDB_H
35#include <netdb.h>
36#endif
37#ifdef HAVE_ARPA_INET_H
38#include <arpa/inet.h>
39#endif
40#ifdef __VMS
41#include <in.h>
42#include <inet.h>
43#endif
44
45#if defined(USE_THREADS_POSIX)
46# ifdef HAVE_PTHREAD_H
47# include <pthread.h>
48# endif
49#elif defined(USE_THREADS_WIN32)
50# ifdef HAVE_PROCESS_H
51# include <process.h>
52# endif
53#endif
54
55#if (defined(NETWARE) && defined(__NOVELL_LIBC__))
56#undef in_addr_t
57#define in_addr_t unsigned long
58#endif
59
60#ifdef HAVE_GETADDRINFO
61# define RESOLVER_ENOMEM EAI_MEMORY
62#else
63# define RESOLVER_ENOMEM ENOMEM
64#endif
65
66#include "urldata.h"
67#include "sendf.h"
68#include "hostip.h"
69#include "hash.h"
70#include "share.h"
71#include "strerror.h"
72#include "url.h"
73#include "multiif.h"
74#include "inet_pton.h"
75#include "inet_ntop.h"
76#include "curl_threads.h"
77#include "connect.h"
78#include "socketpair.h"
79/* The last 3 #include files should be in this order */
80#include "curl_printf.h"
81#include "curl_memory.h"
82#include "memdebug.h"
83
84struct resdata {
85 struct curltime start;
86};
87
88/*
89 * Curl_resolver_global_init()
90 * Called from curl_global_init() to initialize global resolver environment.
91 * Does nothing here.
92 */
93int Curl_resolver_global_init(void)
94{
95 return CURLE_OK;
96}
97
98/*
99 * Curl_resolver_global_cleanup()
100 * Called from curl_global_cleanup() to destroy global resolver environment.
101 * Does nothing here.
102 */
103void Curl_resolver_global_cleanup(void)
104{
105}
106
107/*
108 * Curl_resolver_init()
109 * Called from curl_easy_init() -> Curl_open() to initialize resolver
110 * URL-state specific environment ('resolver' member of the UrlState
111 * structure).
112 */
113CURLcode Curl_resolver_init(struct Curl_easy *easy, void **resolver)
114{
115 (void)easy;
116 *resolver = calloc(1, sizeof(struct resdata));
117 if(!*resolver)
118 return CURLE_OUT_OF_MEMORY;
119 return CURLE_OK;
120}
121
122/*
123 * Curl_resolver_cleanup()
124 * Called from curl_easy_cleanup() -> Curl_close() to cleanup resolver
125 * URL-state specific environment ('resolver' member of the UrlState
126 * structure).
127 */
128void Curl_resolver_cleanup(void *resolver)
129{
130 free(resolver);
131}
132
133/*
134 * Curl_resolver_duphandle()
135 * Called from curl_easy_duphandle() to duplicate resolver URL state-specific
136 * environment ('resolver' member of the UrlState structure).
137 */
138CURLcode Curl_resolver_duphandle(struct Curl_easy *easy, void **to, void *from)
139{
140 (void)from;
141 return Curl_resolver_init(easy, to);
142}
143
144static void destroy_async_data(struct Curl_async *);
145
146/*
147 * Cancel all possibly still on-going resolves for this connection.
148 */
149void Curl_resolver_cancel(struct connectdata *conn)
150{
151 destroy_async_data(&conn->async);
152}
153
154/* This function is used to init a threaded resolve */
155static bool init_resolve_thread(struct connectdata *conn,
156 const char *hostname, int port,
157 const struct addrinfo *hints);
158
159
160/* Data for synchronization between resolver thread and its parent */
161struct thread_sync_data {
162 curl_mutex_t * mtx;
163 int done;
164
165 char *hostname; /* hostname to resolve, Curl_async.hostname
166 duplicate */
167 int port;
168#ifdef USE_SOCKETPAIR
169 struct connectdata *conn;
170 curl_socket_t sock_pair[2]; /* socket pair */
171#endif
172 int sock_error;
173 Curl_addrinfo *res;
174#ifdef HAVE_GETADDRINFO
175 struct addrinfo hints;
176#endif
177 struct thread_data *td; /* for thread-self cleanup */
178};
179
180struct thread_data {
181 curl_thread_t thread_hnd;
182 unsigned int poll_interval;
183 time_t interval_end;
184 struct thread_sync_data tsd;
185};
186
187static struct thread_sync_data *conn_thread_sync_data(struct connectdata *conn)
188{
189 return &(((struct thread_data *)conn->async.os_specific)->tsd);
190}
191
192/* Destroy resolver thread synchronization data */
193static
194void destroy_thread_sync_data(struct thread_sync_data * tsd)
195{
196 if(tsd->mtx) {
197 Curl_mutex_destroy(tsd->mtx);
198 free(tsd->mtx);
199 }
200
201 free(tsd->hostname);
202
203 if(tsd->res)
204 Curl_freeaddrinfo(tsd->res);
205
206#ifdef USE_SOCKETPAIR
207 /*
208 * close one end of the socket pair (may be done in resolver thread);
209 * the other end (for reading) is always closed in the parent thread.
210 */
211 if(tsd->sock_pair[1] != CURL_SOCKET_BAD) {
212 sclose(tsd->sock_pair[1]);
213 }
214#endif
215 memset(tsd, 0, sizeof(*tsd));
216}
217
218/* Initialize resolver thread synchronization data */
219static
220int init_thread_sync_data(struct thread_data * td,
221 const char *hostname,
222 int port,
223 const struct addrinfo *hints)
224{
225 struct thread_sync_data *tsd = &td->tsd;
226
227 memset(tsd, 0, sizeof(*tsd));
228
229 tsd->td = td;
230 tsd->port = port;
231 /* Treat the request as done until the thread actually starts so any early
232 * cleanup gets done properly.
233 */
234 tsd->done = 1;
235#ifdef HAVE_GETADDRINFO
236 DEBUGASSERT(hints);
237 tsd->hints = *hints;
238#else
239 (void) hints;
240#endif
241
242 tsd->mtx = malloc(sizeof(curl_mutex_t));
243 if(tsd->mtx == NULL)
244 goto err_exit;
245
246 Curl_mutex_init(tsd->mtx);
247
248#ifdef USE_SOCKETPAIR
249 /* create socket pair, avoid AF_LOCAL since it doesn't build on Solaris */
250 if(Curl_socketpair(AF_UNIX, SOCK_STREAM, 0, &tsd->sock_pair[0]) < 0) {
251 tsd->sock_pair[0] = CURL_SOCKET_BAD;
252 tsd->sock_pair[1] = CURL_SOCKET_BAD;
253 goto err_exit;
254 }
255#endif
256 tsd->sock_error = CURL_ASYNC_SUCCESS;
257
258 /* Copying hostname string because original can be destroyed by parent
259 * thread during gethostbyname execution.
260 */
261 tsd->hostname = strdup(hostname);
262 if(!tsd->hostname)
263 goto err_exit;
264
265 return 1;
266
267 err_exit:
268 /* Memory allocation failed */
269 destroy_thread_sync_data(tsd);
270 return 0;
271}
272
273static int getaddrinfo_complete(struct connectdata *conn)
274{
275 struct thread_sync_data *tsd = conn_thread_sync_data(conn);
276 int rc;
277
278 rc = Curl_addrinfo_callback(conn, tsd->sock_error, tsd->res);
279 /* The tsd->res structure has been copied to async.dns and perhaps the DNS
280 cache. Set our copy to NULL so destroy_thread_sync_data doesn't free it.
281 */
282 tsd->res = NULL;
283
284 return rc;
285}
286
287
288#ifdef HAVE_GETADDRINFO
289
290/*
291 * getaddrinfo_thread() resolves a name and then exits.
292 *
293 * For builds without ARES, but with ENABLE_IPV6, create a resolver thread
294 * and wait on it.
295 */
296static unsigned int CURL_STDCALL getaddrinfo_thread(void *arg)
297{
298 struct thread_sync_data *tsd = (struct thread_sync_data*)arg;
299 struct thread_data *td = tsd->td;
300 char service[12];
301 int rc;
302#ifdef USE_SOCKETPAIR
303 char buf[1];
304#endif
305
306 msnprintf(service, sizeof(service), "%d", tsd->port);
307
308 rc = Curl_getaddrinfo_ex(tsd->hostname, service, &tsd->hints, &tsd->res);
309
310 if(rc != 0) {
311 tsd->sock_error = SOCKERRNO?SOCKERRNO:rc;
312 if(tsd->sock_error == 0)
313 tsd->sock_error = RESOLVER_ENOMEM;
314 }
315 else {
316 Curl_addrinfo_set_port(tsd->res, tsd->port);
317 }
318
319 Curl_mutex_acquire(tsd->mtx);
320 if(tsd->done) {
321 /* too late, gotta clean up the mess */
322 Curl_mutex_release(tsd->mtx);
323 destroy_thread_sync_data(tsd);
324 free(td);
325 }
326 else {
327#ifdef USE_SOCKETPAIR
328 if(tsd->sock_pair[1] != CURL_SOCKET_BAD) {
329 /* DNS has been resolved, signal client task */
330 buf[0] = 1;
331 if(swrite(tsd->sock_pair[1], buf, sizeof(buf)) < 0) {
332 /* update sock_erro to errno */
333 tsd->sock_error = SOCKERRNO;
334 }
335 }
336#endif
337 tsd->done = 1;
338 Curl_mutex_release(tsd->mtx);
339 }
340
341 return 0;
342}
343
344#else /* HAVE_GETADDRINFO */
345
346/*
347 * gethostbyname_thread() resolves a name and then exits.
348 */
349static unsigned int CURL_STDCALL gethostbyname_thread(void *arg)
350{
351 struct thread_sync_data *tsd = (struct thread_sync_data *)arg;
352 struct thread_data *td = tsd->td;
353
354 tsd->res = Curl_ipv4_resolve_r(tsd->hostname, tsd->port);
355
356 if(!tsd->res) {
357 tsd->sock_error = SOCKERRNO;
358 if(tsd->sock_error == 0)
359 tsd->sock_error = RESOLVER_ENOMEM;
360 }
361
362 Curl_mutex_acquire(tsd->mtx);
363 if(tsd->done) {
364 /* too late, gotta clean up the mess */
365 Curl_mutex_release(tsd->mtx);
366 destroy_thread_sync_data(tsd);
367 free(td);
368 }
369 else {
370 tsd->done = 1;
371 Curl_mutex_release(tsd->mtx);
372 }
373
374 return 0;
375}
376
377#endif /* HAVE_GETADDRINFO */
378
379/*
380 * destroy_async_data() cleans up async resolver data and thread handle.
381 */
382static void destroy_async_data(struct Curl_async *async)
383{
384 if(async->os_specific) {
385 struct thread_data *td = (struct thread_data*) async->os_specific;
386 int done;
387#ifdef USE_SOCKETPAIR
388 curl_socket_t sock_rd = td->tsd.sock_pair[0];
389 struct connectdata *conn = td->tsd.conn;
390#endif
391
392 /*
393 * if the thread is still blocking in the resolve syscall, detach it and
394 * let the thread do the cleanup...
395 */
396 Curl_mutex_acquire(td->tsd.mtx);
397 done = td->tsd.done;
398 td->tsd.done = 1;
399 Curl_mutex_release(td->tsd.mtx);
400
401 if(!done) {
402 Curl_thread_destroy(td->thread_hnd);
403 }
404 else {
405 if(td->thread_hnd != curl_thread_t_null)
406 Curl_thread_join(&td->thread_hnd);
407
408 destroy_thread_sync_data(&td->tsd);
409
410 free(async->os_specific);
411 }
412#ifdef USE_SOCKETPAIR
413 /*
414 * ensure CURLMOPT_SOCKETFUNCTION fires CURL_POLL_REMOVE
415 * before the FD is invalidated to avoid EBADF on EPOLL_CTL_DEL
416 */
417 if(conn)
418 Curl_multi_closed(conn->data, sock_rd);
419 sclose(sock_rd);
420#endif
421 }
422 async->os_specific = NULL;
423
424 free(async->hostname);
425 async->hostname = NULL;
426}
427
428/*
429 * init_resolve_thread() starts a new thread that performs the actual
430 * resolve. This function returns before the resolve is done.
431 *
432 * Returns FALSE in case of failure, otherwise TRUE.
433 */
434static bool init_resolve_thread(struct connectdata *conn,
435 const char *hostname, int port,
436 const struct addrinfo *hints)
437{
438 struct thread_data *td = calloc(1, sizeof(struct thread_data));
439 int err = ENOMEM;
440
441 conn->async.os_specific = (void *)td;
442 if(!td)
443 goto errno_exit;
444
445 conn->async.port = port;
446 conn->async.done = FALSE;
447 conn->async.status = 0;
448 conn->async.dns = NULL;
449 td->thread_hnd = curl_thread_t_null;
450
451 if(!init_thread_sync_data(td, hostname, port, hints)) {
452 conn->async.os_specific = NULL;
453 free(td);
454 goto errno_exit;
455 }
456
457 free(conn->async.hostname);
458 conn->async.hostname = strdup(hostname);
459 if(!conn->async.hostname)
460 goto err_exit;
461
462 /* The thread will set this to 1 when complete. */
463 td->tsd.done = 0;
464
465#ifdef HAVE_GETADDRINFO
466 td->thread_hnd = Curl_thread_create(getaddrinfo_thread, &td->tsd);
467#else
468 td->thread_hnd = Curl_thread_create(gethostbyname_thread, &td->tsd);
469#endif
470
471 if(!td->thread_hnd) {
472 /* The thread never started, so mark it as done here for proper cleanup. */
473 td->tsd.done = 1;
474 err = errno;
475 goto err_exit;
476 }
477
478 return TRUE;
479
480 err_exit:
481 destroy_async_data(&conn->async);
482
483 errno_exit:
484 errno = err;
485 return FALSE;
486}
487
488/*
489 * resolver_error() calls failf() with the appropriate message after a resolve
490 * error
491 */
492
493static CURLcode resolver_error(struct connectdata *conn)
494{
495 const char *host_or_proxy;
496 CURLcode result;
497
498 if(conn->bits.httpproxy) {
499 host_or_proxy = "proxy";
500 result = CURLE_COULDNT_RESOLVE_PROXY;
501 }
502 else {
503 host_or_proxy = "host";
504 result = CURLE_COULDNT_RESOLVE_HOST;
505 }
506
507 failf(conn->data, "Could not resolve %s: %s", host_or_proxy,
508 conn->async.hostname);
509
510 return result;
511}
512
513static CURLcode thread_wait_resolv(struct connectdata *conn,
514 struct Curl_dns_entry **entry,
515 bool report)
516{
517 struct thread_data *td = (struct thread_data*) conn->async.os_specific;
518 CURLcode result = CURLE_OK;
519
520 DEBUGASSERT(conn && td);
521 DEBUGASSERT(td->thread_hnd != curl_thread_t_null);
522
523 /* wait for the thread to resolve the name */
524 if(Curl_thread_join(&td->thread_hnd)) {
525 if(entry)
526 result = getaddrinfo_complete(conn);
527 }
528 else
529 DEBUGASSERT(0);
530
531 conn->async.done = TRUE;
532
533 if(entry)
534 *entry = conn->async.dns;
535
536 if(!conn->async.dns && report)
537 /* a name was not resolved, report error */
538 result = resolver_error(conn);
539
540 destroy_async_data(&conn->async);
541
542 if(!conn->async.dns && report)
543 connclose(conn, "asynch resolve failed");
544
545 return result;
546}
547
548
549/*
550 * Until we gain a way to signal the resolver threads to stop early, we must
551 * simply wait for them and ignore their results.
552 */
553void Curl_resolver_kill(struct connectdata *conn)
554{
555 struct thread_data *td = (struct thread_data*) conn->async.os_specific;
556
557 /* If we're still resolving, we must wait for the threads to fully clean up,
558 unfortunately. Otherwise, we can simply cancel to clean up any resolver
559 data. */
560 if(td && td->thread_hnd != curl_thread_t_null)
561 (void)thread_wait_resolv(conn, NULL, FALSE);
562 else
563 Curl_resolver_cancel(conn);
564}
565
566/*
567 * Curl_resolver_wait_resolv()
568 *
569 * Waits for a resolve to finish. This function should be avoided since using
570 * this risk getting the multi interface to "hang".
571 *
572 * If 'entry' is non-NULL, make it point to the resolved dns entry
573 *
574 * Returns CURLE_COULDNT_RESOLVE_HOST if the host was not resolved,
575 * CURLE_OPERATION_TIMEDOUT if a time-out occurred, or other errors.
576 *
577 * This is the version for resolves-in-a-thread.
578 */
579CURLcode Curl_resolver_wait_resolv(struct connectdata *conn,
580 struct Curl_dns_entry **entry)
581{
582 return thread_wait_resolv(conn, entry, TRUE);
583}
584
585/*
586 * Curl_resolver_is_resolved() is called repeatedly to check if a previous
587 * name resolve request has completed. It should also make sure to time-out if
588 * the operation seems to take too long.
589 */
590CURLcode Curl_resolver_is_resolved(struct connectdata *conn,
591 struct Curl_dns_entry **entry)
592{
593 struct Curl_easy *data = conn->data;
594 struct thread_data *td = (struct thread_data*) conn->async.os_specific;
595 int done = 0;
596
597 *entry = NULL;
598
599 if(!td) {
600 DEBUGASSERT(td);
601 return CURLE_COULDNT_RESOLVE_HOST;
602 }
603
604 Curl_mutex_acquire(td->tsd.mtx);
605 done = td->tsd.done;
606 Curl_mutex_release(td->tsd.mtx);
607
608 if(done) {
609 getaddrinfo_complete(conn);
610
611 if(!conn->async.dns) {
612 CURLcode result = resolver_error(conn);
613 destroy_async_data(&conn->async);
614 return result;
615 }
616 destroy_async_data(&conn->async);
617 *entry = conn->async.dns;
618 }
619 else {
620 /* poll for name lookup done with exponential backoff up to 250ms */
621 /* should be fine even if this converts to 32 bit */
622 time_t elapsed = (time_t)Curl_timediff(Curl_now(),
623 data->progress.t_startsingle);
624 if(elapsed < 0)
625 elapsed = 0;
626
627 if(td->poll_interval == 0)
628 /* Start at 1ms poll interval */
629 td->poll_interval = 1;
630 else if(elapsed >= td->interval_end)
631 /* Back-off exponentially if last interval expired */
632 td->poll_interval *= 2;
633
634 if(td->poll_interval > 250)
635 td->poll_interval = 250;
636
637 td->interval_end = elapsed + td->poll_interval;
638 Curl_expire(conn->data, td->poll_interval, EXPIRE_ASYNC_NAME);
639 }
640
641 return CURLE_OK;
642}
643
644int Curl_resolver_getsock(struct connectdata *conn,
645 curl_socket_t *socks)
646{
647 int ret_val = 0;
648 time_t milli;
649 timediff_t ms;
650 struct Curl_easy *data = conn->data;
651 struct resdata *reslv = (struct resdata *)data->state.resolver;
652#ifdef USE_SOCKETPAIR
653 struct thread_data *td = (struct thread_data*)conn->async.os_specific;
654#else
655 (void)socks;
656#endif
657
658#ifdef USE_SOCKETPAIR
659 if(td) {
660 /* return read fd to client for polling the DNS resolution status */
661 socks[0] = td->tsd.sock_pair[0];
662 DEBUGASSERT(td->tsd.conn == conn || !td->tsd.conn);
663 td->tsd.conn = conn;
664 ret_val = GETSOCK_READSOCK(0);
665 }
666 else {
667#endif
668 ms = Curl_timediff(Curl_now(), reslv->start);
669 if(ms < 3)
670 milli = 0;
671 else if(ms <= 50)
672 milli = (time_t)ms/3;
673 else if(ms <= 250)
674 milli = 50;
675 else
676 milli = 200;
677 Curl_expire(data, milli, EXPIRE_ASYNC_NAME);
678#ifdef USE_SOCKETPAIR
679 }
680#endif
681
682
683 return ret_val;
684}
685
686#ifndef HAVE_GETADDRINFO
687/*
688 * Curl_getaddrinfo() - for platforms without getaddrinfo
689 */
690Curl_addrinfo *Curl_resolver_getaddrinfo(struct connectdata *conn,
691 const char *hostname,
692 int port,
693 int *waitp)
694{
695 struct in_addr in;
696 struct Curl_easy *data = conn->data;
697 struct resdata *reslv = (struct resdata *)data->state.resolver;
698
699 *waitp = 0; /* default to synchronous response */
700
701 if(Curl_inet_pton(AF_INET, hostname, &in) > 0)
702 /* This is a dotted IP address 123.123.123.123-style */
703 return Curl_ip2addr(AF_INET, &in, hostname, port);
704
705 reslv->start = Curl_now();
706
707 /* fire up a new resolver thread! */
708 if(init_resolve_thread(conn, hostname, port, NULL)) {
709 *waitp = 1; /* expect asynchronous response */
710 return NULL;
711 }
712
713 failf(conn->data, "getaddrinfo() thread failed\n");
714
715 return NULL;
716}
717
718#else /* !HAVE_GETADDRINFO */
719
720/*
721 * Curl_resolver_getaddrinfo() - for getaddrinfo
722 */
723Curl_addrinfo *Curl_resolver_getaddrinfo(struct connectdata *conn,
724 const char *hostname,
725 int port,
726 int *waitp)
727{
728 struct addrinfo hints;
729 char sbuf[12];
730 int pf = PF_INET;
731 struct Curl_easy *data = conn->data;
732 struct resdata *reslv = (struct resdata *)data->state.resolver;
733
734 *waitp = 0; /* default to synchronous response */
735
736#ifndef USE_RESOLVE_ON_IPS
737 {
738 struct in_addr in;
739 /* First check if this is an IPv4 address string */
740 if(Curl_inet_pton(AF_INET, hostname, &in) > 0)
741 /* This is a dotted IP address 123.123.123.123-style */
742 return Curl_ip2addr(AF_INET, &in, hostname, port);
743 }
744#ifdef CURLRES_IPV6
745 {
746 struct in6_addr in6;
747 /* check if this is an IPv6 address string */
748 if(Curl_inet_pton(AF_INET6, hostname, &in6) > 0)
749 /* This is an IPv6 address literal */
750 return Curl_ip2addr(AF_INET6, &in6, hostname, port);
751 }
752#endif /* CURLRES_IPV6 */
753#endif /* !USE_RESOLVE_ON_IPS */
754
755#ifdef CURLRES_IPV6
756 /*
757 * Check if a limited name resolve has been requested.
758 */
759 switch(conn->ip_version) {
760 case CURL_IPRESOLVE_V4:
761 pf = PF_INET;
762 break;
763 case CURL_IPRESOLVE_V6:
764 pf = PF_INET6;
765 break;
766 default:
767 pf = PF_UNSPEC;
768 break;
769 }
770
771 if((pf != PF_INET) && !Curl_ipv6works())
772 /* The stack seems to be a non-IPv6 one */
773 pf = PF_INET;
774#endif /* CURLRES_IPV6 */
775
776 memset(&hints, 0, sizeof(hints));
777 hints.ai_family = pf;
778 hints.ai_socktype = (conn->transport == TRNSPRT_TCP)?
779 SOCK_STREAM : SOCK_DGRAM;
780
781 msnprintf(sbuf, sizeof(sbuf), "%d", port);
782
783 reslv->start = Curl_now();
784 /* fire up a new resolver thread! */
785 if(init_resolve_thread(conn, hostname, port, &hints)) {
786 *waitp = 1; /* expect asynchronous response */
787 return NULL;
788 }
789
790 failf(data, "getaddrinfo() thread failed to start\n");
791 return NULL;
792
793}
794
795#endif /* !HAVE_GETADDRINFO */
796
797CURLcode Curl_set_dns_servers(struct Curl_easy *data,
798 char *servers)
799{
800 (void)data;
801 (void)servers;
802 return CURLE_NOT_BUILT_IN;
803
804}
805
806CURLcode Curl_set_dns_interface(struct Curl_easy *data,
807 const char *interf)
808{
809 (void)data;
810 (void)interf;
811 return CURLE_NOT_BUILT_IN;
812}
813
814CURLcode Curl_set_dns_local_ip4(struct Curl_easy *data,
815 const char *local_ip4)
816{
817 (void)data;
818 (void)local_ip4;
819 return CURLE_NOT_BUILT_IN;
820}
821
822CURLcode Curl_set_dns_local_ip6(struct Curl_easy *data,
823 const char *local_ip6)
824{
825 (void)data;
826 (void)local_ip6;
827 return CURLE_NOT_BUILT_IN;
828}
829
830#endif /* CURLRES_THREADED */
831