Annotation of embedaddon/curl/lib/asyn-thread.c, revision 1.1.1.1
1.1 misho 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: 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: time_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: if(conn->bits.httpproxy) {
498: host_or_proxy = "proxy";
499: result = CURLE_COULDNT_RESOLVE_PROXY;
500: }
501: else {
502: host_or_proxy = "host";
503: result = CURLE_COULDNT_RESOLVE_HOST;
504: }
505:
506: failf(conn->data, "Could not resolve %s: %s", host_or_proxy,
507: conn->async.hostname);
508:
509: return result;
510: }
511:
512: static CURLcode thread_wait_resolv(struct connectdata *conn,
513: struct Curl_dns_entry **entry,
514: bool report)
515: {
516: struct thread_data *td = (struct thread_data*) conn->async.os_specific;
517: CURLcode result = CURLE_OK;
518:
519: DEBUGASSERT(conn && td);
520: DEBUGASSERT(td->thread_hnd != curl_thread_t_null);
521:
522: /* wait for the thread to resolve the name */
523: if(Curl_thread_join(&td->thread_hnd)) {
524: if(entry)
525: result = getaddrinfo_complete(conn);
526: }
527: else
528: DEBUGASSERT(0);
529:
530: conn->async.done = TRUE;
531:
532: if(entry)
533: *entry = conn->async.dns;
534:
535: if(!conn->async.dns && report)
536: /* a name was not resolved, report error */
537: result = resolver_error(conn);
538:
539: destroy_async_data(&conn->async);
540:
541: if(!conn->async.dns && report)
542: connclose(conn, "asynch resolve failed");
543:
544: return result;
545: }
546:
547:
548: /*
549: * Until we gain a way to signal the resolver threads to stop early, we must
550: * simply wait for them and ignore their results.
551: */
552: void Curl_resolver_kill(struct connectdata *conn)
553: {
554: struct thread_data *td = (struct thread_data*) conn->async.os_specific;
555:
556: /* If we're still resolving, we must wait for the threads to fully clean up,
557: unfortunately. Otherwise, we can simply cancel to clean up any resolver
558: data. */
559: if(td && td->thread_hnd != curl_thread_t_null)
560: (void)thread_wait_resolv(conn, NULL, FALSE);
561: else
562: Curl_resolver_cancel(conn);
563: }
564:
565: /*
566: * Curl_resolver_wait_resolv()
567: *
568: * Waits for a resolve to finish. This function should be avoided since using
569: * this risk getting the multi interface to "hang".
570: *
571: * If 'entry' is non-NULL, make it point to the resolved dns entry
572: *
573: * Returns CURLE_COULDNT_RESOLVE_HOST if the host was not resolved,
574: * CURLE_OPERATION_TIMEDOUT if a time-out occurred, or other errors.
575: *
576: * This is the version for resolves-in-a-thread.
577: */
578: CURLcode Curl_resolver_wait_resolv(struct connectdata *conn,
579: struct Curl_dns_entry **entry)
580: {
581: return thread_wait_resolv(conn, entry, TRUE);
582: }
583:
584: /*
585: * Curl_resolver_is_resolved() is called repeatedly to check if a previous
586: * name resolve request has completed. It should also make sure to time-out if
587: * the operation seems to take too long.
588: */
589: CURLcode Curl_resolver_is_resolved(struct connectdata *conn,
590: struct Curl_dns_entry **entry)
591: {
592: struct Curl_easy *data = conn->data;
593: struct thread_data *td = (struct thread_data*) conn->async.os_specific;
594: int done = 0;
595:
596: *entry = NULL;
597:
598: if(!td) {
599: DEBUGASSERT(td);
600: return CURLE_COULDNT_RESOLVE_HOST;
601: }
602:
603: Curl_mutex_acquire(td->tsd.mtx);
604: done = td->tsd.done;
605: Curl_mutex_release(td->tsd.mtx);
606:
607: if(done) {
608: getaddrinfo_complete(conn);
609:
610: if(!conn->async.dns) {
611: CURLcode result = resolver_error(conn);
612: destroy_async_data(&conn->async);
613: return result;
614: }
615: destroy_async_data(&conn->async);
616: *entry = conn->async.dns;
617: }
618: else {
619: /* poll for name lookup done with exponential backoff up to 250ms */
620: /* should be fine even if this converts to 32 bit */
621: time_t elapsed = (time_t)Curl_timediff(Curl_now(),
622: data->progress.t_startsingle);
623: if(elapsed < 0)
624: elapsed = 0;
625:
626: if(td->poll_interval == 0)
627: /* Start at 1ms poll interval */
628: td->poll_interval = 1;
629: else if(elapsed >= td->interval_end)
630: /* Back-off exponentially if last interval expired */
631: td->poll_interval *= 2;
632:
633: if(td->poll_interval > 250)
634: td->poll_interval = 250;
635:
636: td->interval_end = elapsed + td->poll_interval;
637: Curl_expire(conn->data, td->poll_interval, EXPIRE_ASYNC_NAME);
638: }
639:
640: return CURLE_OK;
641: }
642:
643: int Curl_resolver_getsock(struct connectdata *conn,
644: curl_socket_t *socks)
645: {
646: int ret_val = 0;
647: time_t milli;
648: timediff_t ms;
649: struct Curl_easy *data = conn->data;
650: struct resdata *reslv = (struct resdata *)data->state.resolver;
651: #ifdef USE_SOCKETPAIR
652: struct thread_data *td = (struct thread_data*)conn->async.os_specific;
653: #else
654: (void)socks;
655: #endif
656:
657: #ifdef USE_SOCKETPAIR
658: if(td) {
659: /* return read fd to client for polling the DNS resolution status */
660: socks[0] = td->tsd.sock_pair[0];
661: DEBUGASSERT(td->tsd.conn == conn || !td->tsd.conn);
662: td->tsd.conn = conn;
663: ret_val = GETSOCK_READSOCK(0);
664: }
665: else {
666: #endif
667: ms = Curl_timediff(Curl_now(), reslv->start);
668: if(ms < 3)
669: milli = 0;
670: else if(ms <= 50)
671: milli = (time_t)ms/3;
672: else if(ms <= 250)
673: milli = 50;
674: else
675: milli = 200;
676: Curl_expire(data, milli, EXPIRE_ASYNC_NAME);
677: #ifdef USE_SOCKETPAIR
678: }
679: #endif
680:
681:
682: return ret_val;
683: }
684:
685: #ifndef HAVE_GETADDRINFO
686: /*
687: * Curl_getaddrinfo() - for platforms without getaddrinfo
688: */
689: Curl_addrinfo *Curl_resolver_getaddrinfo(struct connectdata *conn,
690: const char *hostname,
691: int port,
692: int *waitp)
693: {
694: struct Curl_easy *data = conn->data;
695: struct resdata *reslv = (struct resdata *)data->state.resolver;
696:
697: *waitp = 0; /* default to synchronous response */
698:
699: reslv->start = Curl_now();
700:
701: /* fire up a new resolver thread! */
702: if(init_resolve_thread(conn, hostname, port, NULL)) {
703: *waitp = 1; /* expect asynchronous response */
704: return NULL;
705: }
706:
707: failf(conn->data, "getaddrinfo() thread failed\n");
708:
709: return NULL;
710: }
711:
712: #else /* !HAVE_GETADDRINFO */
713:
714: /*
715: * Curl_resolver_getaddrinfo() - for getaddrinfo
716: */
717: Curl_addrinfo *Curl_resolver_getaddrinfo(struct connectdata *conn,
718: const char *hostname,
719: int port,
720: int *waitp)
721: {
722: struct addrinfo hints;
723: int pf = PF_INET;
724: struct Curl_easy *data = conn->data;
725: struct resdata *reslv = (struct resdata *)data->state.resolver;
726:
727: *waitp = 0; /* default to synchronous response */
728:
729: #ifdef CURLRES_IPV6
730: /*
731: * Check if a limited name resolve has been requested.
732: */
733: switch(conn->ip_version) {
734: case CURL_IPRESOLVE_V4:
735: pf = PF_INET;
736: break;
737: case CURL_IPRESOLVE_V6:
738: pf = PF_INET6;
739: break;
740: default:
741: pf = PF_UNSPEC;
742: break;
743: }
744:
745: if((pf != PF_INET) && !Curl_ipv6works(conn))
746: /* The stack seems to be a non-IPv6 one */
747: pf = PF_INET;
748: #endif /* CURLRES_IPV6 */
749:
750: memset(&hints, 0, sizeof(hints));
751: hints.ai_family = pf;
752: hints.ai_socktype = (conn->transport == TRNSPRT_TCP)?
753: SOCK_STREAM : SOCK_DGRAM;
754:
755: reslv->start = Curl_now();
756: /* fire up a new resolver thread! */
757: if(init_resolve_thread(conn, hostname, port, &hints)) {
758: *waitp = 1; /* expect asynchronous response */
759: return NULL;
760: }
761:
762: failf(data, "getaddrinfo() thread failed to start\n");
763: return NULL;
764:
765: }
766:
767: #endif /* !HAVE_GETADDRINFO */
768:
769: CURLcode Curl_set_dns_servers(struct Curl_easy *data,
770: char *servers)
771: {
772: (void)data;
773: (void)servers;
774: return CURLE_NOT_BUILT_IN;
775:
776: }
777:
778: CURLcode Curl_set_dns_interface(struct Curl_easy *data,
779: const char *interf)
780: {
781: (void)data;
782: (void)interf;
783: return CURLE_NOT_BUILT_IN;
784: }
785:
786: CURLcode Curl_set_dns_local_ip4(struct Curl_easy *data,
787: const char *local_ip4)
788: {
789: (void)data;
790: (void)local_ip4;
791: return CURLE_NOT_BUILT_IN;
792: }
793:
794: CURLcode Curl_set_dns_local_ip6(struct Curl_easy *data,
795: const char *local_ip6)
796: {
797: (void)data;
798: (void)local_ip6;
799: return CURLE_NOT_BUILT_IN;
800: }
801:
802: #endif /* CURLRES_THREADED */
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>