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