Coverage Report

Created: 2026-09-01 06:58

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/curl/lib/vdns/dnscache.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
#ifdef HAVE_NETINET_IN_H
27
#include <netinet/in.h>
28
#endif
29
#ifdef HAVE_NETINET_IN6_H
30
#include <netinet/in6.h>
31
#endif
32
#ifdef HAVE_NETDB_H
33
#include <netdb.h>
34
#endif
35
#ifdef HAVE_ARPA_INET_H
36
#include <arpa/inet.h>
37
#endif
38
#ifdef __VMS
39
#include <in.h>
40
#include <inet.h>
41
#endif
42
43
#include "urldata.h"
44
#include "curl_addrinfo.h"
45
#include "curl_share.h"
46
#include "curl_trc.h"
47
#include "hash.h"
48
#include "progress.h"
49
#include "rand.h"
50
#include "strcase.h"
51
#include "vdns/dnscache.h"
52
#include "vdns/hostip.h"
53
#include "vdns/httpsrr.h"
54
#include "curlx/inet_ntop.h"
55
#include "curlx/inet_pton.h"
56
#include "curlx/strcopy.h"
57
#include "curlx/strparse.h"
58
59
#define MAX_HOSTCACHE_LEN (255 + 7) /* max FQDN + colon + port number + zero */
60
61
17.3k
#define MAX_DNS_CACHE_SIZE 29999
62
63
struct dnsc_id {
64
  struct Curl_str name;
65
  uint16_t port;
66
  char type;
67
};
68
69
static void dnsc_peer2id(struct dnsc_id *pid, char type,
70
                         struct Curl_peer *peer)
71
33.9k
{
72
33.9k
  curlx_str_assign(&pid->name, peer->hostname, strlen(peer->hostname));
73
33.9k
  pid->port = peer->port;
74
33.9k
  pid->type = type;
75
33.9k
}
76
77
static void dnsc_str2id(struct dnsc_id *pid, char type,
78
                        struct Curl_str *name, uint16_t port)
79
16.3k
{
80
16.3k
  pid->name = *name;
81
16.3k
  pid->port = port;
82
16.3k
  pid->type = type;
83
16.3k
}
84
85
struct dnsc_key {
86
  char data[MAX_HOSTCACHE_LEN];
87
  size_t len;
88
};
89
90
/*
91
 * Create a hostcache id string for the provided host + port, to be used by
92
 * the DNS caching. Without alloc. Return length of the id string.
93
 */
94
static void dnsc_id2key(struct dnsc_key *key, struct dnsc_id *id)
95
33.8k
{
96
33.8k
  size_t namelen = curlx_strlen(&id->name);
97
33.8k
  if(namelen > (sizeof(key->data) - 8))
98
0
    namelen = sizeof(key->data) - 8;
99
  /* store and lower case the name */
100
33.8k
  key->data[0] = id->type;
101
33.8k
  Curl_strntolower(key->data + 1, curlx_str(&id->name), namelen);
102
  /* include the terminating 0 in key length */
103
33.8k
  key->len = namelen + 2 +
104
33.8k
             curl_msnprintf(&key->data[namelen + 1], 7, ":%u", id->port);
105
33.8k
}
106
107
static void dnscache_entry_free(struct Curl_dns_entry *dns)
108
16.3k
{
109
16.3k
  Curl_freeaddrinfo(dns->addr);
110
#ifdef USE_HTTPSRR
111
  Curl_httpsrr_destroy(dns->hinfo);
112
#endif
113
16.3k
  curlx_free(dns);
114
16.3k
}
115
116
struct dnscache_prune_data {
117
  struct curltime now;
118
  timediff_t oldest_ms; /* oldest time in cache not pruned. */
119
  timediff_t max_age_ms;
120
};
121
122
/*
123
 * This function is set as a callback to be called for every entry in the DNS
124
 * cache when we want to prune old unused entries.
125
 *
126
 * Returning non-zero means remove the entry, return 0 to keep it in the
127
 * cache.
128
 */
129
static int dnscache_entry_is_stale(void *datap, void *hc)
130
18.8k
{
131
18.8k
  struct dnscache_prune_data *prune = (struct dnscache_prune_data *)datap;
132
18.8k
  struct Curl_dns_entry *dns = (struct Curl_dns_entry *)hc;
133
134
18.8k
  if(dns->timestamp.tv_sec || dns->timestamp.tv_usec) {
135
    /* get age in milliseconds */
136
18.8k
    timediff_t age = curlx_ptimediff_ms(&prune->now, &dns->timestamp);
137
18.8k
    if(!dns->addr)
138
0
      age *= 2; /* negative entries age twice as fast */
139
18.8k
    if(age >= prune->max_age_ms)
140
6
      return TRUE;
141
18.8k
    if(age > prune->oldest_ms)
142
1.79k
      prune->oldest_ms = age;
143
18.8k
  }
144
18.8k
  return FALSE;
145
18.8k
}
146
147
/*
148
 * Prune the DNS cache. This assumes that a lock has already been taken.
149
 * Returns the 'age' of the oldest still kept entry - in milliseconds.
150
 */
151
static timediff_t dnscache_prune(struct Curl_hash *hostcache,
152
                                 timediff_t cache_timeout_ms,
153
                                 struct curltime now)
