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