1 /***************************************************************************
3 * Project ___| | | | _ \| |
5 * | (__| |_| | _ <| |___
6 * \___|\___/|_| \_\_____|
8 * Copyright (C) 1998 - 2020, Daniel Stenberg, <daniel@haxx.se>, et al.
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.
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.
18 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
19 * KIND, either express or implied.
21 ***************************************************************************/
23 #include "curl_setup.h"
24 #include "socketpair.h"
26 /***********************************************************************
27 * Only for threaded name resolves builds
28 **********************************************************************/
29 #ifdef CURLRES_THREADED
31 #ifdef HAVE_NETINET_IN_H
32 #include <netinet/in.h>
37 #ifdef HAVE_ARPA_INET_H
38 #include <arpa/inet.h>
45 #if defined(USE_THREADS_POSIX)
46 # ifdef HAVE_PTHREAD_H
49 #elif defined(USE_THREADS_WIN32)
50 # ifdef HAVE_PROCESS_H
55 #if (defined(NETWARE) && defined(__NOVELL_LIBC__))
57 #define in_addr_t unsigned long
60 #ifdef HAVE_GETADDRINFO
61 # define RESOLVER_ENOMEM EAI_MEMORY
63 # define RESOLVER_ENOMEM ENOMEM
74 #include "inet_ntop.h"
75 #include "curl_threads.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"
84 struct curltime start;
88 * Curl_resolver_global_init()
89 * Called from curl_global_init() to initialize global resolver environment.
92 int Curl_resolver_global_init(void)
98 * Curl_resolver_global_cleanup()
99 * Called from curl_global_cleanup() to destroy global resolver environment.
102 void Curl_resolver_global_cleanup(void)
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
112 CURLcode Curl_resolver_init(struct Curl_easy *easy, void **resolver)
115 *resolver = calloc(1, sizeof(struct resdata));
117 return CURLE_OUT_OF_MEMORY;
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
127 void Curl_resolver_cleanup(void *resolver)
133 * Curl_resolver_duphandle()
134 * Called from curl_easy_duphandle() to duplicate resolver URL state-specific
135 * environment ('resolver' member of the UrlState structure).
137 CURLcode Curl_resolver_duphandle(struct Curl_easy *easy, void **to, void *from)
140 return Curl_resolver_init(easy, to);
143 static void destroy_async_data(struct Curl_async *);
146 * Cancel all possibly still on-going resolves for this connection.
148 void Curl_resolver_cancel(struct connectdata *conn)
150 destroy_async_data(&conn->async);
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);
159 /* Data for synchronization between resolver thread and its parent */
160 struct thread_sync_data {
164 char *hostname; /* hostname to resolve, Curl_async.hostname
167 #ifdef USE_SOCKETPAIR
168 struct connectdata *conn;
169 curl_socket_t sock_pair[2]; /* socket pair */
172 struct Curl_addrinfo *res;
173 #ifdef HAVE_GETADDRINFO
174 struct addrinfo hints;
176 struct thread_data *td; /* for thread-self cleanup */
180 curl_thread_t thread_hnd;
181 unsigned int poll_interval;
182 timediff_t interval_end;
183 struct thread_sync_data tsd;
186 static struct thread_sync_data *conn_thread_sync_data(struct connectdata *conn)
188 return &(((struct thread_data *)conn->async.os_specific)->tsd);
191 /* Destroy resolver thread synchronization data */
193 void destroy_thread_sync_data(struct thread_sync_data *tsd)
196 Curl_mutex_destroy(tsd->mtx);
203 Curl_freeaddrinfo(tsd->res);
205 #ifdef USE_SOCKETPAIR
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.
210 if(tsd->sock_pair[1] != CURL_SOCKET_BAD) {
211 sclose(tsd->sock_pair[1]);
214 memset(tsd, 0, sizeof(*tsd));
217 /* Initialize resolver thread synchronization data */
219 int init_thread_sync_data(struct thread_data *td,
220 const char *hostname,
222 const struct addrinfo *hints)
224 struct thread_sync_data *tsd = &td->tsd;
226 memset(tsd, 0, sizeof(*tsd));
230 /* Treat the request as done until the thread actually starts so any early
231 * cleanup gets done properly.
234 #ifdef HAVE_GETADDRINFO
241 tsd->mtx = malloc(sizeof(curl_mutex_t));
245 Curl_mutex_init(tsd->mtx);
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;
255 tsd->sock_error = CURL_ASYNC_SUCCESS;
257 /* Copying hostname string because original can be destroyed by parent
258 * thread during gethostbyname execution.
260 tsd->hostname = strdup(hostname);
267 /* Memory allocation failed */
268 destroy_thread_sync_data(tsd);
272 static int getaddrinfo_complete(struct connectdata *conn)
274 struct thread_sync_data *tsd = conn_thread_sync_data(conn);
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.
287 #ifdef HAVE_GETADDRINFO
290 * getaddrinfo_thread() resolves a name and then exits.
292 * For builds without ARES, but with ENABLE_IPV6, create a resolver thread
295 static unsigned int CURL_STDCALL getaddrinfo_thread(void *arg)
297 struct thread_sync_data *tsd = (struct thread_sync_data*)arg;
298 struct thread_data *td = tsd->td;
301 #ifdef USE_SOCKETPAIR
305 msnprintf(service, sizeof(service), "%d", tsd->port);
307 rc = Curl_getaddrinfo_ex(tsd->hostname, service, &tsd->hints, &tsd->res);
310 tsd->sock_error = SOCKERRNO?SOCKERRNO:rc;
311 if(tsd->sock_error == 0)
312 tsd->sock_error = RESOLVER_ENOMEM;
315 Curl_addrinfo_set_port(tsd->res, tsd->port);
318 Curl_mutex_acquire(tsd->mtx);
320 /* too late, gotta clean up the mess */
321 Curl_mutex_release(tsd->mtx);
322 destroy_thread_sync_data(tsd);
326 #ifdef USE_SOCKETPAIR
327 if(tsd->sock_pair[1] != CURL_SOCKET_BAD) {
328 /* DNS has been resolved, signal client task */
330 if(swrite(tsd->sock_pair[1], buf, sizeof(buf)) < 0) {
331 /* update sock_erro to errno */
332 tsd->sock_error = SOCKERRNO;
337 Curl_mutex_release(tsd->mtx);
343 #else /* HAVE_GETADDRINFO */
346 * gethostbyname_thread() resolves a name and then exits.
348 static unsigned int CURL_STDCALL gethostbyname_thread(void *arg)
350 struct thread_sync_data *tsd = (struct thread_sync_data *)arg;
351 struct thread_data *td = tsd->td;
353 tsd->res = Curl_ipv4_resolve_r(tsd->hostname, tsd->port);
356 tsd->sock_error = SOCKERRNO;
357 if(tsd->sock_error == 0)
358 tsd->sock_error = RESOLVER_ENOMEM;
361 Curl_mutex_acquire(tsd->mtx);
363 /* too late, gotta clean up the mess */
364 Curl_mutex_release(tsd->mtx);
365 destroy_thread_sync_data(tsd);
370 Curl_mutex_release(tsd->mtx);
376 #endif /* HAVE_GETADDRINFO */
379 * destroy_async_data() cleans up async resolver data and thread handle.
381 static void destroy_async_data(struct Curl_async *async)
383 if(async->os_specific) {
384 struct thread_data *td = (struct thread_data*) async->os_specific;
386 #ifdef USE_SOCKETPAIR
387 curl_socket_t sock_rd = td->tsd.sock_pair[0];
388 struct connectdata *conn = td->tsd.conn;
392 * if the thread is still blocking in the resolve syscall, detach it and
393 * let the thread do the cleanup...
395 Curl_mutex_acquire(td->tsd.mtx);
398 Curl_mutex_release(td->tsd.mtx);
401 Curl_thread_destroy(td->thread_hnd);
404 if(td->thread_hnd != curl_thread_t_null)
405 Curl_thread_join(&td->thread_hnd);
407 destroy_thread_sync_data(&td->tsd);
409 free(async->os_specific);
411 #ifdef USE_SOCKETPAIR
413 * ensure CURLMOPT_SOCKETFUNCTION fires CURL_POLL_REMOVE
414 * before the FD is invalidated to avoid EBADF on EPOLL_CTL_DEL
417 Curl_multi_closed(conn->data, sock_rd);
421 async->os_specific = NULL;
423 free(async->hostname);
424 async->hostname = NULL;
428 * init_resolve_thread() starts a new thread that performs the actual
429 * resolve. This function returns before the resolve is done.
431 * Returns FALSE in case of failure, otherwise TRUE.
433 static bool init_resolve_thread(struct connectdata *conn,
434 const char *hostname, int port,
435 const struct addrinfo *hints)
437 struct thread_data *td = calloc(1, sizeof(struct thread_data));
440 conn->async.os_specific = (void *)td;
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;
450 if(!init_thread_sync_data(td, hostname, port, hints)) {
451 conn->async.os_specific = NULL;
456 free(conn->async.hostname);
457 conn->async.hostname = strdup(hostname);
458 if(!conn->async.hostname)
461 /* The thread will set this to 1 when complete. */
464 #ifdef HAVE_GETADDRINFO
465 td->thread_hnd = Curl_thread_create(getaddrinfo_thread, &td->tsd);
467 td->thread_hnd = Curl_thread_create(gethostbyname_thread, &td->tsd);
470 if(!td->thread_hnd) {
471 /* The thread never started, so mark it as done here for proper cleanup. */
480 destroy_async_data(&conn->async);
488 * resolver_error() calls failf() with the appropriate message after a resolve
492 static CURLcode resolver_error(struct connectdata *conn)
494 const char *host_or_proxy;
497 #ifndef CURL_DISABLE_PROXY
498 if(conn->bits.httpproxy) {
499 host_or_proxy = "proxy";
500 result = CURLE_COULDNT_RESOLVE_PROXY;
505 host_or_proxy = "host";
506 result = CURLE_COULDNT_RESOLVE_HOST;
509 failf(conn->data, "Could not resolve %s: %s", host_or_proxy,
510 conn->async.hostname);
516 * 'entry' may be NULL and then no data is returned
518 static CURLcode thread_wait_resolv(struct connectdata *conn,
519 struct Curl_dns_entry **entry,
522 struct thread_data *td = (struct thread_data*) conn->async.os_specific;
523 CURLcode result = CURLE_OK;
525 DEBUGASSERT(conn && td);
526 DEBUGASSERT(td->thread_hnd != curl_thread_t_null);
528 /* wait for the thread to resolve the name */
529 if(Curl_thread_join(&td->thread_hnd)) {
531 result = getaddrinfo_complete(conn);
536 conn->async.done = TRUE;
539 *entry = conn->async.dns;
541 if(!conn->async.dns && report)
542 /* a name was not resolved, report error */
543 result = resolver_error(conn);
545 destroy_async_data(&conn->async);
547 if(!conn->async.dns && report)
548 connclose(conn, "asynch resolve failed");
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.
558 void Curl_resolver_kill(struct connectdata *conn)
560 struct thread_data *td = (struct thread_data*) conn->async.os_specific;
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
565 if(td && td->thread_hnd != curl_thread_t_null)
566 (void)thread_wait_resolv(conn, NULL, FALSE);
568 Curl_resolver_cancel(conn);
572 * Curl_resolver_wait_resolv()
574 * Waits for a resolve to finish. This function should be avoided since using
575 * this risk getting the multi interface to "hang".
577 * If 'entry' is non-NULL, make it point to the resolved dns entry
579 * Returns CURLE_COULDNT_RESOLVE_HOST if the host was not resolved,
580 * CURLE_OPERATION_TIMEDOUT if a time-out occurred, or other errors.
582 * This is the version for resolves-in-a-thread.
584 CURLcode Curl_resolver_wait_resolv(struct connectdata *conn,
585 struct Curl_dns_entry **entry)
587 return thread_wait_resolv(conn, entry, TRUE);
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.
595 CURLcode Curl_resolver_is_resolved(struct connectdata *conn,
596 struct Curl_dns_entry **entry)
598 struct Curl_easy *data = conn->data;
599 struct thread_data *td = (struct thread_data*) conn->async.os_specific;
607 return CURLE_COULDNT_RESOLVE_HOST;
610 Curl_mutex_acquire(td->tsd.mtx);
612 Curl_mutex_release(td->tsd.mtx);
615 getaddrinfo_complete(conn);
617 if(!conn->async.dns) {
618 CURLcode result = resolver_error(conn);
619 destroy_async_data(&conn->async);
622 destroy_async_data(&conn->async);
623 *entry = conn->async.dns;
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);
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;
640 if(td->poll_interval > 250)
641 td->poll_interval = 250;
643 td->interval_end = elapsed + td->poll_interval;
644 Curl_expire(conn->data, td->poll_interval, EXPIRE_ASYNC_NAME);
650 int Curl_resolver_getsock(struct connectdata *conn,
651 curl_socket_t *socks)
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;
664 #ifdef USE_SOCKETPAIR
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);
670 ret_val = GETSOCK_READSOCK(0);
674 ms = Curl_timediff(Curl_now(), reslv->start);
683 Curl_expire(data, milli, EXPIRE_ASYNC_NAME);
684 #ifdef USE_SOCKETPAIR
692 #ifndef HAVE_GETADDRINFO
694 * Curl_getaddrinfo() - for platforms without getaddrinfo
696 struct Curl_addrinfo *Curl_resolver_getaddrinfo(struct connectdata *conn,
697 const char *hostname,
701 struct Curl_easy *data = conn->data;
702 struct resdata *reslv = (struct resdata *)data->state.resolver;
704 *waitp = 0; /* default to synchronous response */
706 reslv->start = Curl_now();
708 /* fire up a new resolver thread! */
709 if(init_resolve_thread(conn, hostname, port, NULL)) {
710 *waitp = 1; /* expect asynchronous response */
714 failf(conn->data, "getaddrinfo() thread failed\n");
719 #else /* !HAVE_GETADDRINFO */
722 * Curl_resolver_getaddrinfo() - for getaddrinfo
724 struct Curl_addrinfo *Curl_resolver_getaddrinfo(struct connectdata *conn,
725 const char *hostname,
729 struct addrinfo hints;
731 struct Curl_easy *data = conn->data;
732 struct resdata *reslv = (struct resdata *)data->state.resolver;
734 *waitp = 0; /* default to synchronous response */
738 * Check if a limited name resolve has been requested.
740 switch(conn->ip_version) {
741 case CURL_IPRESOLVE_V4:
744 case CURL_IPRESOLVE_V6:
752 if((pf != PF_INET) && !Curl_ipv6works(conn))
753 /* The stack seems to be a non-IPv6 one */
755 #endif /* CURLRES_IPV6 */
757 memset(&hints, 0, sizeof(hints));
758 hints.ai_family = pf;
759 hints.ai_socktype = (conn->transport == TRNSPRT_TCP)?
760 SOCK_STREAM : SOCK_DGRAM;
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 */
769 failf(data, "getaddrinfo() thread failed to start\n");
774 #endif /* !HAVE_GETADDRINFO */
776 CURLcode Curl_set_dns_servers(struct Curl_easy *data,
781 return CURLE_NOT_BUILT_IN;
785 CURLcode Curl_set_dns_interface(struct Curl_easy *data,
790 return CURLE_NOT_BUILT_IN;
793 CURLcode Curl_set_dns_local_ip4(struct Curl_easy *data,
794 const char *local_ip4)
798 return CURLE_NOT_BUILT_IN;
801 CURLcode Curl_set_dns_local_ip6(struct Curl_easy *data,
802 const char *local_ip6)
806 return CURLE_NOT_BUILT_IN;
809 #endif /* CURLRES_THREADED */