Coverage Report

Created: 2026-07-16 06:10

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/curl/lib/cf-dns.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
#include "urldata.h"
27
#include "curl_addrinfo.h"
28
#include "cfilters.h"
29
#include "connect.h"
30
#include "dnscache.h"
31
#include "httpsrr.h"
32
#include "curl_trc.h"
33
#include "progress.h"
34
#include "url.h"
35
#include "cf-dns.h"
36
37
38
struct cf_dns_ctx {
39
  struct Curl_dns_entry *dns;
40
  struct Curl_peer *peer;
41
  CURLcode resolv_result;
42
  uint32_t resolv_id;
43
  uint8_t dns_queries;
44
  uint8_t transport;
45
  BIT(started);
46
  BIT(announced);
47
  BIT(complete_resolve);
48
  BIT(for_proxy);
49
};
50
51
static struct cf_dns_ctx *cf_dns_ctx_create(struct Curl_easy *data,
52
                                            struct Curl_peer *peer,
53
                                            uint8_t dns_queries,
54
                                            uint8_t transport,
55
                                            bool for_proxy,
56
                                            bool complete_resolve)
57
0
{
58
0
  struct cf_dns_ctx *ctx;
59
60
0
  ctx = curlx_calloc(1, sizeof(*ctx));
61
0
  if(!ctx)
62
0
    return NULL;
63
64
0
  Curl_peer_link(&ctx->peer, peer);
65
0
  ctx->dns_queries = dns_queries;
66
0
  ctx->transport = transport;
67
0
  ctx->for_proxy = for_proxy;
68
0
  ctx->complete_resolve = complete_resolve;
69
70
0
  CURL_TRC_DNS(data, "created DNS filter for %s:%u, transport=%x, queries=%x",
71
0
               peer->hostname, peer->port, ctx->transport, ctx->dns_queries);
72
0
  return ctx;
73
0
}
74
75
static void cf_dns_ctx_destroy(struct Curl_easy *data,
76
                               struct cf_dns_ctx *ctx)
77
0
{
78
0
  if(ctx) {
79
0
    Curl_peer_unlink(&ctx->peer);
80
0
    Curl_dns_entry_unlink(data, &ctx->dns);
81
0
    curlx_free(ctx);
82
0
  }
83
0
}
84
85
#ifdef CURLVERBOSE
86
static void cf_dns_report_addr(struct Curl_easy *data,
87
                               struct dynbuf *tmp,
88
                               const char *label,
89
                               int ai_family,
90
                               const struct Curl_addrinfo *ai)
91
0
{
92
0
  char buf[MAX_IPADR_LEN];
93
0
  const char *sep = "";
94
0
  CURLcode result;
95
96
0
  curlx_dyn_reset(tmp);
97
0
  for(; ai; ai = ai->ai_next) {
98
0
    if(ai->ai_family == ai_family) {
99
0
      Curl_printable_address(ai, buf, sizeof(buf));
100
0
      result = curlx_dyn_addf(tmp, "%s%s", sep, buf);
101
0
      if(result) {
102
0
        infof(data, "too many IP, cannot show");
103
0
        return;
104
0
      }
105
0
      sep = ", ";
106
0
    }
107
0
  }
108
109
0
  infof(data, "%s%s", label,
110
0
        (curlx_dyn_len(tmp) ? curlx_dyn_ptr(tmp) : "(none)"));
111
0
}
112
113
static void cf_dns_report(struct Curl_cfilter *cf,
114
                          struct Curl_easy *data,
115
                          struct Curl_dns_entry *dns)
