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