Coverage Report

Created: 2026-09-14 07:04

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