116
0
{
117
0
  struct cf_dns_ctx *ctx = cf->ctx;
118
0
  struct dynbuf tmp;
119
120
0
  if(!Curl_trc_is_verbose(data) ||
121
     /* ignore no name or numerical IP addresses */
122
0
     !dns->hostname[0] || Curl_host_is_ipnum(dns->hostname))
123
0
    return;
124
125
0
  if(ctx->peer->unix_socket) {
126
0
#ifdef USE_UNIX_SOCKETS
127
0
    CURL_TRC_CF(data, cf, "resolved unix://%s", ctx->peer->hostname);
128
#else
129
    DEBUGASSERT(0);
130
#endif
131
0
  }
132
0
  else {
133
0
    curlx_dyn_init(&tmp, 1024);
134
0
    infof(data, "Host %s:%u was resolved.", dns->hostname, dns->port);
135
0
#ifdef CURLRES_IPV6
136
0
    cf_dns_report_addr(data, &tmp, "IPv6: ", AF_INET6, dns->addr);
137
0
#endif
138
0
    cf_dns_report_addr(data, &tmp, "IPv4: ", AF_INET, dns->addr);
139
#ifdef USE_HTTPSRR
140
    if(!dns->hinfo)
141
      infof(data, "HTTPS-RR: -");
142
    else if(!Curl_httpsrr_applicable(data, dns->hinfo))
143
      infof(data, "HTTPS-RR: not applicable");
144
    else {
145
      CURLcode result = Curl_httpsrr_print(&tmp, dns->hinfo);
146
      if(!result)
147
        infof(data, "HTTPS-RR: %s", curlx_dyn_ptr(&tmp));
148
      else
149
        infof(data, "Error printing HTTPS-RR information");
150
    }
151
#endif
152
0
    curlx_dyn_free(&tmp);
153
0
  }
154
0
}
155
#else
156
#define cf_dns_report(x, y, z) Curl_nop_stmt
157
#endif
158
159
/*************************************************************
160
 * Resolve the address of the server or proxy
161
 *************************************************************/
162
static CURLcode cf_dns_start(struct Curl_cfilter *cf,
163
                             struct Curl_easy *data,
164
                             struct Curl_dns_entry **pdns)
165
0
{
166
0
  struct cf_dns_ctx *ctx = cf->ctx;
167
0
  timediff_t timeout_ms = Curl_timeleft_ms(data);
168
0
  CURLcode result;
169
170
0
  *pdns = NULL;
171
172
0
  CURL_TRC_CF(data, cf, "cf_dns_start %s %s:%u",
173
0
              ctx->peer->unix_socket ? "unix-domain-socket" : "host",
174
0
              ctx->peer->hostname, ctx->peer->port);
175
0
  if(ctx->peer->unix_socket)
176
0
    ctx->dns_queries = 0;
177
0
  else if(Curl_is_ipv4addr(ctx->peer->hostname))
178
0
    ctx->dns_queries |= CURL_DNSQ_A;
179
0
#ifdef USE_IPV6
180
0
  else if(ctx->peer->ipv6)
181
0
    ctx->dns_queries |= CURL_DNSQ_AAAA;
182
0
#endif
183
184
0
  result = Curl_resolv(data, ctx->peer, ctx->dns_queries, ctx->transport,
185
0
                       (bool)ctx->for_proxy, timeout_ms,
186
0
                       &ctx->resolv_id, pdns);
187
0
  DEBUGASSERT(!result || !*pdns);
188
0
  if(!result) { /* resolved right away, either sync or from dnscache */
189
0
    DEBUGASSERT(*pdns);
190
0
    return CURLE_OK;
191
0
  }
192
0
  else if(result == CURLE_AGAIN) { /* async resolv in progress */
193
0
    return CURLE_OK;
194
0
  }
195
0
  else if(result == CURLE_OPERATION_TIMEDOUT) { /* took too long */
196
0
    failf(data, "Failed to resolve '%s' with timeout after %"
197
0
          FMT_TIMEDIFF_T " ms", ctx->peer->hostname,
198
0
          curlx_ptimediff_ms(Curl_pgrs_now(data),
199
0
                             &data->progress.t_startsingle));
200
0
    return CURLE_OPERATION_TIMEDOUT;
201
0
  }
202
0
  else {
203
0
    DEBUGASSERT(result);
204
0
    failf(data, "Could not resolve: %s", ctx->peer->hostname);
205
0
    return result;
206
0
  }
207
0
}
208
209
0
#define CURL_HEV3_RESOLVE_DELAY_MS    50
210
211
static bool cf_dns_ready_to_connect(struct Curl_cfilter *cf,
212
                                    struct Curl_easy *data)
