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