1 /***************************************************************************
3 * Project ___| | | | _ \| |
5 * | (__| |_| | _ <| |___
6 * \___|\___/|_| \_\_____|
8 * Copyright (C) 1998 - 2021, 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.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
73 #include "inet_ntop.h"
74 #include "curl_threads.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"
83 struct curltime start;
87 * Curl_resolver_global_init()
88 * Called from curl_global_init() to initialize global resolver environment.
91 int Curl_resolver_global_init(void)
97 * Curl_resolver_global_cleanup()
98 * Called from curl_global_cleanup() to destroy global resolver environment.
101 void Curl_resolver_global_cleanup(void)
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
111 CURLcode Curl_resolver_init(struct Curl_easy *easy, void **resolver)
114 *resolver = calloc(1, sizeof(struct resdata));
116 return CURLE_OUT_OF_MEMORY;
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
126 void Curl_resolver_cleanup(void *resolver)
132 * Curl_resolver_duphandle()
133 * Called from curl_easy_duphandle() to duplicate resolver URL state-specific
134 * environment ('resolver' member of the UrlState structure).
136 CURLcode Curl_resolver_duphandle(struct Curl_easy *easy, void **to, void *from)
139 return Curl_resolver_init(easy, to);
142 static void destroy_async_data(struct Curl_async *);
145 * Cancel all possibly still on-going resolves for this connection.
147 void Curl_resolver_cancel(struct Curl_easy *data)
149 destroy_async_data(&data->state.async);
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);
158 /* Data for synchronization between resolver thread and its parent */
159 struct thread_sync_data {
163 char *hostname; /* hostname to resolve, Curl_async.hostname
165 #ifndef CURL_DISABLE_SOCKETPAIR
166 struct Curl_easy *data;
167 curl_socket_t sock_pair[2]; /* socket pair */
170 struct Curl_addrinfo *res;
171 #ifdef HAVE_GETADDRINFO
172 struct addrinfo hints;
174 struct thread_data *td; /* for thread-self cleanup */
178 curl_thread_t thread_hnd;
179 unsigned int poll_interval;
180 timediff_t interval_end;
181 struct thread_sync_data tsd;
184 static struct thread_sync_data *conn_thread_sync_data(struct Curl_easy *data)
186 return &(data->state.async.tdata->tsd);
189 /* Destroy resolver thread synchronization data */
191 void destroy_thread_sync_data(struct thread_sync_data *tsd)
194 Curl_mutex_destroy(tsd->mtx);
201 Curl_freeaddrinfo(tsd->res);
203 #ifndef CURL_DISABLE_SOCKETPAIR
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.
208 if(tsd->sock_pair[1] != CURL_SOCKET_BAD) {
209 sclose(tsd->sock_pair[1]);
212 memset(tsd, 0, sizeof(*tsd));
215 /* Initialize resolver thread synchronization data */
217 int init_thread_sync_data(struct thread_data *td,
218 const char *hostname,
220 const struct addrinfo *hints)
222 struct thread_sync_data *tsd = &td->tsd;
224 memset(tsd, 0, sizeof(*tsd));
228 /* Treat the request as done until the thread actually starts so any early
229 * cleanup gets done properly.
232 #ifdef HAVE_GETADDRINFO
239 tsd->mtx = malloc(sizeof(curl_mutex_t));
243 Curl_mutex_init(tsd->mtx);
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;
253 tsd->sock_error = CURL_ASYNC_SUCCESS;
255 /* Copying hostname string because original can be destroyed by parent
256 * thread during gethostbyname execution.
258 tsd->hostname = strdup(hostname);
265 /* Memory allocation failed */
266 destroy_thread_sync_data(tsd);
270 static int getaddrinfo_complete(struct Curl_easy *data)
272 struct thread_sync_data *tsd = conn_thread_sync_data(data);
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.
285 #ifdef HAVE_GETADDRINFO
288 * getaddrinfo_thread() resolves a name and then exits.
290 * For builds without ARES, but with ENABLE_IPV6, create a resolver thread
293 static unsigned int CURL_STDCALL getaddrinfo_thread(void *arg)
295 struct thread_sync_data *tsd = (struct thread_sync_data *)arg;
296 struct thread_data *td = tsd->td;
299 #ifndef CURL_DISABLE_SOCKETPAIR
303 msnprintf(service, sizeof(service), "%d", tsd->port);
305 rc = Curl_getaddrinfo_ex(tsd->hostname, service, &tsd->hints, &tsd->res);
308 tsd->sock_error = SOCKERRNO?SOCKERRNO:rc;
309 if(tsd->sock_error == 0)
310 tsd->sock_error = RESOLVER_ENOMEM;
313 Curl_addrinfo_set_port(tsd->res, tsd->port);
316 Curl_mutex_acquire(tsd->mtx);
318 /* too late, gotta clean up the mess */
319 Curl_mutex_release(tsd->mtx);
320 destroy_thread_sync_data(tsd);
324 #ifndef CURL_DISABLE_SOCKETPAIR
325 if(tsd->sock_pair[1] != CURL_SOCKET_BAD) {
326 /* DNS has been resolved, signal client task */
328 if(swrite(tsd->sock_pair[1], buf, sizeof(buf)) < 0) {
329 /* update sock_erro to errno */
330 tsd->sock_error = SOCKERRNO;
335 Curl_mutex_release(tsd->mtx);
341 #else /* HAVE_GETADDRINFO */
344 * gethostbyname_thread() resolves a name and then exits.
346 static unsigned int CURL_STDCALL gethostbyname_thread(void *arg)
348 struct thread_sync_data *tsd = (struct thread_sync_data *)arg;
349 struct thread_data *td = tsd->td;
351 tsd->res = Curl_ipv4_resolve_r(tsd->hostname, tsd->port);
354 tsd->sock_error = SOCKERRNO;
355 if(tsd->sock_error == 0)
356 tsd->sock_error = RESOLVER_ENOMEM;
359 Curl_mutex_acquire(tsd->mtx);
361 /* too late, gotta clean up the mess */
362 Curl_mutex_release(tsd->mtx);
363 destroy_thread_sync_data(tsd);
368 Curl_mutex_release(tsd->mtx);
374 #endif /* HAVE_GETADDRINFO */
377 * destroy_async_data() cleans up async resolver data and thread handle.
379 static void destroy_async_data(struct Curl_async *async)
382 struct thread_data *td = async->tdata;
384 #ifndef CURL_DISABLE_SOCKETPAIR
385 curl_socket_t sock_rd = td->tsd.sock_pair[0];
386 struct Curl_easy *data = td->tsd.data;
390 * if the thread is still blocking in the resolve syscall, detach it and
391 * let the thread do the cleanup...
393 Curl_mutex_acquire(td->tsd.mtx);
396 Curl_mutex_release(td->tsd.mtx);
399 Curl_thread_destroy(td->thread_hnd);
402 if(td->thread_hnd != curl_thread_t_null)
403 Curl_thread_join(&td->thread_hnd);
405 destroy_thread_sync_data(&td->tsd);
409 #ifndef CURL_DISABLE_SOCKETPAIR
411 * ensure CURLMOPT_SOCKETFUNCTION fires CURL_POLL_REMOVE
412 * before the FD is invalidated to avoid EBADF on EPOLL_CTL_DEL
414 Curl_multi_closed(data, sock_rd);
420 free(async->hostname);
421 async->hostname = NULL;
425 * init_resolve_thread() starts a new thread that performs the actual
426 * resolve. This function returns before the resolve is done.
428 * Returns FALSE in case of failure, otherwise TRUE.
430 static bool init_resolve_thread(struct Curl_easy *data,
431 const char *hostname, int port,
432 const struct addrinfo *hints)
434 struct thread_data *td = calloc(1, sizeof(struct thread_data));
436 struct Curl_async *asp = &data->state.async;
438 data->state.async.tdata = td;
446 td->thread_hnd = curl_thread_t_null;
448 if(!init_thread_sync_data(td, hostname, port, hints)) {
455 asp->hostname = strdup(hostname);
459 /* The thread will set this to 1 when complete. */
462 #ifdef HAVE_GETADDRINFO
463 td->thread_hnd = Curl_thread_create(getaddrinfo_thread, &td->tsd);
465 td->thread_hnd = Curl_thread_create(gethostbyname_thread, &td->tsd);
468 if(!td->thread_hnd) {
469 /* The thread never started, so mark it as done here for proper cleanup. */
478 destroy_async_data(asp);
486 * 'entry' may be NULL and then no data is returned
488 static CURLcode thread_wait_resolv(struct Curl_easy *data,
489 struct Curl_dns_entry **entry,
492 struct thread_data *td;
493 CURLcode result = CURLE_OK;
496 td = data->state.async.tdata;
498 DEBUGASSERT(td->thread_hnd != curl_thread_t_null);
500 /* wait for the thread to resolve the name */
501 if(Curl_thread_join(&td->thread_hnd)) {
503 result = getaddrinfo_complete(data);
508 data->state.async.done = TRUE;
511 *entry = data->state.async.dns;
513 if(!data->state.async.dns && report)
514 /* a name was not resolved, report error */
515 result = Curl_resolver_error(data);
517 destroy_async_data(&data->state.async);
519 if(!data->state.async.dns && report)
520 connclose(data->conn, "asynch resolve failed");
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.
530 void Curl_resolver_kill(struct Curl_easy *data)
532 struct thread_data *td = data->state.async.tdata;
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
537 if(td && td->thread_hnd != curl_thread_t_null)
538 (void)thread_wait_resolv(data, NULL, FALSE);
540 Curl_resolver_cancel(data);
544 * Curl_resolver_wait_resolv()
546 * Waits for a resolve to finish. This function should be avoided since using
547 * this risk getting the multi interface to "hang".
549 * If 'entry' is non-NULL, make it point to the resolved dns entry
551 * Returns CURLE_COULDNT_RESOLVE_HOST if the host was not resolved,
552 * CURLE_OPERATION_TIMEDOUT if a time-out occurred, or other errors.
554 * This is the version for resolves-in-a-thread.
556 CURLcode Curl_resolver_wait_resolv(struct Curl_easy *data,
557 struct Curl_dns_entry **entry)
559 return thread_wait_resolv(data, entry, TRUE);
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.
567 CURLcode Curl_resolver_is_resolved(struct Curl_easy *data,
568 struct Curl_dns_entry **entry)
570 struct thread_data *td = data->state.async.tdata;
578 return CURLE_COULDNT_RESOLVE_HOST;
581 Curl_mutex_acquire(td->tsd.mtx);
583 Curl_mutex_release(td->tsd.mtx);
586 getaddrinfo_complete(data);
588 if(!data->state.async.dns) {
589 CURLcode result = Curl_resolver_error(data);
590 destroy_async_data(&data->state.async);
593 destroy_async_data(&data->state.async);
594 *entry = data->state.async.dns;
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);
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;
611 if(td->poll_interval > 250)
612 td->poll_interval = 250;
614 td->interval_end = elapsed + td->poll_interval;
615 Curl_expire(data, td->poll_interval, EXPIRE_ASYNC_NAME);
621 int Curl_resolver_getsock(struct Curl_easy *data, curl_socket_t *socks)
626 struct resdata *reslv = (struct resdata *)data->state.async.resolver;
627 #ifndef CURL_DISABLE_SOCKETPAIR
628 struct thread_data *td = data->state.async.tdata;
633 #ifndef CURL_DISABLE_SOCKETPAIR
635 /* return read fd to client for polling the DNS resolution status */
636 socks[0] = td->tsd.sock_pair[0];
638 ret_val = GETSOCK_READSOCK(0);
642 ms = Curl_timediff(Curl_now(), reslv->start);
651 Curl_expire(data, milli, EXPIRE_ASYNC_NAME);
652 #ifndef CURL_DISABLE_SOCKETPAIR
660 #ifndef HAVE_GETADDRINFO
662 * Curl_getaddrinfo() - for platforms without getaddrinfo
664 struct Curl_addrinfo *Curl_resolver_getaddrinfo(struct Curl_easy *data,
665 const char *hostname,
669 struct resdata *reslv = (struct resdata *)data->state.async.resolver;
671 *waitp = 0; /* default to synchronous response */
673 reslv->start = Curl_now();
675 /* fire up a new resolver thread! */
676 if(init_resolve_thread(data, hostname, port, NULL)) {
677 *waitp = 1; /* expect asynchronous response */
681 failf(data, "getaddrinfo() thread failed");
686 #else /* !HAVE_GETADDRINFO */
689 * Curl_resolver_getaddrinfo() - for getaddrinfo
691 struct Curl_addrinfo *Curl_resolver_getaddrinfo(struct Curl_easy *data,
692 const char *hostname,
696 struct addrinfo hints;
698 struct resdata *reslv = (struct resdata *)data->state.async.resolver;
700 *waitp = 0; /* default to synchronous response */
703 if(Curl_ipv6works(data))
704 /* The stack seems to be IPv6-enabled */
706 #endif /* CURLRES_IPV6 */
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;
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 */
720 failf(data, "getaddrinfo() thread failed to start");
725 #endif /* !HAVE_GETADDRINFO */
727 CURLcode Curl_set_dns_servers(struct Curl_easy *data,
732 return CURLE_NOT_BUILT_IN;
736 CURLcode Curl_set_dns_interface(struct Curl_easy *data,
741 return CURLE_NOT_BUILT_IN;
744 CURLcode Curl_set_dns_local_ip4(struct Curl_easy *data,
745 const char *local_ip4)
749 return CURLE_NOT_BUILT_IN;
752 CURLcode Curl_set_dns_local_ip6(struct Curl_easy *data,
753 const char *local_ip6)
757 return CURLE_NOT_BUILT_IN;
760 #endif /* CURLRES_THREADED */