213
0
{
214
0
  struct cf_dns_ctx *ctx = cf->ctx;
215
216
0
  if(ctx->resolv_result)
217
0
    return TRUE;
218
0
  else if(ctx->dns)
219
0
    return TRUE;
220
0
#ifdef USE_CURL_ASYNC
221
0
  else {
222
    /* We want AAAA answer as we prefer IPv6. If a sub-filter desires
223
    * HTTPS-RR, we check for that query as well. */
224
0
    uint8_t wanted_answers = CURL_DNSQ_AAAA;
225
0
    if(Curl_conn_cf_wants_httpsrr(cf, data))
226
0
      wanted_answers |= CURL_DNSQ_HTTPS;
227
228
    /* Note: if a query was never started, it is considered to have
229
     * an answer (e.g. a negative one). */
230
0
    if(Curl_resolv_has_answers(data, ctx->resolv_id, wanted_answers))
231
0
      return TRUE;
232
    /* If the wanted answers are not available after a delay,
233
     * we let the connect attempts start anyway. */
234
0
    return Curl_resolv_elapsed_ms(data, ctx->resolv_id) >=
235
0
           CURL_HEV3_RESOLVE_DELAY_MS;
236
0
  }
237
#else
238
  (void)data;
239
  DEBUGASSERT(0); /* We should not come here */
240
  return FALSE;
241
#endif /* USE_CURL_ASYNC */
242
0
}
243
244
static CURLcode cf_dns_connect(struct Curl_cfilter *cf,
245
                               struct Curl_easy *data,
246
                               bool *done)
247
0
{
248
0
  struct cf_dns_ctx *ctx = cf->ctx;
249
250
0
  if(cf->connected) {
251
0
    *done = TRUE;
252
0
    return CURLE_OK;
253
0
  }
254
255
0
  *done = FALSE;
256
0
  if(!ctx->started) {
257
0
    ctx->started = TRUE;
258
0
    ctx->resolv_result = cf_dns_start(cf, data, &ctx->dns);
259
0
  }
260
261
0
  if(!ctx->dns && !ctx->resolv_result) {
262
0
    ctx->resolv_result =
263
0
      Curl_resolv_take_result(data, ctx->resolv_id, &ctx->dns);
264
0
  }
265
266
0
  if(ctx->resolv_result) {
267
0
    CURL_TRC_CF(data, cf, "error resolving: %d", (int)ctx->resolv_result);
268
0
    return ctx->resolv_result;
269
0
  }
270
271
0
  if(ctx->dns && !ctx->announced) {
272
0
    ctx->announced = TRUE;
273
0
    if(cf->sockindex == FIRSTSOCKET) {
274
0
      cf->conn->bits.dns_resolved = TRUE;
275
0
      Curl_pgrsTime(data, TIMER_NAMELOOKUP);
276
0
    }
277
0
    cf_dns_report(cf, data, ctx->dns);
278
0
  }
279
280
0
  if(!cf_dns_ready_to_connect(cf, data)) {
281
0
    return CURLE_OK;
282
0
  }
283
284
0
  if(cf->next && !cf->next->connected) {
285
0
    bool sub_done;
286
0
    CURLcode result = Curl_conn_cf_connect(cf->next, data, &sub_done);
287
0
    if(result || !sub_done)
288
0
      return result;
289
0
    DEBUGASSERT(sub_done);
290
0
  }
291
292
  /* sub filter chain is connected */
293
0
  CURL_TRC_CF(data, cf, "connected filter chain below");
294
0
  if(ctx->complete_resolve && !ctx->dns && !ctx->resolv_result) {
295
    /* This filter only connects when it has resolved everything. */
296
0
    CURL_TRC_CF(data, cf, "delay connect until resolve complete");
297
0
    return CURLE_OK;
298
0
  }
299
0
  *done = TRUE;
300
0
  cf->connected = TRUE;
301
0
  Curl_resolv_destroy(data, ctx->resolv_id);
302
0
  return CURLE_OK;
303
0
}
304
305
static void cf_dns_destroy(struct Curl_cfilter *cf, struct Curl_easy *data)
306
0
{
307
0
  struct cf_dns_ctx *ctx = cf->ctx;
308
309
0
  CURL_TRC_CF(data, cf, "destroy");
310
0
  cf_dns_ctx_destroy(data, ctx);
311
0
}
312
313
static CURLcode cf_dns_adjust_pollset(struct Curl_cfilter *cf,
314
                                      struct Curl_easy *data,
315
                                      struct easy_pollset *ps)
