/src/curl/lib/vdns/asyn-thrdd.c
Line | Count | Source |
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 | | #include "curl_setup.h" |
25 | | |
26 | | /*********************************************************************** |
27 | | * Only for threaded name resolves builds |
28 | | **********************************************************************/ |
29 | | #ifdef USE_RESOLV_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 | | #ifdef HAVE_GETADDRINFO |
46 | 0 | #define RESOLVER_ENOMEM EAI_MEMORY /* = WSA_NOT_ENOUGH_MEMORY on Windows */ |
47 | | #else |
48 | | #define RESOLVER_ENOMEM SOCKENOMEM |
49 | | #endif |
50 | | |
51 | | #include "urldata.h" |
52 | | #include "cfilters.h" |
53 | | #include "curl_addrinfo.h" |
54 | | #include "curl_trc.h" |
55 | | #include "url.h" |
56 | | #include "multiif.h" |
57 | | #include "curl_threads.h" |
58 | | #include "progress.h" |
59 | | #include "rand.h" |
60 | | #include "select.h" |
61 | | #include "thrdqueue.h" |
62 | | #include "vdns/hostip.h" |
63 | | #include "vdns/httpsrr.h" |
64 | | #include "curlx/strparse.h" |
65 | | #include "curlx/wait.h" |
66 | | |
67 | | #ifdef USE_ARES |
68 | | #include <ares.h> |
69 | | #ifdef USE_HTTPSRR |
70 | | #define USE_HTTPSRR_ARES /* the combo */ |
71 | | #endif |
72 | | #endif |
73 | | |
74 | | |
75 | | /* |
76 | | * Curl_async_global_init() |
77 | | * Called from curl_global_init() to initialize global resolver environment. |
78 | | * Does nothing here. |
79 | | */ |
80 | | int Curl_async_global_init(void) |
81 | 1 | { |
82 | | #if defined(USE_ARES) && defined(CARES_HAVE_ARES_LIBRARY_INIT) |
83 | | if(ares_library_init(ARES_LIB_INIT_ALL)) { |
84 | | return CURLE_FAILED_INIT; |
85 | | } |
86 | | #endif |
87 | 1 | return CURLE_OK; |
88 | 1 | } |
89 | | |
90 | | /* |
91 | | * Curl_async_global_cleanup() |
92 | | * Called from curl_global_cleanup() to destroy global resolver environment. |
93 | | * Does nothing here. |
94 | | */ |
95 | | void Curl_async_global_cleanup(void) |
96 | 0 | { |
97 | | #if defined(USE_ARES) && defined(CARES_HAVE_ARES_LIBRARY_INIT) |
98 | | ares_library_cleanup(); |
99 | | #endif |
100 | 0 | } |
101 | | |
102 | | #ifdef CURLVERBOSE |
103 | | #define CURL_ASYN_ITEM_DESC_LEN 64 |
104 | 0 | #define async_item_description(x) (x)->description |
105 | | #else |
106 | | #define async_item_description(x) NULL |
107 | | #endif |
108 | | |
109 | | struct async_thrdd_item { |
110 | | struct Curl_addrinfo *res; |
111 | | #ifdef CURLVERBOSE |
112 | | char description[CURL_ASYN_ITEM_DESC_LEN]; |
113 | | #endif |
114 | | int sockerr; |
115 | | uint32_t mid; |
116 | | uint32_t resolv_id; |
117 | | uint16_t port; |
118 | | uint8_t transport; |
119 | | uint8_t dns_queries; |
120 | | BIT(negative); /* resolver answered that the name does not exist */ |
121 | | #ifdef DEBUGBUILD |
122 | | uint32_t delay_ms; |
123 | | uint32_t delay_fail_ms; |
124 | | BIT(dbg_negative); |
125 | | #endif |
126 | | char hostname[1]; |
127 | | }; |
128 | | |
129 | | /* Give up reference to add_ctx */ |
130 | | static void async_thrdd_item_destroy(struct async_thrdd_item *item) |
131 | 0 | { |
132 | 0 | if(item) { |
133 | 0 | if(item->res) |
134 | 0 | Curl_freeaddrinfo(item->res); |
135 | 0 | curlx_free(item); |
136 | 0 | } |
137 | 0 | } |
138 | | |
139 | | /* Initialize context for threaded resolver */ |
140 | | static struct async_thrdd_item *async_thrdd_item_create( |
141 | | struct Curl_easy *data, |
142 | | uint32_t resolv_id, uint8_t dns_queries, |
143 | | const char *hostname, uint16_t port, |
144 | | uint8_t transport) |
145 | 0 | { |
146 | 0 | size_t hostlen = strlen(hostname); |
147 | 0 | struct async_thrdd_item *item; |
148 | |
|
149 | 0 | item = curlx_calloc(1, sizeof(*item) + hostlen); |
150 | 0 | if(!item) |
151 | 0 | return NULL; |
152 | | |
153 | 0 | if(hostlen) /* null-terminator of name already in struct size */ |
154 | 0 | memcpy(item->hostname, hostname, hostlen); |
155 | 0 | item->mid = data->mid; |
156 | 0 | item->resolv_id = resolv_id; |
157 | 0 | item->dns_queries = dns_queries; |
158 | 0 | item->port = port; |
159 | 0 | item->transport = transport; |
160 | |
|
161 | 0 | #ifdef CURLVERBOSE |
162 | 0 | curl_msnprintf(item->description, sizeof(item->description), |
163 | 0 | "[%" FMT_OFF_T "/%u] %s %s:%u", |
164 | 0 | data->id, item->resolv_id, |
165 | 0 | Curl_resolv_query_str(dns_queries), |
166 | 0 | item->hostname, item->port); |
167 | 0 | #endif |
168 | |
|
169 | 0 | #ifdef DEBUGBUILD |
170 | 0 | { |
171 | 0 | const char *p = getenv("CURL_DBG_RESOLV_DELAY"); |
172 | 0 | if(p) { |
173 | 0 | curl_off_t l; |
174 | 0 | if(!curlx_str_number(&p, &l, UINT32_MAX)) { |
175 | 0 | item->delay_ms = (uint32_t)l; |
176 | 0 | } |
177 | 0 | } |
178 | 0 | p = getenv("CURL_DBG_RESOLV_FAIL_DELAY"); |
179 | 0 | if(p) { |
180 | 0 | curl_off_t l; |
181 | 0 | if(!curlx_str_number(&p, &l, UINT32_MAX)) { |
182 | 0 | unsigned char c = 0; |
183 | 0 | Curl_rand_bytes(data, FALSE, &c, 1); |
184 | 0 | item->delay_fail_ms = (uint32_t)l + c; |
185 | 0 | } |
186 | 0 | } |
187 | 0 | if(getenv("CURL_DBG_RESOLV_FAIL_NEGATIVE")) |
188 | 0 | item->dbg_negative = TRUE; |
189 | 0 | } |
190 | 0 | #endif |
191 | |
|
192 | 0 | return item; |
193 | 0 | } |
194 | | |
195 | | #ifdef USE_HTTPSRR_ARES |
196 | | |
197 | | static void async_thrdd_rr_done(void *user_data, ares_status_t status, |
198 | | size_t timeouts, |
199 | | const ares_dns_record_t *dnsrec) |
200 | | { |
201 | | struct Curl_resolv_async *async = user_data; |
202 | | struct async_thrdd_ctx *thrdd = async ? &async->thrdd : NULL; |
203 | | |
204 | | (void)timeouts; |
205 | | if(!thrdd) |
206 | | return; |
207 | | |
208 | | async->dns_responses |= CURL_DNSQ_HTTPS; |
209 | | async->queries_ongoing--; |
210 | | async->done = !async->queries_ongoing; |
211 | | if((ARES_SUCCESS == status) && dnsrec) |
212 | | async->result = Curl_httpsrr_from_ares(dnsrec, &async->httpsrr); |
213 | | } |
214 | | |
215 | | /* Initialize the c-ares channel used for the HTTPS RR side-query, without |
216 | | * submitting any query on it yet. */ |
217 | | static CURLcode async_rr_init(struct Curl_easy *data, |
218 | | struct Curl_resolv_async *async) |
219 | | { |
220 | | struct async_thrdd_ctx *thrdd = &async->thrdd; |
221 | | int status; |
222 | | |
223 | | (void)data; |
224 | | DEBUGASSERT(!thrdd->rr.channel); |
225 | | status = ares_init_options(&thrdd->rr.channel, NULL, 0); |
226 | | if(status != ARES_SUCCESS) { |
227 | | thrdd->rr.channel = NULL; |
228 | | return CURLE_FAILED_INIT; |
229 | | } |
230 | | #ifdef DEBUGBUILD |
231 | | if(getenv("CURL_DNS_SERVER")) { |
232 | | const char *servers = getenv("CURL_DNS_SERVER"); |
233 | | status = ares_set_servers_ports_csv(thrdd->rr.channel, servers); |
234 | | if(status) |
235 | | return CURLE_FAILED_INIT; |
236 | | } |
237 | | #endif |
238 | | return CURLE_OK; |
239 | | } |
240 | | |
241 | | /* Submit the HTTPS RR query on the already initialized channel. */ |
242 | | static CURLcode async_rr_start(struct Curl_easy *data, |
243 | | struct Curl_resolv_async *async) |
244 | | { |
245 | | struct async_thrdd_ctx *thrdd = &async->thrdd; |
246 | | char *https_name = NULL; |
247 | | CURLcode result = CURLE_OK; |
248 | | |
249 | | if(async->peer->port != 443) { |
250 | | https_name = curl_maprintf("_%u._https.%s", |
251 | | async->peer->port, async->peer->hostname); |
252 | | if(!https_name) { |
253 | | result = CURLE_OUT_OF_MEMORY; |
254 | | goto out; |
255 | | } |
256 | | } |
257 | | |
258 | | async->queries_ongoing++; |
259 | | ares_query_dnsrec(thrdd->rr.channel, |
260 | | https_name ? https_name : async->peer->hostname, |
261 | | ARES_CLASS_IN, ARES_REC_TYPE_HTTPS, |
262 | | async_thrdd_rr_done, async, NULL); |
263 | | CURL_TRC_DNS(data, "[HTTPS] query records for %s", |
264 | | https_name ? https_name : async->peer->hostname); |
265 | | |
266 | | out: |
267 | | curlx_free(https_name); |
268 | | return result; |
269 | | } |
270 | | #endif |
271 | | |
272 | | void Curl_async_thrdd_shutdown(struct Curl_easy *data, |
273 | | struct Curl_resolv_async *async) |
274 | 0 | { |
275 | 0 | Curl_async_thrdd_destroy(data, async); |
276 | 0 | } |
277 | | |
278 | | struct async_thrdd_match_ctx { |
279 | | uint32_t mid; |
280 | | uint32_t resolv_id; |
281 | | }; |
282 | | |
283 | | static bool async_thrdd_match_item(void *qitem, void *match_data) |
284 | 0 | { |
285 | 0 | const struct async_thrdd_match_ctx *ctx = match_data; |
286 | 0 | struct async_thrdd_item *item = qitem; |
287 | 0 | return (item->mid == ctx->mid) && (item->resolv_id == ctx->resolv_id); |
288 | 0 | } |
289 | | |
290 | | void Curl_async_thrdd_destroy(struct Curl_easy *data, |
291 | | struct Curl_resolv_async *async) |
292 | 0 | { |
293 | 0 | (void)data; |
294 | 0 | if(async->queries_ongoing && !async->done && |
295 | 0 | data->multi && data->multi->resolv_thrdq) { |
296 | | /* Remove any resolve items still queued */ |
297 | 0 | struct async_thrdd_match_ctx mctx; |
298 | 0 | mctx.mid = data->mid; |
299 | 0 | mctx.resolv_id = async->id; |
300 | 0 | Curl_thrdq_clear(data->multi->resolv_thrdq, |
301 | 0 | async_thrdd_match_item, &mctx); |
302 | 0 | } |
303 | | #ifdef USE_HTTPSRR_ARES |
304 | | if(async->thrdd.rr.channel) { |
305 | | ares_destroy(async->thrdd.rr.channel); |
306 | | async->thrdd.rr.channel = NULL; |
307 | | } |
308 | | #endif |
309 | 0 | async_thrdd_item_destroy(async->thrdd.res_A); |
310 | 0 | async->thrdd.res_A = NULL; |
311 | 0 | async_thrdd_item_destroy(async->thrdd.res_AAAA); |
312 | 0 | async->thrdd.res_AAAA = NULL; |
313 | 0 | } |
314 | | |
315 | | /* |
316 | | * Waits for a resolve to finish. This function should be avoided since using |
317 | | * this risk getting the multi interface to "hang". |
318 | | */ |
319 | | CURLcode Curl_async_await(struct Curl_easy *data, uint32_t resolv_id, |
320 | | struct Curl_dns_entry **pdns) |
321 | 0 | { |
322 | 0 | struct Curl_resolv_async *async = Curl_async_get(data, resolv_id); |
323 | 0 | struct async_thrdd_ctx *thrdd = async ? &async->thrdd : NULL; |
324 | 0 | timediff_t milli, ms; |
325 | 0 | CURLcode result = CURLE_AGAIN; |
326 | |
|
327 | 0 | if(!thrdd) |
328 | 0 | return CURLE_FAILED_INIT; |
329 | | |
330 | 0 | while(result == CURLE_AGAIN) { |
331 | 0 | while(async->queries_ongoing && !async->done) { |
332 | 0 | Curl_async_thrdd_multi_process(data->multi); |
333 | 0 | if(async->done) |
334 | 0 | break; |
335 | | |
336 | 0 | ms = curlx_ptimediff_ms(Curl_pgrs_now(data), &async->start); |
337 | 0 | if(ms < 3) |
338 | 0 | milli = 0; |
339 | 0 | else if(ms <= 50) |
340 | 0 | milli = ms / 3; |
341 | 0 | else if(ms <= 250) |
342 | 0 | milli = 50; |
343 | 0 | else |
344 | 0 | milli = 200; |
345 | 0 | CURL_TRC_DNS(data, "await, waiting %" FMT_TIMEDIFF_T "ms", milli); |
346 | 0 | curlx_wait_ms(milli); |
347 | 0 | } |
348 | 0 | result = Curl_async_take_result(data, async, pdns); |
349 | 0 | } |
350 | 0 | return result; |
351 | 0 | } |
352 | | |
353 | | #ifdef HAVE_GETADDRINFO |
354 | | |
355 | | /* Was the getaddrinfo() failure an authoritative negative answer, |
356 | | i.e. the resolver responded that the name (or its data) does not |
357 | | exist? Transient failures like EAI_AGAIN and local troubles must |
358 | | not count as negative answers. */ |
359 | | static bool gai_negative(int rc) |
360 | 0 | { |
361 | 0 | switch(rc) { |
362 | 0 | #ifdef EAI_NONAME |
363 | 0 | case EAI_NONAME: |
364 | 0 | #endif |
365 | 0 | #if defined(EAI_NODATA) && \ |
366 | 0 | (!defined(EAI_NONAME) || (EAI_NODATA != EAI_NONAME)) |
367 | 0 | case EAI_NODATA: |
368 | 0 | #endif |
369 | 0 | return TRUE; |
370 | 0 | default: |
371 | 0 | return FALSE; |
372 | 0 | } |
373 | 0 | } |
374 | | |
375 | | /* Process the item, using Curl_getaddrinfo_ex() */ |
376 | | static void async_thrdd_item_process(void *arg) |
377 | 0 | { |
378 | 0 | struct async_thrdd_item *item = arg; |
379 | 0 | struct addrinfo hints; |
380 | 0 | char service[12]; |
381 | 0 | int pf = PF_INET; |
382 | 0 | int rc; |
383 | |
|
384 | 0 | #ifdef DEBUGBUILD |
385 | 0 | if(item->delay_ms) { |
386 | 0 | curlx_wait_ms(item->delay_ms); |
387 | 0 | } |
388 | 0 | if(item->delay_fail_ms) { |
389 | 0 | curlx_wait_ms(item->delay_fail_ms); |
390 | 0 | item->negative = item->dbg_negative; |
391 | 0 | return; |
392 | 0 | } |
393 | 0 | #endif |
394 | | |
395 | 0 | memset(&hints, 0, sizeof(hints)); |
396 | 0 | #ifdef CURLRES_IPV6 |
397 | 0 | if(item->dns_queries & CURL_DNSQ_AAAA) { |
398 | 0 | pf = (item->dns_queries & CURL_DNSQ_A) ? PF_UNSPEC : PF_INET6; |
399 | 0 | } |
400 | 0 | #endif |
401 | 0 | hints.ai_family = pf; |
402 | 0 | hints.ai_socktype = Curl_socktype_for_transport(item->transport); |
403 | 0 | hints.ai_protocol = Curl_protocol_for_transport(item->transport); |
404 | | #ifdef __APPLE__ |
405 | | /* If we leave `ai_flags == 0` then macOS is looking for IPV4MAPPED |
406 | | * when doing AAAA queries. We do not want this "help". */ |
407 | | hints.ai_flags = AI_ADDRCONFIG; |
408 | | #endif |
409 | |
|
410 | 0 | curl_msnprintf(service, sizeof(service), "%u", item->port); |
411 | 0 | #ifdef AI_NUMERICSERV |
412 | 0 | hints.ai_flags |= AI_NUMERICSERV; |
413 | 0 | #endif |
414 | |
|
415 | 0 | rc = Curl_getaddrinfo_ex(item->hostname, service, &hints, &item->res); |
416 | 0 | if(rc) { |
417 | 0 | item->sockerr = SOCKERRNO ? SOCKERRNO : rc; |
418 | 0 | if(item->sockerr == 0) |
419 | 0 | item->sockerr = RESOLVER_ENOMEM; |
420 | 0 | item->negative = gai_negative(rc); |
421 | 0 | } |
422 | 0 | else { |
423 | 0 | Curl_addrinfo_set_port(item->res, item->port); |
424 | 0 | } |
425 | 0 | } |
426 | | |
427 | | #else /* HAVE_GETADDRINFO */ |
428 | | |
429 | | /* Process the item, using Curl_ipv4_resolve_r() */ |
430 | | static void async_thrdd_item_process(void *arg) |
431 | | { |
432 | | struct async_thrdd_item *item = arg; |
433 | | |
434 | | #ifdef DEBUGBUILD |
435 | | if(item->delay_ms) { |
436 | | curlx_wait_ms(item->delay_ms); |
437 | | } |
438 | | if(item->delay_fail_ms) { |
439 | | curlx_wait_ms(item->delay_fail_ms); |
440 | | item->negative = item->dbg_negative; |
441 | | return; |
442 | | } |
443 | | #endif |
444 | | item->res = Curl_ipv4_resolve_r(item->hostname, item->port); |
445 | | if(!item->res) { |
446 | | item->sockerr = SOCKERRNO; |
447 | | if(item->sockerr == 0) |
448 | | item->sockerr = RESOLVER_ENOMEM; |
449 | | /* this resolver cannot tell a transient failure from an |
450 | | authoritative negative answer, treat it as before */ |
451 | | item->negative = TRUE; |
452 | | } |
453 | | } |
454 | | |
455 | | #endif /* HAVE_GETADDRINFO */ |
456 | | |
457 | | #ifdef ENABLE_INTERNAL_WAKEUP |
458 | | static void async_thrdd_event(const struct curl_thrdq *tqueue, |
459 | | Curl_thrdq_event ev, |
460 | | void *user_data) |
461 | 0 | { |
462 | 0 | struct Curl_multi *multi = user_data; |
463 | 0 | (void)tqueue; |
464 | 0 | switch(ev) { |
465 | 0 | case CURL_THRDQ_EV_ITEM_DONE: |
466 | 0 | Curl_multi_wakeup_internal(multi); |
467 | 0 | break; |
468 | 0 | default: |
469 | 0 | break; |
470 | 0 | } |
471 | 0 | } |
472 | | #else |
473 | | #define async_thrdd_event NULL |
474 | | #endif |
475 | | |
476 | | static void async_thrdd_item_free(void *item) |
477 | 0 | { |
478 | 0 | async_thrdd_item_destroy(item); |
479 | 0 | } |
480 | | |
481 | | /* Create a thread queue for processing resolv items */ |
482 | | CURLcode Curl_async_thrdd_multi_init(struct Curl_multi *multi, |
483 | | uint32_t min_threads, |
484 | | uint32_t max_threads, |
485 | | uint32_t idle_time_ms) |
486 | 13.6k | { |
487 | 13.6k | CURLcode result; |
488 | 13.6k | DEBUGASSERT(!multi->resolv_thrdq); |
489 | 13.6k | result = Curl_thrdq_create(&multi->resolv_thrdq, "DNS", |
490 | 13.6k | min_threads, max_threads, idle_time_ms, |
491 | 13.6k | async_thrdd_item_free, |
492 | 13.6k | async_thrdd_item_process, |
493 | 13.6k | async_thrdd_event, |
494 | 13.6k | multi); |
495 | 13.6k | #ifdef DEBUGBUILD |
496 | 13.6k | if(!result) { |
497 | 13.6k | const char *p = getenv("CURL_DBG_RESOLV_MAX_THREADS"); |
498 | 13.6k | if(p) { |
499 | 0 | curl_off_t l; |
500 | 0 | if(!curlx_str_number(&p, &l, UINT32_MAX)) { |
501 | 0 | result = Curl_async_thrdd_multi_set_props( |
502 | 0 | multi, min_threads, (uint32_t)l, idle_time_ms); |
503 | 0 | } |
504 | 0 | } |
505 | 13.6k | } |
506 | 13.6k | #endif |
507 | 13.6k | return result; |
508 | 13.6k | } |
509 | | |
510 | | /* Tear down the thread queue, joining active threads or detaching them */ |
511 | | void Curl_async_thrdd_multi_destroy(struct Curl_multi *multi, bool join) |
512 | 13.6k | { |
513 | 13.6k | if(multi->resolv_thrdq) { |
514 | 13.6k | #ifdef CURLVERBOSE |
515 | 13.6k | CURL_TRC_DNS(multi->admin, "destroy thread queue+pool, join=%d", join); |
516 | 13.6k | CURL_THRDQ_TRACE(multi->resolv_thrdq, multi->admin); |
517 | 13.6k | #endif |
518 | 13.6k | Curl_thrdq_destroy(multi->resolv_thrdq, join); |
519 | 13.6k | multi->resolv_thrdq = NULL; |
520 | 13.6k | } |
521 | 13.6k | } |
522 | | |
523 | | #ifdef CURLVERBOSE |
524 | | static void async_thrdd_report_item(struct Curl_easy *data, |
525 | | struct async_thrdd_item *item) |
526 | 0 | { |
527 | 0 | char buf[MAX_IPADR_LEN]; |
528 | 0 | struct dynbuf tmp; |
529 | 0 | const char *sep = ""; |
530 | 0 | const struct Curl_addrinfo *ai = item->res; |
531 | 0 | CURLcode result; |
532 | 0 | int ai_family; |
533 | 0 | #ifdef USE_IPV6 |
534 | 0 | ai_family = (item->dns_queries & CURL_DNSQ_AAAA) ? AF_INET6 : AF_INET; |
535 | | #else |
536 | | ai_family = AF_INET; |
537 | | #endif |
538 | |
|
539 | 0 | if(!CURL_TRC_DNS_is_verbose(data)) |
540 | 0 | return; |
541 | | |
542 | 0 | curlx_dyn_init(&tmp, 1024); |
543 | 0 | for(; ai; ai = ai->ai_next) { |
544 | 0 | if(ai->ai_family == ai_family) { |
545 | 0 | Curl_printable_address(ai, buf, sizeof(buf)); |
546 | 0 | result = curlx_dyn_addf(&tmp, "%s%s", sep, buf); |
547 | 0 | if(result) { |
548 | 0 | CURL_TRC_DNS(data, "too many IP, cannot show"); |
549 | 0 | goto out; |
550 | 0 | } |
551 | 0 | sep = ", "; |
552 | 0 | } |
553 | 0 | } |
554 | | |
555 | 0 | CURL_TRC_DNS(data, "Host %s:%u resolved IPv%c: %s", |
556 | 0 | item->hostname, item->port, |
557 | 0 | (item->dns_queries & CURL_DNSQ_AAAA) ? '6' : '4', |
558 | 0 | (curlx_dyn_len(&tmp) ? curlx_dyn_ptr(&tmp) : "(none)")); |
559 | 0 | out: |
560 | 0 | curlx_dyn_free(&tmp); |
561 | 0 | } |
562 | | #endif /* CURLVERBOSE */ |
563 | | |
564 | | /* Process the receiving end of the thread queue, dispatching |
565 | | * processed items to their transfer when it can still be found |
566 | | * and has an `async` state present. Otherwise, destroy the item. */ |
567 | | void Curl_async_thrdd_multi_process(struct Curl_multi *multi) |
568 | 14.6k | { |
569 | 14.6k | struct Curl_easy *data; |
570 | 14.6k | void *qitem; |
571 | | |
572 | 14.6k | while(!Curl_thrdq_recv(multi->resolv_thrdq, &qitem)) { |
573 | | /* dispatch resolve result */ |
574 | 0 | struct async_thrdd_item *item = qitem; |
575 | 0 | struct Curl_resolv_async *async = NULL; |
576 | |
|
577 | 0 | data = Curl_multi_get_easy(multi, item->mid); |
578 | 0 | if(data) |
579 | 0 | async = Curl_async_get(data, item->resolv_id); |
580 | 0 | if(async) { |
581 | 0 | struct async_thrdd_item **pdest = &async->thrdd.res_A; |
582 | |
|
583 | 0 | #ifdef CURLRES_IPV6 |
584 | 0 | if(item->dns_queries & CURL_DNSQ_AAAA) |
585 | 0 | pdest = &async->thrdd.res_AAAA; |
586 | 0 | #endif |
587 | 0 | if(!*pdest) { |
588 | 0 | *pdest = item; |
589 | 0 | item = NULL; |
590 | 0 | } |
591 | 0 | else |
592 | 0 | DEBUGASSERT(0); /* should not receive duplicates here */ |
593 | |
|
594 | 0 | --async->queries_ongoing; |
595 | 0 | Curl_multi_mark_dirty(data); |
596 | 0 | } |
597 | 0 | async_thrdd_item_free(item); |
598 | 0 | } |
599 | 14.6k | CURL_THRDQ_TRACE(multi->resolv_thrdq, multi->admin); |
600 | 14.6k | } |
601 | | |
602 | | CURLcode Curl_async_thrdd_multi_set_props(struct Curl_multi *multi, |
603 | | uint32_t min_threads, |
604 | | uint32_t max_threads, |
605 | | uint32_t idle_time_ms) |
606 | 0 | { |
607 | 0 | return Curl_thrdq_set_props(multi->resolv_thrdq, |
608 | 0 | min_threads, max_threads, idle_time_ms); |
609 | 0 | } |
610 | | |
611 | | static CURLcode async_thrdd_query(struct Curl_easy *data, |
612 | | struct Curl_resolv_async *async, |
613 | | uint8_t dns_queries) |
614 | 0 | { |
615 | 0 | struct async_thrdd_item *item; |
616 | 0 | CURLcode result; |
617 | |
|
618 | 0 | item = async_thrdd_item_create(data, async->id, dns_queries, |
619 | 0 | async->peer->hostname, async->peer->port, |
620 | 0 | async->transport); |
621 | 0 | if(!item) { |
622 | 0 | result = CURLE_OUT_OF_MEMORY; |
623 | 0 | goto out; |
624 | 0 | } |
625 | 0 | CURL_TRC_DNS(data, "queueing query %s", item->description); |
626 | 0 | result = Curl_thrdq_send(data->multi->resolv_thrdq, item, |
627 | 0 | async_item_description(item), async->timeout_ms); |
628 | 0 | if(result) |
629 | 0 | goto out; |
630 | 0 | item = NULL; |
631 | 0 | async->queries_ongoing++; |
632 | |
|
633 | 0 | out: |
634 | 0 | if(item) |
635 | 0 | async_thrdd_item_free(item); |
636 | 0 | return result; |
637 | 0 | } |
638 | | |
639 | | CURLcode Curl_async_getaddrinfo(struct Curl_easy *data, |
640 | | struct Curl_resolv_async *async) |
641 | 0 | { |
642 | 0 | CURLcode result = CURLE_FAILED_INIT; |
643 | 0 | void *resolver = NULL; |
644 | |
|
645 | 0 | if(async->queries_ongoing || async->done) |
646 | 0 | return CURLE_FAILED_INIT; |
647 | | |
648 | | #ifdef USE_HTTPSRR_ARES |
649 | | DEBUGASSERT(!async->thrdd.rr.channel); |
650 | | if((async->dns_queries & CURL_DNSQ_HTTPS) && !async->is_ipaddr) { |
651 | | result = async_rr_init(data, async); |
652 | | if(result) |
653 | | goto out; |
654 | | resolver = async->thrdd.rr.channel; |
655 | | } |
656 | | #endif |
657 | | |
658 | 0 | result = Curl_resolv_announce_start(data, resolver); |
659 | 0 | if(result) |
660 | 0 | return result; |
661 | | |
662 | | #ifdef USE_HTTPSRR_ARES |
663 | | if(resolver) { |
664 | | result = async_rr_start(data, async); |
665 | | if(result) |
666 | | goto out; |
667 | | } |
668 | | #endif |
669 | | |
670 | 0 | #ifdef CURLRES_IPV6 |
671 | | /* Do not start an AAAA query for an IPv4 address when |
672 | | * we will start an A query for it. */ |
673 | 0 | if((async->dns_queries & CURL_DNSQ_AAAA) && |
674 | 0 | !(async->is_ipv4addr && (async->dns_queries & CURL_DNSQ_A))) { |
675 | 0 | result = async_thrdd_query(data, async, CURL_DNSQ_AAAA); |
676 | 0 | if(result) |
677 | 0 | goto out; |
678 | 0 | } |
679 | 0 | #endif |
680 | 0 | if(async->dns_queries & CURL_DNSQ_A) { |
681 | 0 | result = async_thrdd_query(data, async, CURL_DNSQ_A); |
682 | 0 | if(result) |
683 | 0 | goto out; |
684 | 0 | } |
685 | | |
686 | 0 | CURL_THRDQ_TRACE(data->multi->resolv_thrdq, data); |
687 | |
|
688 | 0 | out: |
689 | 0 | if(!async->queries_ongoing) |
690 | 0 | async->done = TRUE; |
691 | |
|
692 | 0 | if(result) |
693 | 0 | CURL_TRC_DNS(data, "error queueing query %s:%d -> %d", |
694 | 0 | async->peer->hostname, async->peer->port, (int)result); |
695 | 0 | return result; |
696 | 0 | } |
697 | | |
698 | | CURLcode Curl_async_pollset(struct Curl_easy *data, |
699 | | struct Curl_resolv_async *async, |
700 | | struct easy_pollset *ps) |
701 | 0 | { |
702 | 0 | timediff_t timeout_ms; |
703 | |
|
704 | 0 | timeout_ms = Curl_async_timeleft_ms(data, async); |
705 | | #ifdef USE_HTTPSRR_ARES |
706 | | if(async->thrdd.rr.channel) { |
707 | | CURLcode result = Curl_ares_pollset(data, async->thrdd.rr.channel, ps); |
708 | | if(result) |
709 | | return result; |
710 | | timeout_ms = Curl_ares_timeout_ms(data, async, async->thrdd.rr.channel); |
711 | | } |
712 | | #else |
713 | 0 | (void)ps; |
714 | 0 | #endif |
715 | |
|
716 | 0 | if(!async->done) { |
717 | 0 | const struct curltime *pnow = Curl_pgrs_now(data); |
718 | | #ifndef ENABLE_INTERNAL_WAKEUP |
719 | | timediff_t stutter_ms, elapsed_ms; |
720 | | elapsed_ms = curlx_ptimediff_ms(pnow, &async->start); |
721 | | if(elapsed_ms < 3) |
722 | | stutter_ms = 1; |
723 | | else if(elapsed_ms <= 50) |
724 | | stutter_ms = elapsed_ms / 3; |
725 | | else if(elapsed_ms <= 250) |
726 | | stutter_ms = 50; |
727 | | else |
728 | | stutter_ms = 200; |
729 | | timeout_ms = CURLMIN(stutter_ms, timeout_ms); |
730 | | #else |
731 | 0 | if(async->queries_ongoing && |
732 | 0 | !Curl_thrdq_check_started(data->multi->resolv_thrdq)) { |
733 | | /* The queue has items but starting a worker thread to process |
734 | | them just failed again; expire soon to check once more, |
735 | | instead of sleeping on the full resolve timeout. */ |
736 | 0 | CURL_TRC_DNS(data, "resolver thread start failed again, retrying"); |
737 | 0 | timeout_ms = CURLMIN(100, timeout_ms); |
738 | 0 | } |
739 | 0 | #endif |
740 | 0 | Curl_expire_set(data, EXPIRE_ASYNC_NAME, timeout_ms, pnow); |
741 | 0 | } |
742 | 0 | return CURLE_OK; |
743 | 0 | } |
744 | | |
745 | | #if defined(USE_IPV6) && defined(HAVE_SOCKADDR_IN6_SIN6_SCOPE_ID) |
746 | | static bool async_thrdd_item_missing_scope(struct Curl_easy *data, |
747 | | struct async_thrdd_item *item) |
748 | 0 | { |
749 | 0 | const struct Curl_addrinfo *ai; |
750 | | |
751 | | /* scope id already globally set */ |
752 | 0 | if(data->conn && data->conn->scope_id) |
753 | 0 | return FALSE; |
754 | | |
755 | 0 | for(ai = item->res; ai; ai = ai->ai_next) { |
756 | 0 | if(ai->ai_family == AF_INET6) { |
757 | 0 | struct sockaddr_in6 *sa6 = (void *)ai->ai_addr; |
758 | 0 | if(IN6_IS_ADDR_LINKLOCAL(&sa6->sin6_addr) && !sa6->sin6_scope_id) |
759 | 0 | return TRUE; |
760 | 0 | } |
761 | 0 | } |
762 | 0 | return FALSE; |
763 | 0 | } |
764 | | |
765 | | static void async_thrdd_item_strip_results(struct async_thrdd_item *item, |
766 | | int ai_family) |
767 | 0 | { |
768 | 0 | struct Curl_addrinfo *ai = item->res, **panchor = &item->res; |
769 | 0 | while(ai) { |
770 | 0 | if(ai->ai_family == ai_family) { |
771 | 0 | *panchor = ai->ai_next; |
772 | 0 | ai->ai_next = NULL; |
773 | 0 | Curl_freeaddrinfo(ai); |
774 | 0 | ai = *panchor; |
775 | 0 | } |
776 | 0 | else { |
777 | 0 | panchor = &ai->ai_next; |
778 | 0 | ai = ai->ai_next; |
779 | 0 | } |
780 | 0 | } |
781 | 0 | } |
782 | | |
783 | | #endif /* USE_IPV6 && HAVE_SOCKADDR_IN6_SIN6_SCOPE_ID */ |
784 | | |
785 | | static CURLcode async_thrdd_check_done(struct Curl_easy *data, |
786 | | struct Curl_resolv_async *async) |
787 | 0 | { |
788 | 0 | struct async_thrdd_ctx *thrdd = &async->thrdd; |
789 | |
|
790 | 0 | (void)data; |
791 | 0 | if(thrdd->res_A && !thrdd->processed_A) { |
792 | 0 | VERBOSE(async_thrdd_report_item(data, thrdd->res_A)); |
793 | | /* move addrinfos to the async result member */ |
794 | 0 | async->dns_responses |= thrdd->res_A->dns_queries; |
795 | 0 | async->ai_A = thrdd->res_A->res; |
796 | 0 | thrdd->res_A->res = NULL; |
797 | 0 | thrdd->processed_A = TRUE; |
798 | 0 | } |
799 | |
|
800 | 0 | if(thrdd->res_AAAA && !thrdd->processed_AAAA) { |
801 | 0 | #if defined(USE_IPV6) && defined(HAVE_SOCKADDR_IN6_SIN6_SCOPE_ID) |
802 | | /* do we accept the incoming AAAA response? */ |
803 | 0 | if(!(thrdd->res_AAAA->dns_queries & CURL_DNSQ_A) && |
804 | 0 | async_thrdd_item_missing_scope(data, thrdd->res_AAAA)) { |
805 | | /* We queried "only" AF_INET6. This may be problematic when the |
806 | | * result has ipv6 link-local addresses and did not give |
807 | | * any scope id for it. glibc has a long outstanding bug |
808 | | * <https://sourceware.org/bugzilla/show_bug.cgi?id=14413> |
809 | | * that gives scope ids only on AF_UNSPEC queries. */ |
810 | 0 | struct async_thrdd_item *item = thrdd->res_AAAA; |
811 | 0 | CURLcode result; |
812 | | |
813 | | /* Reuse the item and queue it again, this time with added |
814 | | * CURL_DNSQ_A which resolves using AF_UNSPEC. */ |
815 | 0 | thrdd->res_AAAA = NULL; |
816 | 0 | item->dns_queries |= CURL_DNSQ_A; |
817 | 0 | if(item->res) { |
818 | 0 | Curl_freeaddrinfo(item->res); |
819 | 0 | item->res = NULL; |
820 | 0 | } |
821 | |
|
822 | 0 | CURL_TRC_DNS(data, "re-queueing query %s for AF_UNSPEC resolve", |
823 | 0 | item->description); |
824 | 0 | result = Curl_thrdq_send(data->multi->resolv_thrdq, item, |
825 | 0 | async_item_description(item), |
826 | 0 | async->timeout_ms); |
827 | 0 | if(result) { |
828 | 0 | async_thrdd_item_free(item); |
829 | 0 | return result; |
830 | 0 | } |
831 | 0 | async->queries_ongoing++; |
832 | 0 | return CURLE_AGAIN; |
833 | 0 | } |
834 | | /* accepting the AAAA result, strip it of any AF_INET entries, |
835 | | * as we might have resolved it with AF_UNSPEC. */ |
836 | 0 | async_thrdd_item_strip_results(thrdd->res_AAAA, AF_INET); |
837 | 0 | #endif /* USE_IPV6 && HAVE_SOCKADDR_IN6_SIN6_SCOPE_ID */ |
838 | 0 | VERBOSE(async_thrdd_report_item(data, thrdd->res_AAAA)); |
839 | | /* move addrinfos to the async result member */ |
840 | 0 | async->dns_responses |= thrdd->res_AAAA->dns_queries; |
841 | 0 | async->ai_AAAA = thrdd->res_AAAA->res; |
842 | 0 | thrdd->res_AAAA->res = NULL; |
843 | 0 | thrdd->processed_AAAA = TRUE; |
844 | 0 | } |
845 | | |
846 | 0 | if(async->queries_ongoing) |
847 | 0 | return CURLE_AGAIN; |
848 | 0 | async->done = TRUE; |
849 | 0 | return CURLE_OK; |
850 | 0 | } |
851 | | |
852 | | /* |
853 | | * Curl_async_take_result() is called repeatedly to check if a previous |
854 | | * name resolve request has completed. It should also make sure to time-out if |
855 | | * the operation seems to take too long. |
856 | | */ |
857 | | CURLcode Curl_async_take_result(struct Curl_easy *data, |
858 | | struct Curl_resolv_async *async, |
859 | | struct Curl_dns_entry **pdns) |
860 | 0 | { |
861 | 0 | struct async_thrdd_ctx *thrdd = &async->thrdd; |
862 | 0 | struct Curl_dns_entry *dns = NULL; |
863 | 0 | CURLcode result = CURLE_OK; |
864 | |
|
865 | 0 | DEBUGASSERT(pdns); |
866 | 0 | *pdns = NULL; |
867 | |
|
868 | | #ifdef USE_HTTPSRR_ARES |
869 | | /* best effort, ignore errors */ |
870 | | if(thrdd->rr.channel) |
871 | | (void)Curl_ares_perform(thrdd->rr.channel, 0); |
872 | | #endif |
873 | | #ifndef ENABLE_INTERNAL_WAKEUP |
874 | | Curl_async_thrdd_multi_process(data->multi); |
875 | | #endif |
876 | |
|
877 | 0 | result = async_thrdd_check_done(data, async); |
878 | 0 | if(result) |
879 | 0 | return result; |
880 | | |
881 | 0 | Curl_expire_clear(data, EXPIRE_ASYNC_NAME); |
882 | | |
883 | | /* A failure is an authoritative negative answer, eligible for |
884 | | negative caching, only when every A/AAAA query performed came |
885 | | back answering that the name does not exist. A query that |
886 | | failed transiently or never returned is not an answer. */ |
887 | 0 | { |
888 | 0 | const uint8_t ip_queries = |
889 | 0 | async->dns_queries & (CURL_DNSQ_A | CURL_DNSQ_AAAA); |
890 | 0 | bool negative = (async->dns_responses & ip_queries) == ip_queries; |
891 | 0 | if(thrdd->res_A && (async->ai_A || !thrdd->res_A->negative)) |
892 | 0 | negative = FALSE; |
893 | 0 | if(thrdd->res_AAAA && (async->ai_AAAA || !thrdd->res_AAAA->negative)) |
894 | 0 | negative = FALSE; |
895 | 0 | if(!thrdd->res_A && !thrdd->res_AAAA) |
896 | 0 | negative = FALSE; |
897 | 0 | async->negative_answer = negative; |
898 | 0 | } |
899 | |
|
900 | 0 | if(async->result) { |
901 | 0 | result = async->result; |
902 | 0 | goto out; |
903 | 0 | } |
904 | | |
905 | 0 | if(async->ai_A || async->ai_AAAA) { |
906 | 0 | dns = Curl_dnsc_mk_addr2( |
907 | 0 | data, async->dns_queries, &async->ai_A, &async->ai_AAAA, async->peer); |
908 | 0 | if(!dns) { |
909 | 0 | result = CURLE_OUT_OF_MEMORY; |
910 | 0 | goto out; |
911 | 0 | } |
912 | 0 | } |
913 | | |
914 | | #ifdef USE_HTTPSRR_ARES |
915 | | if(!dns && thrdd->rr.channel) { |
916 | | Curl_httpsrr_trace(data, async->httpsrr); |
917 | | dns = Curl_dnsc_mk_https(data, &async->httpsrr, async->peer); |
918 | | if(!dns) { |
919 | | result = CURLE_OUT_OF_MEMORY; |
920 | | goto out; |
921 | | } |
922 | | } |
923 | | #endif |
924 | | |
925 | 0 | if(dns) { |
926 | 0 | *pdns = dns; |
927 | 0 | dns = NULL; |
928 | 0 | } |
929 | 0 | CURL_THRDQ_TRACE(data->multi->resolv_thrdq, data); |
930 | |
|
931 | 0 | out: |
932 | 0 | Curl_dns_entry_unlink(data, &dns); |
933 | 0 | Curl_async_thrdd_shutdown(data, async); |
934 | 0 | if(!result && !*pdns) |
935 | 0 | result = Curl_async_failed(data, async, NULL); |
936 | 0 | if(result && |
937 | 0 | (result != CURLE_COULDNT_RESOLVE_HOST) && |
938 | 0 | (result != CURLE_COULDNT_RESOLVE_PROXY)) { |
939 | 0 | CURL_TRC_DNS(data, "Error %d resolving %s:%d", |
940 | 0 | (int)result, async->peer->hostname, async->peer->port); |
941 | 0 | } |
942 | 0 | return result; |
943 | 0 | } |
944 | | |
945 | | #endif /* USE_RESOLV_THREADED */ |