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