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