154
17.3k
{
155
17.3k
  struct dnscache_prune_data user;
156
157
17.3k
  user.max_age_ms = cache_timeout_ms;
158
17.3k
  user.now = now;
159
17.3k
  user.oldest_ms = 0;
160
161
17.3k
  Curl_hash_clean_with_criterium(hostcache,
162
17.3k
                                 (void *)&user,
163
17.3k
                                 dnscache_entry_is_stale);
164
165
17.3k
  return user.oldest_ms;
166
17.3k
}
167
168
static struct Curl_dnscache *dnscache_get(struct Curl_easy *data)
169
68.8k
{
170
68.8k
  if(data->share && data->share->specifier & (1 << CURL_LOCK_DATA_DNS))
171
0
    return &data->share->dnscache;
172
68.8k
  if(data->multi)
173
68.8k
    return &data->multi->dnscache;
174
0
  return NULL;
175
68.8k
}
176
177
static void dnscache_lock(struct Curl_easy *data,
178
                          struct Curl_dnscache *dnscache)
179
68.8k
{
180
68.8k
  if(data->share && dnscache == &data->share->dnscache)
181
0
    Curl_share_lock(data, CURL_LOCK_DATA_DNS, CURL_LOCK_ACCESS_SINGLE);
182
68.8k
}
183
184
static void dnscache_unlock(struct Curl_easy *data,
185
                            struct Curl_dnscache *dnscache)
186
68.8k
{
187
68.8k
  if(data->share && dnscache == &data->share->dnscache)
188
0
    Curl_share_unlock(data, CURL_LOCK_DATA_DNS);
189
68.8k
}
190
191
/*
192
 * Library-wide function for pruning the DNS cache. This function takes and
193
 * returns the appropriate locks.
194
 */
195
void Curl_dnscache_prune(struct Curl_easy *data)
196
17.3k
{
197
17.3k
  struct Curl_dnscache *dnscache = dnscache_get(data);
198
  /* the timeout may be set -1 (forever) */
199
17.3k
  timediff_t timeout_ms = data->set.dns_cache_timeout_ms;
200
201
17.3k
  if(!dnscache || (timeout_ms == -1))
202
    /* NULL hostcache means we cannot do it */
203
0
    return;
204
205
17.3k
  dnscache_lock(data, dnscache);
206
207
17.3k
  do {
208
    /* Remove outdated and unused entries from the hostcache */
209
17.3k
    timediff_t oldest_ms =
210
17.3k
      dnscache_prune(&dnscache->entries, timeout_ms, *Curl_pgrs_now(data));
211
212
17.3k
    if(Curl_hash_count(&dnscache->entries) > MAX_DNS_CACHE_SIZE)
213
      /* prune the ones over half this age */
214
0
      timeout_ms = oldest_ms / 2;
215
17.3k
    else
216
17.3k
      break;
217
218
    /* if the cache size is still too big, use the oldest age as new prune
219
       limit */
220
17.3k
  } while(timeout_ms);
221
222
17.3k
  dnscache_unlock(data, dnscache);
223
17.3k
}
224
225
void Curl_dnscache_clear(struct Curl_easy *data)
226
0
{
227
0
  struct Curl_dnscache *dnscache = dnscache_get(data);
228
0
  if(dnscache) {
229
0
    dnscache_lock(data, dnscache);
230
0
    Curl_hash_clean(&dnscache->entries);
231
0
    dnscache_unlock(data, dnscache);
232
0
  }
233
0
}
234
235
/* lookup address, returns entry if found and not stale */
236
static CURLcode fetch_addr(struct Curl_easy *data,
237
                           struct Curl_dnscache *dnscache,
238
                           uint8_t dns_queries,
239
                           struct Curl_peer *peer,
240
                           struct Curl_dns_entry **pdns)