316
0
{
317
0
#ifdef USE_CURL_ASYNC
318
0
  if(!cf->connected)
319
0
    return Curl_resolv_pollset(data, ps);
320
#else
321
  (void)cf;
322
  (void)data;
323
  (void)ps;
324
#endif
325
0
  return CURLE_OK;
326
0
}
327
328
static CURLcode cf_dns_cntrl(struct Curl_cfilter *cf,
329
                             struct Curl_easy *data,
330
                             int event, int arg1, void *arg2)
331
0
{
332
0
  struct cf_dns_ctx *ctx = cf->ctx;
333
0
  CURLcode result = CURLE_OK;
334
335
0
  (void)arg1;
336
0
  (void)arg2;
337
0
  switch(event) {
338
0
  case CF_CTRL_DATA_DONE:
339
0
    if(ctx->dns) {
340
      /* Should only come here when the connect attempt failed and
341
       * `data` is giving up on it. On a successful connect, we already
342
       * unlinked the DNS entry. */
343
0
      Curl_dns_entry_unlink(data, &ctx->dns);
344
0
    }
345
0
    break;
346
0
  default:
347
0
    break;
348
0
  }
349
0
  return result;
350
0
}
351
352
struct Curl_cftype Curl_cft_dns = {
353
  "DNS",
354
  CF_TYPE_SETUP,
355
  CURL_LOG_LVL_NONE,
356
  cf_dns_destroy,
357
  cf_dns_connect,
358
  Curl_cf_def_shutdown,
359
  cf_dns_adjust_pollset,
360
  Curl_cf_def_data_pending,
361
  Curl_cf_def_send,
362
  Curl_cf_def_recv,
363
  cf_dns_cntrl,
364
  Curl_cf_def_conn_is_alive,
365
  Curl_cf_def_conn_keep_alive,
366
  Curl_cf_def_query,
367
};
368
369
static CURLcode cf_dns_create(struct Curl_cfilter **pcf,
370
                              struct Curl_easy *data,
371
                              struct Curl_peer *peer,
372
                              uint8_t dns_queries,
373
                              uint8_t transport,
374
                              bool for_proxy,
375
                              bool complete_resolve)
376
0
{
377
0
  struct Curl_cfilter *cf = NULL;
378
0
  struct cf_dns_ctx *ctx;
379
0
  CURLcode result = CURLE_OK;
380
381
0
  (void)data;
382
0
  ctx = cf_dns_ctx_create(data, peer, dns_queries, transport,
383
0
                          for_proxy, complete_resolve);
384
0
  if(!ctx) {
385
0
    result = CURLE_OUT_OF_MEMORY;
386
0
    goto out;
387
0
  }
388
389
0
  result = Curl_cf_create(&cf, &Curl_cft_dns, ctx);
390
391
0
out:
392
0
  *pcf = result ? NULL : cf;
393
0
  if(result)
394
0
    cf_dns_ctx_destroy(data, ctx);
395
0
  return result;
396
0
}
397
398
/* Adds a "resolv" filter at the top of the connection's filter chain.
399
 * The filter will resolve the peer on the first connect attempt. */
400
CURLcode Curl_cf_dns_add(struct Curl_easy *data,
401
                         struct connectdata *conn,
402
                         int sockindex,
403
                         struct Curl_peer *peer,
404
                         uint8_t dns_queries,
405
                         uint8_t transport)
