Coverage Report

Created: 2026-10-22 06:50

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