241
17.5k
{
242
17.5k
  struct Curl_dns_entry *dns = NULL;
243
17.5k
  struct dnsc_id id;
244
17.5k
  struct dnsc_key key;
245
17.5k
  char type = CURL_DNSQ_IS_ADDR(dns_queries) ?
246
17.5k
              CURL_DNST_ADDR : CURL_DNST_HTTPS;
247
17.5k
  CURLcode result = CURLE_OK;
248
249
17.5k
  *pdns = NULL;
250
17.5k
  if(!dnscache)
251
0
    return CURLE_OK;
252
253
17.5k
  dnsc_peer2id(&id, type, peer);
254
17.5k
  dnsc_id2key(&key, &id);
255
256
  /* See if it is already in our dns cache */
257
17.5k
  dns = Curl_hash_pick(&dnscache->entries, key.data, key.len);
258
259
  /* No entry found in cache, check if we might have a wildcard entry */
260
17.5k
  if(!dns && (type == CURL_DNST_ADDR) && data->state.wildcard_resolve) {
261
0
    struct Curl_str wildname;
262
263
0
    curlx_str_assign(&wildname, "*", 1);
264
0
    dnsc_str2id(&id, CURL_DNST_ADDR, &wildname, peer->port);
265
0
    dnsc_id2key(&key, &id);
266
267
    /* See if it is already in our dns cache */
268
0
    dns = Curl_hash_pick(&dnscache->entries, key.data, key.len);
269
0
  }
270
271
17.5k
  if(dns && (data->set.dns_cache_timeout_ms != -1)) {
272
    /* See whether the returned entry is stale. Done before we release lock */
273
1.24k
    struct dnscache_prune_data user;
274
275
1.24k
    user.now = *Curl_pgrs_now(data);
276
1.24k
    user.max_age_ms = data->set.dns_cache_timeout_ms;
277
1.24k
    user.oldest_ms = 0;
278
279
1.24k
    if(dnscache_entry_is_stale(&user, dns)) {
280
0
      infof(data, "Hostname in DNS cache was stale, zapped");
281
0
      dns = NULL; /* the memory deallocation is being handled by the hash */
282
0
      Curl_hash_delete(&dnscache->entries, key.data, key.len);
283
0
    }
284
1.24k
  }
285
286
  /* We need to cache address information and HTTPS-RR separately. */
287
17.5k
  if(dns && CURL_DNSQ_IS_ADDR(dns_queries)) {
288
1.24k
    if((uint8_t)(dns->dns_queries & dns_queries) !=
289
1.24k
       (uint8_t)(dns_queries & CURL_DNSQ_ADDR)) {
290
      /* The entry does not cover all wanted address queries, a miss. */
291
0
      dns = NULL;
292
0
    }
293
1.24k
    else if(!(dns->dns_responses & dns_queries)) {
294
      /* The entry has no responses for the wanted DNS queries. */
295
0
      CURL_TRC_DNS(data, "cache entry does not have type=%s addresses",
296
0
                   Curl_resolv_query_str(dns_queries));
297
0
      dns = NULL;
298
0
      result = CURLE_COULDNT_RESOLVE_HOST;
299
0
    }
300
1.24k
    else if(dns && !dns->addr) { /* negative entry */
301
0
      dns = NULL;
302
0
      result = CURLE_COULDNT_RESOLVE_HOST;
303
0
    }
304
1.24k
  }
305
306
17.5k
  *pdns = dns;
307
17.5k
  return result;
308
17.5k
}
309
310
/*
311
 * Curl_dnscache_get() fetches a 'Curl_dns_entry' already in the DNS cache.
312
 *
313
 * Curl_resolv() checks initially and multi_runsingle() checks each time
314
 * it discovers the handle in the state WAITRESOLVE whether the hostname
315
 * has already been resolved and the address has already been stored in
316
 * the DNS cache. This short circuits waiting for a lot of pending
317
 * lookups for the same hostname requested by different handles.
318
 *
319
 * Returns the Curl_dns_entry entry pointer or NULL if not in the cache.
320
 *
321
 * The returned data *MUST* be "released" with Curl_dns_entry_unlink() after
322
 * use, or we will leak memory!
323
 */
324
CURLcode Curl_dnscache_get(struct Curl_easy *data,
325
                           uint8_t dns_queries,
326
                           struct Curl_peer *peer,
327
                           struct Curl_dns_entry **pentry)
328
17.5k
{
329
17.5k
  struct Curl_dnscache *dnscache = dnscache_get(data);
330
17.5k
  struct Curl_dns_entry *dns = NULL;
331
17.5k
  CURLcode result = CURLE_OK;
332
333
17.5k
  dnscache_lock(data, dnscache);
334
17.5k
  result = fetch_addr(data, dnscache, dns_queries, peer, &dns);
335
17.5k
  if(!result && dns)
336
1.24k
    dns->refcount++; /* we pass out a reference */
337
16.3k
  else if(result) {
338
0
    DEBUGASSERT(!dns);
339
0
    dns = NULL;
340
0
  }
341
17.5k
  dnscache_unlock(data, dnscache);
342
343
17.5k
  CURL_TRC_DNS(data, "cache lookup %s:%u queries=%s -> %d %sfound",
344
17.5k
               peer->hostname, peer->port, Curl_resolv_query_str(dns_queries),
345
17.5k
               (int)result, dns ? "" : "not ");
346
17.5k
  *pentry = dns;
347
17.5k
  return result;
348
17.5k
}
349
350
#ifndef CURL_DISABLE_SHUFFLE_DNS
351
/*
352
 * Return # of addresses in a Curl_addrinfo struct
353
 */
354
static int num_addresses(const struct Curl_addrinfo *addr)
355
6
{
356
6
  int i = 0;
357
12
  while(addr) {
358
6
    addr = addr->ai_next;
359
6
    i++;
360
6
  }
361
6
  return i;
362
6
}
363
364
/*
365
 * dns_shuffle_addr() shuffles the order of addresses in a 'Curl_addrinfo'
366
 * struct by re-linking its linked list.
367
 *
368
 * The addr argument should be the address of a pointer to the head node of a
369
 * `Curl_addrinfo` list and it will be modified to point to the new head after
370
 * shuffling.
371
 *
372
 * Not declared static only to make it easy to use in a unit test!
373
 *
374
 * @unittest 1608
375
 */
376
UNITTEST CURLcode dns_shuffle_addr(struct Curl_easy *data,
377
                                   struct Curl_addrinfo **addr);
378
UNITTEST CURLcode dns_shuffle_addr(struct Curl_easy *data,
379
                                   struct Curl_addrinfo **addr)
