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