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