380
6
{
381
6
  CURLcode result = CURLE_OK;
382
6
  const int num_addrs = num_addresses(*addr);
383
384
6
  if(num_addrs > 1) {
385
0
    struct Curl_addrinfo **nodes;
386
0
    CURL_TRC_DNS(data, "Shuffling %d addresses", num_addrs);
387
388
0
    nodes = curlx_malloc(num_addrs * sizeof(*nodes));
389
0
    if(nodes) {
390
0
      int i;
391
0
      unsigned int *rnd;
392
0
      const size_t rnd_size = num_addrs * sizeof(*rnd);
393
394
      /* build a plain array of Curl_addrinfo pointers */
395
0
      nodes[0] = *addr;
396
0
      for(i = 1; i < num_addrs; i++) {
397
0
        nodes[i] = nodes[i - 1]->ai_next;
398
0
      }
399
400
0
      rnd = curlx_malloc(rnd_size);
401
0
      if(rnd) {
402
        /* Fisher-Yates shuffle */
403
0
        if(Curl_rand(data, (unsigned char *)rnd, rnd_size) == CURLE_OK) {
404
0
          struct Curl_addrinfo *swap_tmp;
405
0
          for(i = num_addrs - 1; i > 0; i--) {
406
0
            swap_tmp = nodes[rnd[i] % (unsigned int)(i + 1)];
407
0
            nodes[rnd[i] % (unsigned int)(i + 1)] = nodes[i];
408
0
            nodes[i] = swap_tmp;
409
0
          }
410
411
          /* relink list in the new order */
412
0
          for(i = 1; i < num_addrs; i++) {
413
0
            nodes[i - 1]->ai_next = nodes[i];
414
0
          }
415
416
0
          nodes[num_addrs - 1]->ai_next = NULL;
417
0
          *addr = nodes[0];
418
0
        }
419
0
        curlx_free(rnd);
420
0
      }
421
0
      else
422
0
        result = CURLE_OUT_OF_MEMORY;
423
0
      curlx_free(nodes);
424
0
    }
425
0
    else
426
0
      result = CURLE_OUT_OF_MEMORY;
427
0
  }
428
6
  return result;
429
6
}
430
#endif
431
432
static bool dnscache_ai_has_family(struct Curl_addrinfo *ai, int ai_family)
433
32.3k
{
434
48.4k
  for(; ai; ai = ai->ai_next) {
435
32.3k
    if(ai->ai_family == ai_family)
436
16.3k
      return TRUE;
437
32.3k
  }
438
16.0k
  return FALSE;
439
32.3k
}
440
441
static struct Curl_dns_entry *dnsc_entry_create(struct Curl_easy *data,
442
                                                struct dnsc_id *pid,
443
                                                bool permanent)
444
16.3k
{
445
16.3k
  struct Curl_dns_entry *dns = NULL;
446
447
  /* Create a new cache entry, struct already has the hostname NUL */
448
16.3k
  dns = curlx_calloc(1, sizeof(struct Curl_dns_entry) +
449
16.3k
                     curlx_strlen(&pid->name));
450
16.3k
  if(!dns)
451
0
    goto out;
452
453
16.3k
  dns->refcount = 1; /* the cache has the first reference */
454
16.3k
  dns->hostlen = curlx_strlen(&pid->name);
455
16.3k
  dns->port = pid->port;
456
16.3k
  if(dns->hostlen)
457
16.3k
    memcpy(dns->hostname, curlx_str(&pid->name), dns->hostlen);
458
459
16.3k
  if(permanent) {
460
0
    dns->timestamp.tv_sec = 0; /* an entry that never goes stale */
461
0
    dns->timestamp.tv_usec = 0; /* an entry that never goes stale */
462
0
  }
463
16.3k
  else {
464
16.3k
    dns->timestamp = *Curl_pgrs_now(data);
465
16.3k
  }
466
467
16.3k
out:
468
16.3k
  return dns;
469
16.3k
}
470
471
static struct Curl_dns_entry *dnsc_entry_assign_addr(
472
  struct Curl_easy *data,
473
  struct Curl_dns_entry *dns,
474
  uint8_t dns_queries,
475
  struct Curl_addrinfo **paddr1,
476
  struct Curl_addrinfo **paddr2)