406
0
{
407
0
  struct Curl_cfilter *cf = NULL;
408
0
  bool for_proxy = FALSE;
409
0
  CURLcode result;
410
411
0
  if(!peer)
412
0
    return CURLE_FAILED_INIT;
413
0
#ifndef CURL_DISABLE_PROXY
414
0
  for_proxy = (peer == conn->socks_proxy.peer) ||
415
0
              (peer == conn->http_proxy.peer);
416
0
#endif
417
418
0
  result = cf_dns_create(&cf, data, peer, dns_queries, transport,
419
0
                         for_proxy, FALSE);
420
0
  if(result)
421
0
    goto out;
422
0
  Curl_conn_cf_add(data, conn, sockindex, cf);
423
0
out:
424
0
  return result;
425
0
}
426
427
/* Insert a new "resolv" filter directly after `cf`. It will
428
 * start a DNS resolve for the given peer on the
429
 * first connect attempt.
430
 * See socks.c on how this is used to make a non-blocking DNS
431
 * resolve during connect.
432
 */
433
CURLcode Curl_cf_dns_insert_after(struct Curl_cfilter *cf_at,
434
                                  struct Curl_easy *data,
435
                                  uint8_t dns_queries,
436
                                  struct Curl_peer *peer,
437
                                  uint8_t transport,
438
                                  bool complete_resolve)
439
0
{
440
0
  struct Curl_cfilter *cf;
441
0
  CURLcode result;
442
443
0
  result = cf_dns_create(&cf, data, peer, dns_queries, transport,
444
0
                         FALSE, complete_resolve);
445
0
  if(result)
446
0
    return result;
447
448
0
  Curl_conn_cf_insert_after(cf_at, cf);
449
0
  return CURLE_OK;
450
0
}
451
452
/* Return the resolv result from the first "resolv" filter, starting
453
 * the given filter `cf` downwards.
454
 */
455
static CURLcode cf_dns_result(struct Curl_cfilter *cf,
456
                              struct Curl_peer *peer)
457
0
{
458
0
  for(; cf; cf = cf->next) {
459
0
    if(cf->cft == &Curl_cft_dns) {
460
0
      struct cf_dns_ctx *ctx = cf->ctx;
461
0
      if(Curl_peer_same_destination(ctx->peer, peer)) {
462
0
        if(ctx->dns || ctx->resolv_result)
463
0
          return ctx->resolv_result;
464
0
        return CURLE_AGAIN;
465
0
      }
466
0
      return CURLE_OK; /* ok, but no results */
467
0
    }
468
0
  }
469
0
  return CURLE_FAILED_INIT;
470
0
}
471
472
/* Return the result of the DNS resolution for peer. Searches for a "resolv"
473
 * filter from the top of the filter chain down. Returns
474
 * - CURLE_AGAIN when not done yet
475
 * - CURLE_OK when DNS was successfully resolved
476
 * - CURLR_FAILED_INIT when no resolv filter was found
477
 * - error returned by the DNS resolv
478
 */
479
CURLcode Curl_conn_dns_result(struct connectdata *conn,
480
                              int sockindex,
481
                              struct Curl_peer *peer)
482
0
{
483
0
  return cf_dns_result(conn->cfilter[sockindex], peer);
484
0
}
485
486
static const struct Curl_addrinfo *cf_dns_get_nth_ai(
487
  struct Curl_cfilter *cf,
488
  const struct Curl_addrinfo *ai,
489
  int ai_family, unsigned int index)
