• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
25 /***********************************************************************
26  * Only for threaded name resolves builds
27  **********************************************************************/
28 #ifdef CURLRES_THREADED
29 
30 #ifdef HAVE_NETINET_IN_H
31 #include <netinet/in.h>
32 #endif
33 #ifdef HAVE_NETDB_H
34 #include <netdb.h>
35 #endif
36 #ifdef HAVE_ARPA_INET_H
37 #include <arpa/inet.h>
38 #endif
39 #ifdef __VMS
40 #include <in.h>
41 #include <inet.h>
42 #endif
43 
44 #if defined(USE_THREADS_POSIX)
45 #  ifdef HAVE_PTHREAD_H
46 #    include <pthread.h>
47 #  endif
48 #elif defined(USE_THREADS_WIN32)
49 #  ifdef HAVE_PROCESS_H
50 #    include <process.h>
51 #  endif
52 #endif
53 
54 #if (defined(NETWARE) && defined(__NOVELL_LIBC__))
55 #undef in_addr_t
56 #define in_addr_t unsigned long
57 #endif
58 
59 #ifdef HAVE_GETADDRINFO
60 #  define RESOLVER_ENOMEM  EAI_MEMORY
61 #else
62 #  define RESOLVER_ENOMEM  ENOMEM
63 #endif
64 
65 #include "urldata.h"
66 #include "sendf.h"
67 #include "hostip.h"
68 #include "hash.h"
69 #include "share.h"
70 #include "strerror.h"
71 #include "url.h"
72 #include "multiif.h"
73 #include "inet_pton.h"
74 #include "inet_ntop.h"
75 #include "curl_threads.h"
76 #include "connect.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 connectdata * conn)147 void Curl_resolver_cancel(struct connectdata *conn)
148 {
149   destroy_async_data(&conn->async);
150 }
151 
152 /* This function is used to init a threaded resolve */
153 static bool init_resolve_thread(struct connectdata *conn,
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 
163   char *hostname;        /* hostname to resolve, Curl_async.hostname
164                             duplicate */
165   int port;
166   int sock_error;
167   Curl_addrinfo *res;
168 #ifdef HAVE_GETADDRINFO
169   struct addrinfo hints;
170 #endif
171   struct thread_data *td; /* for thread-self cleanup */
172 };
173 
174 struct thread_data {
175   curl_thread_t thread_hnd;
176   unsigned int poll_interval;
177   time_t interval_end;
178   struct thread_sync_data tsd;
179 };
180 
conn_thread_sync_data(struct connectdata * conn)181 static struct thread_sync_data *conn_thread_sync_data(struct connectdata *conn)
182 {
183   return &(((struct thread_data *)conn->async.os_specific)->tsd);
184 }
185 
186 /* Destroy resolver thread synchronization data */
187 static
destroy_thread_sync_data(struct thread_sync_data * tsd)188 void destroy_thread_sync_data(struct thread_sync_data * tsd)
189 {
190   if(tsd->mtx) {
191     Curl_mutex_destroy(tsd->mtx);
192     free(tsd->mtx);
193   }
194 
195   free(tsd->hostname);
196 
197   if(tsd->res)
198     Curl_freeaddrinfo(tsd->res);
199 
200   memset(tsd, 0, sizeof(*tsd));
201 }
202 
203 /* Initialize resolver thread synchronization data */
204 static
init_thread_sync_data(struct thread_data * td,const char * hostname,int port,const struct addrinfo * hints)205 int init_thread_sync_data(struct thread_data * td,
206                            const char *hostname,
207                            int port,
208                            const struct addrinfo *hints)
209 {
210   struct thread_sync_data *tsd = &td->tsd;
211 
212   memset(tsd, 0, sizeof(*tsd));
213 
214   tsd->td = td;
215   tsd->port = port;
216   /* Treat the request as done until the thread actually starts so any early
217    * cleanup gets done properly.
218    */
219   tsd->done = 1;
220 #ifdef HAVE_GETADDRINFO
221   DEBUGASSERT(hints);
222   tsd->hints = *hints;
223 #else
224   (void) hints;
225 #endif
226 
227   tsd->mtx = malloc(sizeof(curl_mutex_t));
228   if(tsd->mtx == NULL)
229     goto err_exit;
230 
231   Curl_mutex_init(tsd->mtx);
232 
233   tsd->sock_error = CURL_ASYNC_SUCCESS;
234 
235   /* Copying hostname string because original can be destroyed by parent
236    * thread during gethostbyname execution.
237    */
238   tsd->hostname = strdup(hostname);
239   if(!tsd->hostname)
240     goto err_exit;
241 
242   return 1;
243 
244  err_exit:
245   /* Memory allocation failed */
246   destroy_thread_sync_data(tsd);
247   return 0;
248 }
249 
getaddrinfo_complete(struct connectdata * conn)250 static int getaddrinfo_complete(struct connectdata *conn)
251 {
252   struct thread_sync_data *tsd = conn_thread_sync_data(conn);
253   int rc;
254 
255   rc = Curl_addrinfo_callback(conn, tsd->sock_error, tsd->res);
256   /* The tsd->res structure has been copied to async.dns and perhaps the DNS
257      cache.  Set our copy to NULL so destroy_thread_sync_data doesn't free it.
258   */
259   tsd->res = NULL;
260 
261   return rc;
262 }
263 
264 
265 #ifdef HAVE_GETADDRINFO
266 
267 /*
268  * getaddrinfo_thread() resolves a name and then exits.
269  *
270  * For builds without ARES, but with ENABLE_IPV6, create a resolver thread
271  * and wait on it.
272  */
getaddrinfo_thread(void * arg)273 static unsigned int CURL_STDCALL getaddrinfo_thread(void *arg)
274 {
275   struct thread_sync_data *tsd = (struct thread_sync_data*)arg;
276   struct thread_data *td = tsd->td;
277   char service[12];
278   int rc;
279 
280   msnprintf(service, sizeof(service), "%d", tsd->port);
281 
282   rc = Curl_getaddrinfo_ex(tsd->hostname, service, &tsd->hints, &tsd->res);
283 
284   if(rc != 0) {
285     tsd->sock_error = SOCKERRNO?SOCKERRNO:rc;
286     if(tsd->sock_error == 0)
287       tsd->sock_error = RESOLVER_ENOMEM;
288   }
289   else {
290     Curl_addrinfo_set_port(tsd->res, tsd->port);
291   }
292 
293   Curl_mutex_acquire(tsd->mtx);
294   if(tsd->done) {
295     /* too late, gotta clean up the mess */
296     Curl_mutex_release(tsd->mtx);
297     destroy_thread_sync_data(tsd);
298     free(td);
299   }
300   else {
301     tsd->done = 1;
302     Curl_mutex_release(tsd->mtx);
303   }
304 
305   return 0;
306 }
307 
308 #else /* HAVE_GETADDRINFO */
309 
310 /*
311  * gethostbyname_thread() resolves a name and then exits.
312  */
gethostbyname_thread(void * arg)313 static unsigned int CURL_STDCALL gethostbyname_thread(void *arg)
314 {
315   struct thread_sync_data *tsd = (struct thread_sync_data *)arg;
316   struct thread_data *td = tsd->td;
317 
318   tsd->res = Curl_ipv4_resolve_r(tsd->hostname, tsd->port);
319 
320   if(!tsd->res) {
321     tsd->sock_error = SOCKERRNO;
322     if(tsd->sock_error == 0)
323       tsd->sock_error = RESOLVER_ENOMEM;
324   }
325 
326   Curl_mutex_acquire(tsd->mtx);
327   if(tsd->done) {
328     /* too late, gotta clean up the mess */
329     Curl_mutex_release(tsd->mtx);
330     destroy_thread_sync_data(tsd);
331     free(td);
332   }
333   else {
334     tsd->done = 1;
335     Curl_mutex_release(tsd->mtx);
336   }
337 
338   return 0;
339 }
340 
341 #endif /* HAVE_GETADDRINFO */
342 
343 /*
344  * destroy_async_data() cleans up async resolver data and thread handle.
345  */
destroy_async_data(struct Curl_async * async)346 static void destroy_async_data(struct Curl_async *async)
347 {
348   if(async->os_specific) {
349     struct thread_data *td = (struct thread_data*) async->os_specific;
350     int done;
351 
352     /*
353      * if the thread is still blocking in the resolve syscall, detach it and
354      * let the thread do the cleanup...
355      */
356     Curl_mutex_acquire(td->tsd.mtx);
357     done = td->tsd.done;
358     td->tsd.done = 1;
359     Curl_mutex_release(td->tsd.mtx);
360 
361     if(!done) {
362       Curl_thread_destroy(td->thread_hnd);
363     }
364     else {
365       if(td->thread_hnd != curl_thread_t_null)
366         Curl_thread_join(&td->thread_hnd);
367 
368       destroy_thread_sync_data(&td->tsd);
369 
370       free(async->os_specific);
371     }
372   }
373   async->os_specific = NULL;
374 
375   free(async->hostname);
376   async->hostname = NULL;
377 }
378 
379 /*
380  * init_resolve_thread() starts a new thread that performs the actual
381  * resolve. This function returns before the resolve is done.
382  *
383  * Returns FALSE in case of failure, otherwise TRUE.
384  */
init_resolve_thread(struct connectdata * conn,const char * hostname,int port,const struct addrinfo * hints)385 static bool init_resolve_thread(struct connectdata *conn,
386                                 const char *hostname, int port,
387                                 const struct addrinfo *hints)
388 {
389   struct thread_data *td = calloc(1, sizeof(struct thread_data));
390   int err = ENOMEM;
391 
392   conn->async.os_specific = (void *)td;
393   if(!td)
394     goto errno_exit;
395 
396   conn->async.port = port;
397   conn->async.done = FALSE;
398   conn->async.status = 0;
399   conn->async.dns = NULL;
400   td->thread_hnd = curl_thread_t_null;
401 
402   if(!init_thread_sync_data(td, hostname, port, hints)) {
403     conn->async.os_specific = NULL;
404     free(td);
405     goto errno_exit;
406   }
407 
408   free(conn->async.hostname);
409   conn->async.hostname = strdup(hostname);
410   if(!conn->async.hostname)
411     goto err_exit;
412 
413   /* The thread will set this to 1 when complete. */
414   td->tsd.done = 0;
415 
416 #ifdef HAVE_GETADDRINFO
417   td->thread_hnd = Curl_thread_create(getaddrinfo_thread, &td->tsd);
418 #else
419   td->thread_hnd = Curl_thread_create(gethostbyname_thread, &td->tsd);
420 #endif
421 
422   if(!td->thread_hnd) {
423     /* The thread never started, so mark it as done here for proper cleanup. */
424     td->tsd.done = 1;
425     err = errno;
426     goto err_exit;
427   }
428 
429   return TRUE;
430 
431  err_exit:
432   destroy_async_data(&conn->async);
433 
434  errno_exit:
435   errno = err;
436   return FALSE;
437 }
438 
439 /*
440  * resolver_error() calls failf() with the appropriate message after a resolve
441  * error
442  */
443 
resolver_error(struct connectdata * conn)444 static CURLcode resolver_error(struct connectdata *conn)
445 {
446   const char *host_or_proxy;
447   CURLcode result;
448 
449   if(conn->bits.httpproxy) {
450     host_or_proxy = "proxy";
451     result = CURLE_COULDNT_RESOLVE_PROXY;
452   }
453   else {
454     host_or_proxy = "host";
455     result = CURLE_COULDNT_RESOLVE_HOST;
456   }
457 
458   failf(conn->data, "Could not resolve %s: %s", host_or_proxy,
459         conn->async.hostname);
460 
461   return result;
462 }
463 
thread_wait_resolv(struct connectdata * conn,struct Curl_dns_entry ** entry,bool report)464 static CURLcode thread_wait_resolv(struct connectdata *conn,
465                                    struct Curl_dns_entry **entry,
466                                    bool report)
467 {
468   struct thread_data   *td = (struct thread_data*) conn->async.os_specific;
469   CURLcode result = CURLE_OK;
470 
471   DEBUGASSERT(conn && td);
472   DEBUGASSERT(td->thread_hnd != curl_thread_t_null);
473 
474   /* wait for the thread to resolve the name */
475   if(Curl_thread_join(&td->thread_hnd)) {
476     if(entry)
477       result = getaddrinfo_complete(conn);
478   }
479   else
480     DEBUGASSERT(0);
481 
482   conn->async.done = TRUE;
483 
484   if(entry)
485     *entry = conn->async.dns;
486 
487   if(!conn->async.dns && report)
488     /* a name was not resolved, report error */
489     result = resolver_error(conn);
490 
491   destroy_async_data(&conn->async);
492 
493   if(!conn->async.dns && report)
494     connclose(conn, "asynch resolve failed");
495 
496   return result;
497 }
498 
499 
500 /*
501  * Until we gain a way to signal the resolver threads to stop early, we must
502  * simply wait for them and ignore their results.
503  */
Curl_resolver_kill(struct connectdata * conn)504 void Curl_resolver_kill(struct connectdata *conn)
505 {
506   struct thread_data *td = (struct thread_data*) conn->async.os_specific;
507 
508   /* If we're still resolving, we must wait for the threads to fully clean up,
509      unfortunately.  Otherwise, we can simply cancel to clean up any resolver
510      data. */
511   if(td && td->thread_hnd != curl_thread_t_null)
512     (void)thread_wait_resolv(conn, NULL, FALSE);
513   else
514     Curl_resolver_cancel(conn);
515 }
516 
517 /*
518  * Curl_resolver_wait_resolv()
519  *
520  * Waits for a resolve to finish. This function should be avoided since using
521  * this risk getting the multi interface to "hang".
522  *
523  * If 'entry' is non-NULL, make it point to the resolved dns entry
524  *
525  * Returns CURLE_COULDNT_RESOLVE_HOST if the host was not resolved,
526  * CURLE_OPERATION_TIMEDOUT if a time-out occurred, or other errors.
527  *
528  * This is the version for resolves-in-a-thread.
529  */
Curl_resolver_wait_resolv(struct connectdata * conn,struct Curl_dns_entry ** entry)530 CURLcode Curl_resolver_wait_resolv(struct connectdata *conn,
531                                    struct Curl_dns_entry **entry)
532 {
533   return thread_wait_resolv(conn, entry, TRUE);
534 }
535 
536 /*
537  * Curl_resolver_is_resolved() is called repeatedly to check if a previous
538  * name resolve request has completed. It should also make sure to time-out if
539  * the operation seems to take too long.
540  */
Curl_resolver_is_resolved(struct connectdata * conn,struct Curl_dns_entry ** entry)541 CURLcode Curl_resolver_is_resolved(struct connectdata *conn,
542                                    struct Curl_dns_entry **entry)
543 {
544   struct Curl_easy *data = conn->data;
545   struct thread_data   *td = (struct thread_data*) conn->async.os_specific;
546   int done = 0;
547 
548   *entry = NULL;
549 
550   if(!td) {
551     DEBUGASSERT(td);
552     return CURLE_COULDNT_RESOLVE_HOST;
553   }
554 
555   Curl_mutex_acquire(td->tsd.mtx);
556   done = td->tsd.done;
557   Curl_mutex_release(td->tsd.mtx);
558 
559   if(done) {
560     getaddrinfo_complete(conn);
561 
562     if(!conn->async.dns) {
563       CURLcode result = resolver_error(conn);
564       destroy_async_data(&conn->async);
565       return result;
566     }
567     destroy_async_data(&conn->async);
568     *entry = conn->async.dns;
569   }
570   else {
571     /* poll for name lookup done with exponential backoff up to 250ms */
572     timediff_t elapsed = Curl_timediff(Curl_now(),
573                                        data->progress.t_startsingle);
574     if(elapsed < 0)
575       elapsed = 0;
576 
577     if(td->poll_interval == 0)
578       /* Start at 1ms poll interval */
579       td->poll_interval = 1;
580     else if(elapsed >= td->interval_end)
581       /* Back-off exponentially if last interval expired  */
582       td->poll_interval *= 2;
583 
584     if(td->poll_interval > 250)
585       td->poll_interval = 250;
586 
587     td->interval_end = elapsed + td->poll_interval;
588     Curl_expire(conn->data, td->poll_interval, EXPIRE_ASYNC_NAME);
589   }
590 
591   return CURLE_OK;
592 }
593 
Curl_resolver_getsock(struct connectdata * conn,curl_socket_t * socks,int numsocks)594 int Curl_resolver_getsock(struct connectdata *conn,
595                           curl_socket_t *socks,
596                           int numsocks)
597 {
598   time_t milli;
599   timediff_t ms;
600   struct Curl_easy *data = conn->data;
601   struct resdata *reslv = (struct resdata *)data->state.resolver;
602   (void)socks;
603   (void)numsocks;
604   ms = Curl_timediff(Curl_now(), reslv->start);
605   if(ms < 3)
606     milli = 0;
607   else if(ms <= 50)
608     milli = ms/3;
609   else if(ms <= 250)
610     milli = 50;
611   else
612     milli = 200;
613   Curl_expire(data, milli, EXPIRE_ASYNC_NAME);
614   return 0;
615 }
616 
617 #ifndef HAVE_GETADDRINFO
618 /*
619  * Curl_getaddrinfo() - for platforms without getaddrinfo
620  */
Curl_resolver_getaddrinfo(struct connectdata * conn,const char * hostname,int port,int * waitp)621 Curl_addrinfo *Curl_resolver_getaddrinfo(struct connectdata *conn,
622                                          const char *hostname,
623                                          int port,
624                                          int *waitp)
625 {
626   struct in_addr in;
627   struct Curl_easy *data = conn->data;
628   struct resdata *reslv = (struct resdata *)data->state.resolver;
629 
630   *waitp = 0; /* default to synchronous response */
631 
632   if(Curl_inet_pton(AF_INET, hostname, &in) > 0)
633     /* This is a dotted IP address 123.123.123.123-style */
634     return Curl_ip2addr(AF_INET, &in, hostname, port);
635 
636   reslv->start = Curl_now();
637 
638   /* fire up a new resolver thread! */
639   if(init_resolve_thread(conn, hostname, port, NULL)) {
640     *waitp = 1; /* expect asynchronous response */
641     return NULL;
642   }
643 
644   failf(conn->data, "getaddrinfo() thread failed\n");
645 
646   return NULL;
647 }
648 
649 #else /* !HAVE_GETADDRINFO */
650 
651 /*
652  * Curl_resolver_getaddrinfo() - for getaddrinfo
653  */
Curl_resolver_getaddrinfo(struct connectdata * conn,const char * hostname,int port,int * waitp)654 Curl_addrinfo *Curl_resolver_getaddrinfo(struct connectdata *conn,
655                                          const char *hostname,
656                                          int port,
657                                          int *waitp)
658 {
659   struct addrinfo hints;
660   char sbuf[12];
661   int pf = PF_INET;
662   struct Curl_easy *data = conn->data;
663   struct resdata *reslv = (struct resdata *)data->state.resolver;
664 
665   *waitp = 0; /* default to synchronous response */
666 
667 #ifndef USE_RESOLVE_ON_IPS
668   {
669     struct in_addr in;
670     /* First check if this is an IPv4 address string */
671     if(Curl_inet_pton(AF_INET, hostname, &in) > 0)
672       /* This is a dotted IP address 123.123.123.123-style */
673       return Curl_ip2addr(AF_INET, &in, hostname, port);
674   }
675 #ifdef CURLRES_IPV6
676   {
677     struct in6_addr in6;
678     /* check if this is an IPv6 address string */
679     if(Curl_inet_pton(AF_INET6, hostname, &in6) > 0)
680       /* This is an IPv6 address literal */
681       return Curl_ip2addr(AF_INET6, &in6, hostname, port);
682   }
683 #endif /* CURLRES_IPV6 */
684 #endif /* !USE_RESOLVE_ON_IPS */
685 
686 #ifdef CURLRES_IPV6
687   /*
688    * Check if a limited name resolve has been requested.
689    */
690   switch(conn->ip_version) {
691   case CURL_IPRESOLVE_V4:
692     pf = PF_INET;
693     break;
694   case CURL_IPRESOLVE_V6:
695     pf = PF_INET6;
696     break;
697   default:
698     pf = PF_UNSPEC;
699     break;
700   }
701 
702   if((pf != PF_INET) && !Curl_ipv6works())
703     /* The stack seems to be a non-IPv6 one */
704     pf = PF_INET;
705 #endif /* CURLRES_IPV6 */
706 
707   memset(&hints, 0, sizeof(hints));
708   hints.ai_family = pf;
709   hints.ai_socktype = conn->socktype;
710 
711   msnprintf(sbuf, sizeof(sbuf), "%d", port);
712 
713   reslv->start = Curl_now();
714   /* fire up a new resolver thread! */
715   if(init_resolve_thread(conn, hostname, port, &hints)) {
716     *waitp = 1; /* expect asynchronous response */
717     return NULL;
718   }
719 
720   failf(data, "getaddrinfo() thread failed to start\n");
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