477
16.3k
{
478
16.3k
  if(!dns)
479
0
    goto out;
480
  /* only do this when this is the only reference */
481
16.3k
  DEBUGASSERT(dns->refcount == 1);
482
16.3k
  DEBUGASSERT(dns->type == CURL_DNST_INIT);
483
484
16.3k
  dns->type = CURL_DNST_ADDR;
485
  /* queries should only be about addresses */
486
16.3k
  DEBUGASSERT(!(dns_queries & ~CURL_DNSQ_ADDR));
487
16.3k
  dns->dns_queries = (dns_queries & CURL_DNSQ_ADDR);
488
489
  /* Take the given address lists into the entry */
490
16.3k
  if(paddr1 && *paddr1) {
491
16.3k
    dns->addr = *paddr1;
492
16.3k
    *paddr1 = NULL;
493
16.3k
  }
494
16.3k
  if(paddr2 && *paddr2) {
495
0
    struct Curl_addrinfo **phead = &dns->addr;
496
0
    while(*phead)
497
0
      phead = &(*phead)->ai_next;
498
0
    *phead = *paddr2;
499
0
    *paddr2 = NULL;
500
0
  }
501
502
16.3k
  if((dns_queries & CURL_DNSQ_A) &&
503
16.3k
     dnscache_ai_has_family(dns->addr, PF_INET))
504
16.3k
    dns->dns_responses |= CURL_DNSQ_A;
505
506
16.3k
#ifdef USE_IPV6
507
16.3k
  if((dns_queries & CURL_DNSQ_AAAA) &&
508
16.0k
     dnscache_ai_has_family(dns->addr, PF_INET6))
509
0
    dns->dns_responses |= CURL_DNSQ_AAAA;
510
16.3k
#endif /* USE_IPV6 */
511
512
16.3k
#ifndef CURL_DISABLE_SHUFFLE_DNS
513
  /* shuffle addresses if requested */
514
16.3k
  if(data->set.dns_shuffle_addresses && dns->addr) {
515
6
    CURLcode result = dns_shuffle_addr(data, &dns->addr);
516
6
    if(result) {
517
      /* free without lock, we are the sole owner */
518
0
      dnscache_entry_free(dns);
519
0
      dns = NULL;
520
0
      goto out;
521
0
    }
522
6
  }
523
#else
524
  (void)data;
525
#endif
526
527
16.3k
out:
528
16.3k
  if(paddr1 && *paddr1) {
529
0
    Curl_freeaddrinfo(*paddr1);
530
0
    *paddr1 = NULL;
531
0
  }
532
16.3k
  if(paddr2 && *paddr2) {
533
0
    Curl_freeaddrinfo(*paddr2);
534
0
    *paddr2 = NULL;
535
0
  }
536
16.3k
  return dns;
537
16.3k
}
538
539
struct Curl_dns_entry *Curl_dnsc_mk_addr(struct Curl_easy *data,
540
                                         uint8_t dns_queries,
541
                                         struct Curl_addrinfo **paddr,
542
                                         struct Curl_peer *peer)
543
16.3k
{
544
16.3k
  struct dnsc_id id;
545
16.3k
  struct Curl_dns_entry *dns;
546
547
16.3k
  dnsc_peer2id(&id, CURL_DNST_ADDR, peer);
548
16.3k
  dns = dnsc_entry_create(data, &id, FALSE);
549
16.3k
  dns = dnsc_entry_assign_addr(data, dns, dns_queries, paddr, NULL);
550
16.3k
  return dns;
551
16.3k
}
552
553
struct Curl_dns_entry *Curl_dnsc_mk_addr2(struct Curl_easy *data,
554
                                          uint8_t dns_queries,
555
                                          struct Curl_addrinfo **paddr1,
556
                                          struct Curl_addrinfo **paddr2,
557
                                          struct Curl_peer *peer)
558
0
{
559
0
  struct dnsc_id id;
560
0
  struct Curl_dns_entry *dns;
561
562
0
  dnsc_peer2id(&id, CURL_DNST_ADDR, peer);
563
0
  dns = dnsc_entry_create(data, &id, FALSE);
564
0
  dns = dnsc_entry_assign_addr(data, dns, dns_queries, paddr1, paddr2);
565
0
  return dns;
566
0
}
567
568
#ifdef USE_HTTPSRR
569
static struct Curl_dns_entry *dnsc_entry_assign_https(
570
  struct Curl_dns_entry *dns,
571
  struct Curl_https_rrinfo **phinfo)
572
{
573
  if(!dns)
574
    goto out;
575
  /* only do this when this is the only reference */
576
  DEBUGASSERT(dns->refcount == 1);
577
  DEBUGASSERT(dns->type == CURL_DNST_INIT);
578
579
  if(dns->hinfo) {
580
    Curl_httpsrr_destroy(dns->hinfo);
581
    dns->hinfo = NULL;
582
  }
583
  dns->type = CURL_DNST_HTTPS;
584
  dns->dns_responses = dns->dns_queries = CURL_DNSQ_HTTPS;
585
  if(phinfo) {
586
    dns->hinfo = *phinfo;
587
    *phinfo = NULL;
588
  }
589
out:
590
  if(phinfo && *phinfo) {
591
    Curl_httpsrr_destroy(*phinfo);
592
    *phinfo = NULL;
593
  }
594
  return dns;
595
}
596
597
struct Curl_dns_entry *Curl_dnsc_mk_https(struct Curl_easy *data,
598
                                          struct Curl_https_rrinfo **phinfo,
599
                                          struct Curl_peer *peer)
600
{
601
  struct dnsc_id id;
602
  struct Curl_dns_entry *dns;
603
604
  dnsc_peer2id(&id, CURL_DNST_HTTPS, peer);
605
  dns = dnsc_entry_create(data, &id, FALSE);
606
  dns = dnsc_entry_assign_https(dns, phinfo);
607
  return dns;
608
}
609
610
static struct Curl_dns_entry *dnsc_add_https(struct Curl_easy *data,
611
                                             struct Curl_dnscache *dnscache,
612
                                             struct Curl_https_rrinfo **phinfo,
613
                                             struct dnsc_id *id,
614
                                             bool permanent)
615
{
616
  struct Curl_dns_entry *dns, *dns2;
617
  struct dnsc_key key;
618
619
  dns = dnsc_entry_create(data, id, permanent);
620
  dns = dnsc_entry_assign_https(dns, phinfo);
621
  if(!dns)
622
    return NULL;
623
624
  /* Store the resolved data in our DNS cache. */
625
  dnsc_id2key(&key, id);
626
  dns2 = Curl_hash_add(&dnscache->entries, key.data, key.len, (void *)dns);
627
  if(!dns2) {
628
    dnscache_entry_free(dns);
629
    return NULL;
630
  }
631
632
  dns = dns2;
633
  dns->refcount++; /* mark entry as in-use */
634
  return dns;
635
}
636
#endif /* USE_HTTPSRR */
637
638
static struct Curl_dns_entry *dnsc_add_addr(struct Curl_easy *data,
639
                                            struct Curl_dnscache *dnscache,
640
                                            uint8_t dns_queries,
641
                                            struct Curl_addrinfo **paddr,
642
                                            struct dnsc_id *id,
643
                                            struct dnsc_key *key,
644
                                            bool permanent)