490
0
{
491
0
  struct cf_dns_ctx *ctx = cf->ctx;
492
0
  unsigned int i = 0;
493
494
0
  if((ai_family == AF_INET) && !(ctx->dns_queries & CURL_DNSQ_A))
495
0
    return NULL;
496
0
#ifdef USE_IPV6
497
0
  if((ai_family == AF_INET6) && !(ctx->dns_queries & CURL_DNSQ_AAAA))
498
0
    return NULL;
499
0
#endif
500
0
  for(i = 0; ai; ai = ai->ai_next) {
501
0
    if(ai->ai_family == ai_family) {
502
0
      if(i == index)
503
0
        return ai;
504
0
      ++i;
505
0
    }
506
0
  }
507
0
  return NULL;
508
0
}
509
510
/* Return the addrinfo at `index` for the given `family` from the
511
 * first "resolve" filter underneath `cf`. If the DNS resolving is
512
 * not done yet or if no address for the family exists, returns NULL.
513
 */
514
const struct Curl_addrinfo *Curl_cf_dns_get_ai(struct Curl_cfilter *cf,
515
                                               struct Curl_easy *data,
516
                                               struct Curl_peer *peer,
517
                                               int ai_family,
518
                                               unsigned int index)
519
0
{
520
0
  (void)data;
521
0
  for(; cf; cf = cf->next) {
522
0
    if(cf->cft == &Curl_cft_dns) {
523
0
      struct cf_dns_ctx *ctx = cf->ctx;
524
0
      if(Curl_peer_same_destination(ctx->peer, peer)) {
525
0
        if(ctx->resolv_result)
526
0
          return NULL;
527
0
        else if(ctx->dns)
528
0
          return cf_dns_get_nth_ai(cf, ctx->dns->addr, ai_family, index);
529
0
        else
530
0
          return Curl_resolv_get_ai(data, ctx->resolv_id, ai_family, index);
531
0
      }
532
0
    }
533
0
  }
534
0
  return NULL;
535
0
}
536
537
/* Return the addrinfo at `index` for the given `family` from the
538
 * first "resolve" filter at the connection. If the DNS resolving is
539
 * not done yet or if no address for the family exists, returns NULL.
540
 */
541
const struct Curl_addrinfo *Curl_conn_dns_get_ai(struct Curl_easy *data,
542
                                                 struct Curl_peer *peer,
543
                                                 int sockindex,
544
                                                 int ai_family,
545
                                                 unsigned int index)
546
0
{
547
0
  struct connectdata *conn = data->conn;
548
0
  return Curl_cf_dns_get_ai(conn->cfilter[sockindex], data, peer,
549
0
                            ai_family, index);
550
0
}
551
552
#ifdef USE_HTTPSRR
553
/* Return the HTTPS-RR info from the first "resolve" filter at the
554
 * connection. If the DNS resolving is not done yet or if there
555
 * is no HTTPS-RR info, returns NULL.
556
 */
557
const struct Curl_https_rrinfo *
558
Curl_conn_dns_get_https(struct Curl_easy *data,
559
                        int sockindex,
560
                        struct Curl_peer *peer)
561
{
562
  struct Curl_cfilter *cf = data->conn->cfilter[sockindex];
563
  for(; cf; cf = cf->next) {
564
    if(cf->cft == &Curl_cft_dns) {
565
      struct cf_dns_ctx *ctx = cf->ctx;
566
      if(Curl_peer_same_destination(ctx->peer, peer)) {
567
        if(ctx->dns)
568
          return ctx->dns->hinfo;
569
        else
570
          return Curl_resolv_get_https(data, ctx->resolv_id);
571
      }
572
    }
573
  }
574
  return NULL;
575
}
576
577
bool Curl_conn_dns_resolved_https(struct Curl_easy *data,
578
                                  int sockindex,
579
                                  struct Curl_peer *peer)
580
{
581
  struct Curl_cfilter *cf = data->conn->cfilter[sockindex];
582
  for(; cf; cf = cf->next) {
583
    if(cf->cft == &Curl_cft_dns) {
584
      struct cf_dns_ctx *ctx = cf->ctx;
585
      if(Curl_peer_same_destination(ctx->peer, peer)) {
586
        if(ctx->dns)
587
          return TRUE;
588
        else
589
          return Curl_resolv_knows_https(data, ctx->resolv_id);
590
      }
591
    }
592
  }
593
  return TRUE;
594
}
595
596
#endif /* USE_HTTPSRR */