1/***************************************************************************
2 * _ _ ____ _
3 * Project ___| | | | _ \| |
4 * / __| | | | |_) | |
5 * | (__| |_| | _ <| |___
6 * \___|\___/|_| \_\_____|
7 *
8 * Copyright (C) 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 * SPDX-License-Identifier: curl
22 *
23 ***************************************************************************/
24
25#include "curl_setup.h"
26#include "socketpair.h"
27
28/***********************************************************************
29 * Only for threaded name resolves builds
30 **********************************************************************/
31#ifdef CURLRES_THREADED
32
33#ifdef HAVE_NETINET_IN_H
34#include <netinet/in.h>
35#endif
36#ifdef HAVE_NETDB_H
37#include <netdb.h>
38#endif
39#ifdef HAVE_ARPA_INET_H
40#include <arpa/inet.h>
41#endif
42#ifdef __VMS
43#include <in.h>
44#include <inet.h>
45#endif
46
47#if defined(USE_THREADS_POSIX) && defined(HAVE_PTHREAD_H)
48# include <pthread.h>
49#endif
50
51#ifdef HAVE_GETADDRINFO
52# define RESOLVER_ENOMEM EAI_MEMORY
53#else
54# define RESOLVER_ENOMEM ENOMEM
55#endif
56
57#include "urldata.h"
58#include "sendf.h"
59#include "hostip.h"
60#include "hash.h"
61#include "share.h"
62#include "url.h"
63#include "multiif.h"
64#include "inet_ntop.h"
65#include "curl_threads.h"
66#include "connect.h"
67/* The last 3 #include files should be in this order */
68#include "curl_printf.h"
69#include "curl_memory.h"
70#include "memdebug.h"
71
72struct resdata {
73 struct curltime start;
74};
75
76/*
77 * Curl_resolver_global_init()
78 * Called from curl_global_init() to initialize global resolver environment.
79 * Does nothing here.
80 */
81int Curl_resolver_global_init(void)
82{
83 return CURLE_OK;
84}
85
86/*
87 * Curl_resolver_global_cleanup()
88 * Called from curl_global_cleanup() to destroy global resolver environment.
89 * Does nothing here.
90 */
91void Curl_resolver_global_cleanup(void)
92{
93}
94
95/*
96 * Curl_resolver_init()
97 * Called from curl_easy_init() -> Curl_open() to initialize resolver
98 * URL-state specific environment ('resolver' member of the UrlState
99 * structure).
100 */
101CURLcode Curl_resolver_init(struct Curl_easy *easy, void **resolver)
102{
103 (void)easy;
104 *resolver = calloc(1, sizeof(struct resdata));
105 if(!*resolver)
106 return CURLE_OUT_OF_MEMORY;
107 return CURLE_OK;
108}
109
110/*
111 * Curl_resolver_cleanup()
112 * Called from curl_easy_cleanup() -> Curl_close() to cleanup resolver
113 * URL-state specific environment ('resolver' member of the UrlState
114 * structure).
115 */
116void Curl_resolver_cleanup(void *resolver)
117{
118 free(resolver);
119}
120
121/*
122 * Curl_resolver_duphandle()
123 * Called from curl_easy_duphandle() to duplicate resolver URL state-specific
124 * environment ('resolver' member of the UrlState structure).
125 */
126CURLcode Curl_resolver_duphandle(struct Curl_easy *easy, void **to, void *from)
127{
128 (void)from;
129 return Curl_resolver_init(easy, resolver: to);
130}
131
132static void destroy_async_data(struct Curl_async *);
133
134/*
135 * Cancel all possibly still on-going resolves for this connection.
136 */
137void Curl_resolver_cancel(struct Curl_easy *data)
138{
139 destroy_async_data(&data->state.async);
140}
141
142/* This function is used to init a threaded resolve */
143static bool init_resolve_thread(struct Curl_easy *data,
144 const char *hostname, int port,
145 const struct addrinfo *hints);
146
147
148/* Data for synchronization between resolver thread and its parent */
149struct thread_sync_data {
150 curl_mutex_t *mtx;
151 int done;
152 int port;
153 char *hostname; /* hostname to resolve, Curl_async.hostname
154 duplicate */
155#ifndef CURL_DISABLE_SOCKETPAIR
156 struct Curl_easy *data;
157 curl_socket_t sock_pair[2]; /* socket pair */
158#endif
159 int sock_error;
160 struct Curl_addrinfo *res;
161#ifdef HAVE_GETADDRINFO
162 struct addrinfo hints;
163#endif
164 struct thread_data *td; /* for thread-self cleanup */
165};
166
167struct thread_data {
168 curl_thread_t thread_hnd;
169 unsigned int poll_interval;
170 timediff_t interval_end;
171 struct thread_sync_data tsd;
172};
173
174static struct thread_sync_data *conn_thread_sync_data(struct Curl_easy *data)
175{
176 return &(data->state.async.tdata->tsd);
177}
178
179/* Destroy resolver thread synchronization data */
180static
181void destroy_thread_sync_data(struct thread_sync_data *tsd)
182{
183 if(tsd->mtx) {
184 Curl_mutex_destroy(tsd->mtx);
185 free(tsd->mtx);
186 }
187
188 free(tsd->hostname);
189
190 if(tsd->res)
191 Curl_freeaddrinfo(cahead: tsd->res);
192
193#ifndef CURL_DISABLE_SOCKETPAIR
194 /*
195 * close one end of the socket pair (may be done in resolver thread);
196 * the other end (for reading) is always closed in the parent thread.
197 */
198 if(tsd->sock_pair[1] != CURL_SOCKET_BAD) {
199 sclose(tsd->sock_pair[1]);
200 }
201#endif
202 memset(s: tsd, c: 0, n: sizeof(*tsd));
203}
204
205/* Initialize resolver thread synchronization data */
206static
207int init_thread_sync_data(struct thread_data *td,
208 const char *hostname,
209 int port,
210 const struct addrinfo *hints)
211{
212 struct thread_sync_data *tsd = &td->tsd;
213
214 memset(s: tsd, c: 0, n: sizeof(*tsd));
215
216 tsd->td = td;
217 tsd->port = port;
218 /* Treat the request as done until the thread actually starts so any early
219 * cleanup gets done properly.
220 */
221 tsd->done = 1;
222#ifdef HAVE_GETADDRINFO
223 DEBUGASSERT(hints);
224 tsd->hints = *hints;
225#else
226 (void) hints;
227#endif
228
229 tsd->mtx = malloc(sizeof(curl_mutex_t));
230 if(!tsd->mtx)
231 goto err_exit;
232
233 Curl_mutex_init(tsd->mtx);
234
235#ifndef CURL_DISABLE_SOCKETPAIR
236 /* create socket pair, avoid AF_LOCAL since it doesn't build on Solaris */
237 if(Curl_socketpair(AF_UNIX, SOCK_STREAM, 0, &tsd->sock_pair[0]) < 0) {
238 tsd->sock_pair[0] = CURL_SOCKET_BAD;
239 tsd->sock_pair[1] = CURL_SOCKET_BAD;
240 goto err_exit;
241 }
242#endif
243 tsd->sock_error = CURL_ASYNC_SUCCESS;
244
245 /* Copying hostname string because original can be destroyed by parent
246 * thread during gethostbyname execution.
247 */
248 tsd->hostname = strdup(hostname);
249 if(!tsd->hostname)
250 goto err_exit;
251
252 return 1;
253
254err_exit:
255#ifndef CURL_DISABLE_SOCKETPAIR
256 if(tsd->sock_pair[0] != CURL_SOCKET_BAD) {
257 sclose(tsd->sock_pair[0]);
258 tsd->sock_pair[0] = CURL_SOCKET_BAD;
259 }
260#endif
261 destroy_thread_sync_data(tsd);
262 return 0;
263}
264
265static CURLcode getaddrinfo_complete(struct Curl_easy *data)
266{
267 struct thread_sync_data *tsd = conn_thread_sync_data(data);
268 CURLcode result;
269
270 result = Curl_addrinfo_callback(data, status: tsd->sock_error, ai: tsd->res);
271 /* The tsd->res structure has been copied to async.dns and perhaps the DNS
272 cache. Set our copy to NULL so destroy_thread_sync_data doesn't free it.
273 */
274 tsd->res = NULL;
275
276 return result;
277}
278
279
280#ifdef HAVE_GETADDRINFO
281
282/*
283 * getaddrinfo_thread() resolves a name and then exits.
284 *
285 * For builds without ARES, but with ENABLE_IPV6, create a resolver thread
286 * and wait on it.
287 */
288static unsigned int CURL_STDCALL getaddrinfo_thread(void *arg)
289{
290 struct thread_sync_data *tsd = (struct thread_sync_data *)arg;
291 struct thread_data *td = tsd->td;
292 char service[12];
293 int rc;
294#ifndef CURL_DISABLE_SOCKETPAIR
295 char buf[1];
296#endif
297
298 msnprintf(buffer: service, maxlength: sizeof(service), format: "%d", tsd->port);
299
300 rc = Curl_getaddrinfo_ex(nodename: tsd->hostname, servname: service, hints: &tsd->hints, result: &tsd->res);
301
302 if(rc) {
303 tsd->sock_error = SOCKERRNO?SOCKERRNO:rc;
304 if(tsd->sock_error == 0)
305 tsd->sock_error = RESOLVER_ENOMEM;
306 }
307 else {
308 Curl_addrinfo_set_port(tsd->res, tsd->port);
309 }
310
311 Curl_mutex_acquire(tsd->mtx);
312 if(tsd->done) {
313 /* too late, gotta clean up the mess */
314 Curl_mutex_release(tsd->mtx);
315 destroy_thread_sync_data(tsd);
316 free(td);
317 }
318 else {
319#ifndef CURL_DISABLE_SOCKETPAIR
320 if(tsd->sock_pair[1] != CURL_SOCKET_BAD) {
321 /* DNS has been resolved, signal client task */
322 buf[0] = 1;
323 if(swrite(tsd->sock_pair[1], buf, sizeof(buf)) < 0) {
324 /* update sock_erro to errno */
325 tsd->sock_error = SOCKERRNO;
326 }
327 }
328#endif
329 tsd->done = 1;
330 Curl_mutex_release(tsd->mtx);
331 }
332
333 return 0;
334}
335
336#else /* HAVE_GETADDRINFO */
337
338/*
339 * gethostbyname_thread() resolves a name and then exits.
340 */
341static unsigned int CURL_STDCALL gethostbyname_thread(void *arg)
342{
343 struct thread_sync_data *tsd = (struct thread_sync_data *)arg;
344 struct thread_data *td = tsd->td;
345
346 tsd->res = Curl_ipv4_resolve_r(tsd->hostname, tsd->port);
347
348 if(!tsd->res) {
349 tsd->sock_error = SOCKERRNO;
350 if(tsd->sock_error == 0)
351 tsd->sock_error = RESOLVER_ENOMEM;
352 }
353
354 Curl_mutex_acquire(tsd->mtx);
355 if(tsd->done) {
356 /* too late, gotta clean up the mess */
357 Curl_mutex_release(tsd->mtx);
358 destroy_thread_sync_data(tsd);
359 free(td);
360 }
361 else {
362 tsd->done = 1;
363 Curl_mutex_release(tsd->mtx);
364 }
365
366 return 0;
367}
368
369#endif /* HAVE_GETADDRINFO */
370
371/*
372 * destroy_async_data() cleans up async resolver data and thread handle.
373 */
374static void destroy_async_data(struct Curl_async *async)
375{
376 if(async->tdata) {
377 struct thread_data *td = async->tdata;
378 int done;
379#ifndef CURL_DISABLE_SOCKETPAIR
380 curl_socket_t sock_rd = td->tsd.sock_pair[0];
381 struct Curl_easy *data = td->tsd.data;
382#endif
383
384 /*
385 * if the thread is still blocking in the resolve syscall, detach it and
386 * let the thread do the cleanup...
387 */
388 Curl_mutex_acquire(td->tsd.mtx);
389 done = td->tsd.done;
390 td->tsd.done = 1;
391 Curl_mutex_release(td->tsd.mtx);
392
393 if(!done) {
394 Curl_thread_destroy(hnd: td->thread_hnd);
395 }
396 else {
397 if(td->thread_hnd != curl_thread_t_null)
398 Curl_thread_join(hnd: &td->thread_hnd);
399
400 destroy_thread_sync_data(tsd: &td->tsd);
401
402 free(async->tdata);
403 }
404#ifndef CURL_DISABLE_SOCKETPAIR
405 /*
406 * ensure CURLMOPT_SOCKETFUNCTION fires CURL_POLL_REMOVE
407 * before the FD is invalidated to avoid EBADF on EPOLL_CTL_DEL
408 */
409 Curl_multi_closed(data, s: sock_rd);
410 sclose(sock_rd);
411#endif
412 }
413 async->tdata = NULL;
414
415 free(async->hostname);
416 async->hostname = NULL;
417}
418
419/*
420 * init_resolve_thread() starts a new thread that performs the actual
421 * resolve. This function returns before the resolve is done.
422 *
423 * Returns FALSE in case of failure, otherwise TRUE.
424 */
425static bool init_resolve_thread(struct Curl_easy *data,
426 const char *hostname, int port,
427 const struct addrinfo *hints)
428{
429 struct thread_data *td = calloc(1, sizeof(struct thread_data));
430 int err = ENOMEM;
431 struct Curl_async *asp = &data->state.async;
432
433 data->state.async.tdata = td;
434 if(!td)
435 goto errno_exit;
436
437 asp->port = port;
438 asp->done = FALSE;
439 asp->status = 0;
440 asp->dns = NULL;
441 td->thread_hnd = curl_thread_t_null;
442
443 if(!init_thread_sync_data(td, hostname, port, hints)) {
444 asp->tdata = NULL;
445 free(td);
446 goto errno_exit;
447 }
448
449 free(asp->hostname);
450 asp->hostname = strdup(hostname);
451 if(!asp->hostname)
452 goto err_exit;
453
454 /* The thread will set this to 1 when complete. */
455 td->tsd.done = 0;
456
457#ifdef HAVE_GETADDRINFO
458 td->thread_hnd = Curl_thread_create(func: getaddrinfo_thread, arg: &td->tsd);
459#else
460 td->thread_hnd = Curl_thread_create(gethostbyname_thread, &td->tsd);
461#endif
462
463 if(!td->thread_hnd) {
464 /* The thread never started, so mark it as done here for proper cleanup. */
465 td->tsd.done = 1;
466 err = errno;
467 goto err_exit;
468 }
469
470 return TRUE;
471
472err_exit:
473 destroy_async_data(async: asp);
474
475errno_exit:
476 errno = err;
477 return FALSE;
478}
479
480/*
481 * 'entry' may be NULL and then no data is returned
482 */
483static CURLcode thread_wait_resolv(struct Curl_easy *data,
484 struct Curl_dns_entry **entry,
485 bool report)
486{
487 struct thread_data *td;
488 CURLcode result = CURLE_OK;
489
490 DEBUGASSERT(data);
491 td = data->state.async.tdata;
492 DEBUGASSERT(td);
493 DEBUGASSERT(td->thread_hnd != curl_thread_t_null);
494
495 /* wait for the thread to resolve the name */
496 if(Curl_thread_join(hnd: &td->thread_hnd)) {
497 if(entry)
498 result = getaddrinfo_complete(data);
499 }
500 else
501 DEBUGASSERT(0);
502
503 data->state.async.done = TRUE;
504
505 if(entry)
506 *entry = data->state.async.dns;
507
508 if(!data->state.async.dns && report)
509 /* a name was not resolved, report error */
510 result = Curl_resolver_error(data);
511
512 destroy_async_data(async: &data->state.async);
513
514 if(!data->state.async.dns && report)
515 connclose(data->conn, "asynch resolve failed");
516
517 return result;
518}
519
520
521/*
522 * Until we gain a way to signal the resolver threads to stop early, we must
523 * simply wait for them and ignore their results.
524 */
525void Curl_resolver_kill(struct Curl_easy *data)
526{
527 struct thread_data *td = data->state.async.tdata;
528
529 /* If we're still resolving, we must wait for the threads to fully clean up,
530 unfortunately. Otherwise, we can simply cancel to clean up any resolver
531 data. */
532 if(td && td->thread_hnd != curl_thread_t_null
533 && (data->set.quick_exit != 1L))
534 (void)thread_wait_resolv(data, NULL, FALSE);
535 else
536 Curl_resolver_cancel(data);
537}
538
539/*
540 * Curl_resolver_wait_resolv()
541 *
542 * Waits for a resolve to finish. This function should be avoided since using
543 * this risk getting the multi interface to "hang".
544 *
545 * If 'entry' is non-NULL, make it point to the resolved dns entry
546 *
547 * Returns CURLE_COULDNT_RESOLVE_HOST if the host was not resolved,
548 * CURLE_OPERATION_TIMEDOUT if a time-out occurred, or other errors.
549 *
550 * This is the version for resolves-in-a-thread.
551 */
552CURLcode Curl_resolver_wait_resolv(struct Curl_easy *data,
553 struct Curl_dns_entry **entry)
554{
555 return thread_wait_resolv(data, entry, TRUE);
556}
557
558/*
559 * Curl_resolver_is_resolved() is called repeatedly to check if a previous
560 * name resolve request has completed. It should also make sure to time-out if
561 * the operation seems to take too long.
562 */
563CURLcode Curl_resolver_is_resolved(struct Curl_easy *data,
564 struct Curl_dns_entry **entry)
565{
566 struct thread_data *td = data->state.async.tdata;
567 int done = 0;
568
569 DEBUGASSERT(entry);
570 *entry = NULL;
571
572 if(!td) {
573 DEBUGASSERT(td);
574 return CURLE_COULDNT_RESOLVE_HOST;
575 }
576
577 Curl_mutex_acquire(td->tsd.mtx);
578 done = td->tsd.done;
579 Curl_mutex_release(td->tsd.mtx);
580
581 if(done) {
582 getaddrinfo_complete(data);
583
584 if(!data->state.async.dns) {
585 CURLcode result = Curl_resolver_error(data);
586 destroy_async_data(async: &data->state.async);
587 return result;
588 }
589 destroy_async_data(async: &data->state.async);
590 *entry = data->state.async.dns;
591 }
592 else {
593 /* poll for name lookup done with exponential backoff up to 250ms */
594 /* should be fine even if this converts to 32 bit */
595 timediff_t elapsed = Curl_timediff(newer: Curl_now(),
596 older: data->progress.t_startsingle);
597 if(elapsed < 0)
598 elapsed = 0;
599
600 if(td->poll_interval == 0)
601 /* Start at 1ms poll interval */
602 td->poll_interval = 1;
603 else if(elapsed >= td->interval_end)
604 /* Back-off exponentially if last interval expired */
605 td->poll_interval *= 2;
606
607 if(td->poll_interval > 250)
608 td->poll_interval = 250;
609
610 td->interval_end = elapsed + td->poll_interval;
611 Curl_expire(data, milli: td->poll_interval, EXPIRE_ASYNC_NAME);
612 }
613
614 return CURLE_OK;
615}
616
617int Curl_resolver_getsock(struct Curl_easy *data, curl_socket_t *socks)
618{
619 int ret_val = 0;
620 timediff_t milli;
621 timediff_t ms;
622 struct resdata *reslv = (struct resdata *)data->state.async.resolver;
623#ifndef CURL_DISABLE_SOCKETPAIR
624 struct thread_data *td = data->state.async.tdata;
625#else
626 (void)socks;
627#endif
628
629#ifndef CURL_DISABLE_SOCKETPAIR
630 if(td) {
631 /* return read fd to client for polling the DNS resolution status */
632 socks[0] = td->tsd.sock_pair[0];
633 td->tsd.data = data;
634 ret_val = GETSOCK_READSOCK(0);
635 }
636 else {
637#endif
638 ms = Curl_timediff(newer: Curl_now(), older: reslv->start);
639 if(ms < 3)
640 milli = 0;
641 else if(ms <= 50)
642 milli = ms/3;
643 else if(ms <= 250)
644 milli = 50;
645 else
646 milli = 200;
647 Curl_expire(data, milli, EXPIRE_ASYNC_NAME);
648#ifndef CURL_DISABLE_SOCKETPAIR
649 }
650#endif
651
652
653 return ret_val;
654}
655
656#ifndef HAVE_GETADDRINFO
657/*
658 * Curl_getaddrinfo() - for platforms without getaddrinfo
659 */
660struct Curl_addrinfo *Curl_resolver_getaddrinfo(struct Curl_easy *data,
661 const char *hostname,
662 int port,
663 int *waitp)
664{
665 struct resdata *reslv = (struct resdata *)data->state.async.resolver;
666
667 *waitp = 0; /* default to synchronous response */
668
669 reslv->start = Curl_now();
670
671 /* fire up a new resolver thread! */
672 if(init_resolve_thread(data, hostname, port, NULL)) {
673 *waitp = 1; /* expect asynchronous response */
674 return NULL;
675 }
676
677 failf(data, "getaddrinfo() thread failed");
678
679 return NULL;
680}
681
682#else /* !HAVE_GETADDRINFO */
683
684/*
685 * Curl_resolver_getaddrinfo() - for getaddrinfo
686 */
687struct Curl_addrinfo *Curl_resolver_getaddrinfo(struct Curl_easy *data,
688 const char *hostname,
689 int port,
690 int *waitp)
691{
692 struct addrinfo hints;
693 int pf = PF_INET;
694 struct resdata *reslv = (struct resdata *)data->state.async.resolver;
695
696 *waitp = 0; /* default to synchronous response */
697
698#ifdef CURLRES_IPV6
699 if((data->conn->ip_version != CURL_IPRESOLVE_V4) && Curl_ipv6works(data)) {
700 /* The stack seems to be IPv6-enabled */
701 if(data->conn->ip_version == CURL_IPRESOLVE_V6)
702 pf = PF_INET6;
703 else
704 pf = PF_UNSPEC;
705 }
706#endif /* CURLRES_IPV6 */
707
708 memset(s: &hints, c: 0, n: 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: &hints)) {
716 *waitp = 1; /* expect asynchronous response */
717 return NULL;
718 }
719
720 failf(data, fmt: "getaddrinfo() thread failed to start");
721 return NULL;
722
723}
724
725#endif /* !HAVE_GETADDRINFO */
726
727CURLcode 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
736CURLcode 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
744CURLcode 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
752CURLcode 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