645
0
{
646
0
  struct Curl_dns_entry *dns;
647
0
  struct Curl_dns_entry *dns2;
648
649
0
  dns = dnsc_entry_create(data, id, permanent);
650
0
  dns = dnsc_entry_assign_addr(data, dns, dns_queries, paddr, NULL);
651
0
  if(!dns)
652
0
    return NULL;
653
654
  /* Store the resolved data in our DNS cache. */
655
0
  dns2 = Curl_hash_add(&dnscache->entries, key->data, key->len, (void *)dns);
656
0
  if(!dns2) {
657
0
    dnscache_entry_free(dns);
658
0
    return NULL;
659
0
  }
660
661
0
  dns = dns2;
662
0
  dns->refcount++; /* mark entry as in-use */
663
0
  return dns;
664
0
}
665
666
static struct Curl_dns_entry *dnsc_add_peer_addr(
667
  struct Curl_easy *data,
668
  struct Curl_dnscache *dnscache,
669
  uint8_t dns_queries,
670
  struct Curl_addrinfo **paddr,
671
  struct dnsc_id *id,
672
  bool permanent)
673
0
{
674
0
  struct Curl_dns_entry *dns;
675
0
  struct Curl_dns_entry *dns2;
676
0
  struct dnsc_key key;
677
678
0
  dns = dnsc_entry_create(data, id, permanent);
679
0
  dns = dnsc_entry_assign_addr(data, dns, dns_queries, paddr, NULL);
680
0
  if(!dns)
681
0
    return NULL;
682
683
  /* Store the resolved data in our DNS cache. */
684
0
  dnsc_id2key(&key, id);
685
0
  dns2 = Curl_hash_add(&dnscache->entries, key.data, key.len, (void *)dns);
686
0
  if(!dns2) {
687
0
    dnscache_entry_free(dns);
688
0
    return NULL;
689
0
  }
690
691
0
  dns = dns2;
692
0
  dns->refcount++; /* mark entry as in-use */
693
0
  return dns;
694
0
}
695
696
CURLcode Curl_dnscache_add(struct Curl_easy *data,
697
                           struct Curl_dns_entry *entry)
698
16.3k
{
699
16.3k
  struct Curl_dnscache *dnscache = dnscache_get(data);
700
16.3k
  struct Curl_str name;
701
16.3k
  struct dnsc_id id;
702
16.3k
  struct dnsc_key key;
703
704
16.3k
  if(!dnscache)
705
0
    return CURLE_FAILED_INIT;
706
16.3k
  if(!entry || (entry->type == CURL_DNST_INIT))
707
0
    return CURLE_BAD_FUNCTION_ARGUMENT;
708
709
16.3k
  curlx_str_assign(&name, entry->hostname, entry->hostlen);
710
16.3k
  dnsc_str2id(&id, entry->type, &name, entry->port);
711
16.3k
  dnsc_id2key(&key, &id);
712
713
  /* Store the resolved data in our DNS cache and up ref count */
714
16.3k
  dnscache_lock(data, dnscache);
715
16.3k
  if(!Curl_hash_add(&dnscache->entries, key.data, key.len, (void *)entry)) {
716
0
    dnscache_unlock(data, dnscache);
717
0
    return CURLE_OUT_OF_MEMORY;
718
0
  }
719
16.3k
  entry->refcount++;
720
16.3k
  dnscache_unlock(data, dnscache);
721
16.3k
  CURL_TRC_DNS(data, "cached entry for %s:%u queries=%s",
722
16.3k
               entry->hostname, entry->port,
723
16.3k
               Curl_resolv_query_str(entry->dns_queries));
724
16.3k
  return CURLE_OK;
725
16.3k
}
726
727
CURLcode Curl_dnscache_add_negative(struct Curl_easy *data,
728
                                    uint8_t dns_queries,
729
                                    struct Curl_peer *peer)
730
0
{
731
0
  struct Curl_dnscache *dnscache = dnscache_get(data);
732
0
  struct Curl_dns_entry *dns = NULL;
733
0
  struct dnsc_id id;
734
0
  CURLcode result = CURLE_OK;
735
736
0
  DEBUGASSERT(dnscache);
737
0
  if(!dnscache)
738
0
    return CURLE_FAILED_INIT;
739
740
0
  dnscache_lock(data, dnscache);
741
742
0
  if(dns_queries & CURL_DNSQ_ADDR) {
743
    /* put this new host in the cache */
744
0
    dnsc_peer2id(&id, CURL_DNST_ADDR, peer);
745
0
    dns = dnsc_add_peer_addr(data, dnscache, dns_queries, NULL, &id, FALSE);
746
0
    if(!dns)
747
0
      result = CURLE_OUT_OF_MEMORY;
748
0
  }
749
#ifdef USE_HTTPSRR
750
  else if(dns_queries == CURL_DNSQ_HTTPS) {
751
    dnsc_peer2id(&id, CURL_DNST_HTTPS, peer);
752
    dns = dnsc_add_https(data, dnscache, NULL, &id, FALSE);
753
    if(!dns)
754
      result = CURLE_OUT_OF_MEMORY;
755
  }
756
#endif
757
0
  else {
758
    /* a query we do not know, just cache nothing */
759
0
    DEBUGASSERT(0);
760
0
  }
761
762
0
  if(dns) {
763
    /* release the returned reference; the cache itself will keep the
764
     * entry alive: */
765
0
    dns->refcount--;
766
0
    CURL_TRC_DNS(data, "cache negative name resolve for %s:%d type=%s",
767
0
                 peer->hostname, peer->port,
768
0
                 Curl_resolv_query_str(dns_queries));
769
0
  }
770
0
  dnscache_unlock(data, dnscache);
771
0
  return result;
772
0
}
773
774
/*
775
 * Curl_dns_entry_unlink() releases a reference to the given cached DNS entry.
776
 * When the reference count reaches 0, the entry is destroyed. It is important
777
 * that only one unlink is made for each Curl_resolv() call.
778
 *
779
 * May be called with 'data' == NULL for global cache.
780
 */
781
void Curl_dns_entry_unlink(struct Curl_easy *data,
782
                           struct Curl_dns_entry **pdns)
783
22.5k
{
784
22.5k
  if(*pdns) {
785
17.6k
    struct Curl_dnscache *dnscache = dnscache_get(data);
786
17.6k
    struct Curl_dns_entry *dns = *pdns;
787
17.6k
    *pdns = NULL;
788
17.6k
    dnscache_lock(data, dnscache);
789
17.6k
    dns->refcount--;
790
17.6k
    if(dns->refcount == 0)
791
44
      dnscache_entry_free(dns);
792
17.6k
    dnscache_unlock(data, dnscache);
793
17.6k
  }
794
22.5k
}
795
796
static void dnscache_entry_dtor(void *entry)
797
16.3k
{
798
16.3k
  struct Curl_dns_entry *dns = (struct Curl_dns_entry *)entry;
799
16.3k
  DEBUGASSERT(dns && (dns->refcount > 0));
800
16.3k
  dns->refcount--;
801
16.3k
  if(dns->refcount == 0)
802
16.3k
    dnscache_entry_free(dns);
803
16.3k
}
804
805
/*
806
 * Curl_dnscache_init() inits a new DNS cache.
807
 */
808
void Curl_dnscache_init(struct Curl_dnscache *dns, size_t size)
809
20.0k
{
810
20.0k
  Curl_hash_init(&dns->entries, size, Curl_hash_str, curlx_str_key_compare,
811
20.0k
                 dnscache_entry_dtor);
812
20.0k
}
813
814
void Curl_dnscache_destroy(struct Curl_dnscache *dns)
815
20.0k
{
816
20.0k
  Curl_hash_destroy(&dns->entries);
817
20.0k
}
818
819
CURLcode Curl_loadhostpairs(struct Curl_easy *data)
820
0
{
821
0
  struct Curl_dnscache *dnscache = dnscache_get(data);
822
0
  struct curl_slist *hostp;
823
0
  struct dnsc_id id;
824
0
  struct dnsc_key key;
825
826
0
  if(!dnscache)
827
0
    return CURLE_FAILED_INIT;
828
829
  /* Default is no wildcard found */
830
0
  data->state.wildcard_resolve = FALSE;
831
832
0
  for(hostp = data->state.resolve; hostp; hostp = hostp->next) {
833
0
    const char *host = hostp->data;
834
0
    struct Curl_str source;
835
0
    if(!host)
836
0
      continue;
837
0
    if(*host == '-') {
838
0
      curl_off_t num = 0;
839
0
      host++;
840
0
      if(!curlx_str_single(&host, '[')) {
841
0
        if(curlx_str_until(&host, &source, MAX_IPADR_LEN, ']') ||
842
0
           curlx_str_single(&host, ']') ||
843
0
           curlx_str_single(&host, ':'))
844
0
          continue;
845
0
      }
846
0
      else {
847
0
        if(curlx_str_until(&host, &source, 4096, ':') ||
848
0
           curlx_str_single(&host, ':')) {
849
0
          continue;
850
0
        }
851
0
      }
852
853
0
      if(!curlx_str_number(&host, &num, 0xffff)) {
854
        /* Create an entry id, based upon the hostname and port */
855
0
        dnsc_str2id(&id, CURL_DNST_ADDR, &source, (uint16_t)num);
856
0
        dnsc_id2key(&key, &id);
857
0
        dnscache_lock(data, dnscache);
858
        /* delete entry, ignore if it did not exist */
859
0
        Curl_hash_delete(&dnscache->entries, key.data, key.len);
860
0
        dnscache_unlock(data, dnscache);
861
0
      }
862
0
    }
863
0
    else {
864
0
      struct Curl_dns_entry *dns;
865
0
      struct Curl_addrinfo *head = NULL, *tail = NULL;
866
0
      char address[64];
867
0
      curl_off_t tmpofft = 0;
868
0
      uint16_t port = 0;
869
0
      bool permanent = TRUE;
870
0
      bool error = TRUE;
871
0
      VERBOSE(const char *addresses = NULL);
872
873
0
      if(*host == '+') {
874
0
        host++;
875
0
        permanent = FALSE;
876
0
      }
877
0
      if(!curlx_str_single(&host, '[')) {
878
0
        if(curlx_str_until(&host, &source, MAX_IPADR_LEN, ']') ||
879
0
           curlx_str_single(&host, ']'))
880
0
          continue;
881
0
      }
882
0
      else {
883
0
        if(curlx_str_until(&host, &source, 4096, ':'))
884
0
          continue;
885
0
      }
886
0
      if(curlx_str_single(&host, ':') ||
887
0
         curlx_str_number(&host, &tmpofft, 0xffff) ||
888
0
         curlx_str_single(&host, ':'))
889
0
        goto err;
890
0
      port = (uint16_t)tmpofft;
891
892
0
      VERBOSE(addresses = host);
893
894
      /* start the address section */
895
0
      while(*host) {
896
0
        struct Curl_str target;
897
0
        struct Curl_addrinfo *ai;
898
0
        CURLcode result;
899
900
0
        if(!curlx_str_single(&host, '[')) {
901
0
          if(curlx_str_until(&host, &target, MAX_IPADR_LEN, ']') ||
902
0
             curlx_str_single(&host, ']'))
903
0
            goto err;
904
0
        }
905
0
        else {
906
0
          if(curlx_str_until(&host, &target, 4096, ',')) {
907
0
            if(curlx_str_single(&host, ','))
908
0
              goto err;
909
            /* survive nothing but a comma */
910
0
            continue;
911
0
          }
912
0
        }
913
#ifndef USE_IPV6
914
        if(memchr(curlx_str(&target), ':', curlx_strlen(&target))) {
915
          infof(data, "Ignoring resolve address '%.*s', missing IPv6 support.",
916
                (int)curlx_strlen(&target), curlx_str(&target));
917
          if(curlx_str_single(&host, ','))
918
            goto err;
919
          continue;
920
        }
921
#endif
922
923
0
        if(curlx_strlen(&target) >= sizeof(address))
924
0
          goto err;
925
926
0
        memcpy(address, curlx_str(&target), curlx_strlen(&target));
927
0
        address[curlx_strlen(&target)] = '\0';
928
929
0
        result = Curl_str2addr(address, port, &ai);
930
0
        if(result) {
931
0
          infof(data, "Resolve IP address '%s' found is illegal", address);
932
0
          goto err;
933
0
        }
934
935
0
        if(tail) {
936
0
          tail->ai_next = ai;
937
0
          tail = tail->ai_next;
938
0
        }
939
0
        else {
940
0
          head = tail = ai;
941
0
        }
942
0
        if(curlx_str_single(&host, ','))
943
0
          break;
944
0
      }
945
946
0
      if(!head)
947
0
        goto err;
948
949
0
      error = FALSE;
950
0
err:
951
0
      if(error) {
952
0
        failf(data, "Could not parse CURLOPT_RESOLVE entry '%s'", hostp->data);
953
0
        Curl_freeaddrinfo(head);
954
0
        return CURLE_SETOPT_OPTION_SYNTAX;
955
0
      }
956
957
0
      dnsc_str2id(&id, CURL_DNST_ADDR, &source, port);
958
0
      dnsc_id2key(&key, &id);
959
0
      dnscache_lock(data, dnscache);
960
961
      /* See if it is already in our dns cache */
962
0
      dns = Curl_hash_pick(&dnscache->entries, key.data, key.len);
963
964
0
      if(dns) {
965
0
        infof(data, "RESOLVE %.*s:%u - old addresses discarded",
966
0
              (int)curlx_strlen(&source), curlx_str(&source), port);
967
        /* delete old entry, there are two reasons for this
968
         1. old entry may have different addresses.
969
         2. even if entry with correct addresses is already in the cache,
970
            but if it is close to expire, then by the time next http
971
            request is made, it can get expired and pruned because old
972
            entry is not necessarily marked as permanent.
973
         3. when adding a non-permanent entry, we want it to remove and
974
            replace an existing permanent entry.
975
         4. when adding a non-permanent entry, we want it to get a "fresh"
976
            timeout that starts _now_. */
977
978
0
        Curl_hash_delete(&dnscache->entries, key.data, key.len);
979
0
      }
980
981
      /* put this new host in the cache, override all address queries */
982
0
      dns = dnsc_add_addr(data, dnscache, CURL_DNSQ_ADDR, &head,
983
0
                          &id, &key, permanent);
984
0
      if(dns)
985
        /* release the returned reference; the cache itself will keep the
986
         * entry alive: */
987
0
        dns->refcount--;
988
989
0
      dnscache_unlock(data, dnscache);
990
991
0
      if(!dns)
992
0
        return CURLE_OUT_OF_MEMORY;
993
994
0
      infof(data, "[DNS] added %.*s:%u:%s to cache%s",
995
0
            (int)curlx_strlen(&id.name), curlx_str(&id.name), id.port,
996
0
            addresses, permanent ? "" : " (non-permanent)");
997
998
      /* Wildcard hostname */
999
0
      if(curlx_str_casecompare(&source, "*")) {
1000
0
        infof(data, "RESOLVE *:%u using wildcard", port);
1001
0
        data->state.wildcard_resolve = TRUE;
1002
0
      }
1003
0
    }
1004
0
  }
1005
0
  data->state.resolve = NULL; /* dealt with now */
1006
1007
0
  return CURLE_OK;
1008
0
}