Coverage Report

Created: 2026-10-22 06:51

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
7.35k
#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
14.9k
{
67
14.9k
  curlx_str_assign(&pid->name, peer->hostname, strlen(peer->hostname));
68
14.9k
  pid->port = peer->port;
69
14.9k
  pid->type = type;
70
14.9k
}
71
72
static void dnsc_str2id(struct dnsc_id *pid, char type,
73
                        struct Curl_str *name, uint16_t port)
74
7.45k
{
75
7.45k
  pid->name = *name;
76
7.45k
  pid->port = port;
77
7.45k
  pid->type = type;
78
7.45k
}
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
14.9k
{
89
14.9k
  size_t namelen = curlx_strlen(&id->name);
90
14.9k
  if(namelen > (MAX_HOSTCACHE_LEN - 3))
91
0
    namelen = MAX_HOSTCACHE_LEN - 3;
92
  /* store and lower case the name */
93
14.9k
  key->data[0] = id->type;
94
14.9k
  key->data[1] = (uint8_t)((id->port >> 8) & 0xff);
95
14.9k
  key->data[2] = (uint8_t)(id->port & 0xff);
96
14.9k
  Curl_strntolower((char *)key->data + 3, curlx_str(&id->name), namelen);
97
14.9k
  key->len = namelen + 3;
98
14.9k
}
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
8
{
106
8
  return dns &&
107
         /* Neither the requested nor the cached key was truncated. */
108
8
         (((dns->hostlen <= (MAX_HOSTCACHE_LEN - 3)) &&
109
8
           (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
8
}
116
117
static void dnscache_entry_free(struct Curl_dns_entry *dns)
118
7.49k
{
119
7.49k
  Curl_freeaddrinfo(dns->addr);
120
7.49k
#ifdef USE_HTTPSRR
121
7.49k
  Curl_httpsrr_destroy(dns->hinfo);
122
7.49k
#endif
123
7.49k
  curlx_free(dns);
124
7.49k
}
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
7.46k
{
141
7.46k
  struct dnscache_prune_data *prune = (struct dnscache_prune_data *)datap;
142
7.46k
  struct Curl_dns_entry *dns = (struct Curl_dns_entry *)hc;
143
144
7.46k
  if(!dns->permanent) {
145
    /* get age in milliseconds */
146
7.46k
    timediff_t age_ms = curlx_ptimediff_ms(prune->pnow, &dns->added);
147
7.46k
    switch(dns->type) {
148
7.46k
    case CURL_DNST_ADDR:
149
7.46k
      if(!dns->addr)
150
0
        age_ms *= 2; /* negative entries age twice as fast */
151
7.46k
      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
7.46k
    }
161
7.46k
    if(age_ms >= prune->max_age_ms)
162
15
      return TRUE;
163
7.44k
    if(age_ms > prune->oldest_ms)
164
1.53k
      prune->oldest_ms = age_ms;
165
7.44k
  }
166
7.44k
  return FALSE;
167
7.46k
}
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
7.35k
{
177
7.35k
  struct dnscache_prune_data user;
178
179
7.35k
  user.max_age_ms = cache_timeout_ms;
180
7.35k
  user.pnow = pnow;
181
7.35k
  user.oldest_ms = 0;
182
183
7.35k
  Curl_hash_clean_with_criterium(hostcache,
184
7.35k
                                 (void *)&user,
185
7.35k
                                 dnscache_entry_is_stale);
186
187
7.35k
  return user.oldest_ms;
188
7.35k
}
189
190
static struct Curl_dnscache *dnscache_get(struct Curl_easy *data)
191
29.7k
{
192
29.7k
  if(data->share && data->share->specifier & (1 << CURL_LOCK_DATA_DNS))
193
0
    return &data->share->dnscache;
194
29.7k
  if(data->multi)
195
29.7k
    return &data->multi->dnscache;
196
0
  return NULL;
197
29.7k
}
198
199
static void dnscache_lock(struct Curl_easy *data,
200
                          struct Curl_dnscache *dnscache)
201
29.7k
{
202
29.7k
  if(data->share && dnscache == &data->share->dnscache)
203
0
    Curl_share_lock(data, CURL_LOCK_DATA_DNS, CURL_LOCK_ACCESS_SINGLE);
204
29.7k
}
205
206
static void dnscache_unlock(struct Curl_easy *data,
207
                            struct Curl_dnscache *dnscache)
208
29.7k
{
209
29.7k
  if(data->share && dnscache == &data->share->dnscache)
210
0
    Curl_share_unlock(data, CURL_LOCK_DATA_DNS);
211
29.7k
}
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
7.35k
{
219
7.35k
  struct Curl_dnscache *dnscache = dnscache_get(data);
220
  /* the timeout may be set -1 (forever) */
221
7.35k
  timediff_t timeout_ms = data->set.dns_cache_timeout_ms;
222
223
7.35k
  if(!dnscache || (timeout_ms == -1))
224
    /* NULL hostcache means we cannot do it */
225
0
    return;
226
227
7.35k
  dnscache_lock(data, dnscache);
228
229
7.35k
  do {
230
    /* Remove outdated and unused entries from the hostcache */
231
7.35k
    timediff_t oldest_ms =
232
7.35k
      dnscache_prune(&dnscache->entries, timeout_ms, pnow);
233
234
7.35k
    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
7.35k
    else
238
7.35k
      break;
239
240
    /* if the cache size is still too big, use the oldest age as new prune
241
       limit */
242
7.35k
  } while(timeout_ms);
243
244
7.35k
  dnscache_unlock(data, dnscache);
245
7.35k
}
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
7.46k
{
264
7.46k
  struct Curl_dns_entry *dns = NULL;
265
7.46k
  struct dnsc_id id;
266
7.46k
  struct dnsc_key key;
267
7.46k
  char type = CURL_DNSQ_IS_ADDR(dns_queries) ?
268
7.46k
              CURL_DNST_ADDR : CURL_DNST_HTTPS;
269
7.46k
  CURLcode result = CURLE_OK;
270
271
7.46k
  *pdns = NULL;
272
7.46k
  if(!dnscache)
273
0
    return CURLE_OK;
274
275
7.46k
  dnsc_peer2id(&id, type, peer);
276
7.46k
  dnsc_id2key(&key, &id);
277
278
  /* See if it is already in our dns cache */
279
7.46k
  dns = Curl_hash_pick(&dnscache->entries, key.data, key.len);
280
281
7.46k
  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
7.46k
  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
7.46k
  if(dns && (data->set.dns_cache_timeout_ms != -1)) {
299
    /* See whether the returned entry is stale. Done before we release lock */
300
8
    struct dnscache_prune_data user;
301
302
8
    user.pnow = Curl_pgrs_now(data);
303
8
    user.max_age_ms = data->set.dns_cache_timeout_ms;
304
8
    user.oldest_ms = 0;
305
306
8
    if(dnscache_entry_is_stale(&user, dns)) {
307
2
      infof(data, "Hostname in DNS cache was stale, zapped");
308
2
      dns = NULL; /* the memory deallocation is being handled by the hash */
309
2
      Curl_hash_delete(&dnscache->entries, key.data, key.len);
310
2
    }
311
8
  }
312
313
  /* We need to cache address information and HTTPS-RR separately. */
314
7.46k
  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
7.46k
  *pdns = dns;
334
7.46k
  return result;
335
7.46k
}
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
7.46k
{
356
7.46k
  struct Curl_dnscache *dnscache = dnscache_get(data);
357
7.46k
  struct Curl_dns_entry *dns = NULL;
358
7.46k
  CURLcode result = CURLE_OK;
359
360
7.46k
  dnscache_lock(data, dnscache);
361
7.46k
  result = fetch_entry(data, dnscache, dns_queries, peer, &dns);
362
7.46k
  if(!result && dns)
363
6
    dns->refcount++; /* we pass out a reference */
364
7.45k
  else if(result) {
365
0
    DEBUGASSERT(!dns);
366
0
    dns = NULL;
367
0
  }
368
7.46k
  dnscache_unlock(data, dnscache);
369
370
7.46k
  CURL_TRC_DNS(data, "cache lookup %s:%u queries=%s -> %d %sfound",
371
7.46k
               peer->hostname, peer->port, Curl_resolv_query_str(dns_queries),
372
7.46k
               (int)result, dns ? "" : "not ");
373
7.46k
  *pentry = dns;
374
7.46k
  return result;
375
7.46k
}
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
6
{
383
6
  int i = 0;
384
12
  while(addr) {
385
6
    addr = addr->ai_next;
386
6
    i++;
387
6
  }
388
6
  return i;
389
6
}
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
6
{
408
6
  CURLcode result = CURLE_OK;
409
6
  const int num_addrs = num_addresses(*addr);
410
411
6
  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
6
  return result;
456
6
}
457
#endif
458
459
static bool dnscache_ai_has_family(struct Curl_addrinfo *ai, int ai_family)
460
14.8k
{
461
22.2k
  for(; ai; ai = ai->ai_next) {
462
14.8k
    if(ai->ai_family == ai_family)
463
7.45k
      return TRUE;
464
14.8k
  }
465
7.40k
  return FALSE;
466
14.8k
}
467
468
static struct Curl_dns_entry *dnsc_entry_create(struct dnsc_id *pid,
469
                                                bool permanent)
470
7.49k
{
471
7.49k
  struct Curl_dns_entry *dns = NULL;
472
473
7.49k
  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
7.49k
  dns = curlx_calloc(1, sizeof(struct Curl_dns_entry) +
479
7.49k
                     curlx_strlen(&pid->name));
480
7.49k
  if(!dns)
481
0
    goto out;
482
483
7.49k
  dns->refcount = 1; /* the cache has the first reference */
484
7.49k
  dns->port = pid->port;
485
7.49k
  dns->hostlen = (uint16_t)curlx_strlen(&pid->name);
486
7.49k
  if(dns->hostlen)
487
7.49k
    memcpy(dns->hostname, curlx_str(&pid->name), dns->hostlen);
488
7.49k
  dns->permanent = permanent;
489
490
7.49k
out:
491
7.49k
  return dns;
492
7.49k
}
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
7.49k
{
501
7.49k
  if(!dns)
502
0
    goto out;
503
  /* only do this when this is the only reference */
504
7.49k
  DEBUGASSERT(dns->refcount == 1);
505
7.49k
  DEBUGASSERT(dns->type == CURL_DNST_INIT);
506
507
7.49k
  dns->type = CURL_DNST_ADDR;
508
  /* queries should only be about addresses */
509
7.49k
  DEBUGASSERT(!(dns_queries & ~CURL_DNSQ_ADDR));
510
7.49k
  dns->dns_queries = (dns_queries & CURL_DNSQ_ADDR);
511
512
  /* Take the given address lists into the entry */
513
7.49k
  if(paddr1 && *paddr1) {
514
7.49k
    dns->addr = *paddr1;
515
7.49k
    *paddr1 = NULL;
516
7.49k
  }
517
7.49k
  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
7.49k
  if((dns_queries & CURL_DNSQ_A) &&
526
7.45k
     dnscache_ai_has_family(dns->addr, PF_INET))
527
7.45k
    dns->dns_responses |= CURL_DNSQ_A;
528
529
7.49k
#ifdef USE_IPV6
530
7.49k
  if((dns_queries & CURL_DNSQ_AAAA) &&
531
7.40k
     dnscache_ai_has_family(dns->addr, PF_INET6))
532
0
    dns->dns_responses |= CURL_DNSQ_AAAA;
533
7.49k
#endif /* USE_IPV6 */
534
535
7.49k
#ifndef CURL_DISABLE_SHUFFLE_DNS
536
  /* shuffle addresses if requested */
537
7.49k
  if(data->set.dns_shuffle_addresses && dns->addr) {
538
6
    CURLcode result = dns_shuffle_addr(data, &dns->addr);
539
6
    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
6
  }
546
#else
547
  (void)data;
548
#endif
549
550
7.49k
out:
551
7.49k
  if(paddr1 && *paddr1) {
552
0
    Curl_freeaddrinfo(*paddr1);
553
0
    *paddr1 = NULL;
554
0
  }
555
7.49k
  if(paddr2 && *paddr2) {
556
0
    Curl_freeaddrinfo(*paddr2);
557
0
    *paddr2 = NULL;
558
0
  }
559
7.49k
  return dns;
560
7.49k
}
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
7.49k
{
567
7.49k
  struct dnsc_id id;
568
7.49k
  struct Curl_dns_entry *dns;
569
570
7.49k
  dnsc_peer2id(&id, CURL_DNST_ADDR, peer);
571
7.49k
  dns = dnsc_entry_create(&id, FALSE);
572
7.49k
  dns = dnsc_entry_assign_addr(data, dns, dns_queries, paddr, NULL);
573
7.49k
  return dns;
574
7.49k
}
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
7.45k
{
596
7.45k
  entry->added = *Curl_pgrs_now(data);
597
7.45k
  return Curl_hash_add(&dnscache->entries, key->data, key->len, entry);
598
7.45k
}
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
7.45k
{
731
7.45k
  struct Curl_dnscache *dnscache = dnscache_get(data);
732
7.45k
  struct Curl_str name;
733
7.45k
  struct dnsc_id id;
734
7.45k
  struct dnsc_key key;
735
736
7.45k
  if(!dnscache)
737
0
    return CURLE_FAILED_INIT;
738
7.45k
  if(!entry || (entry->type == CURL_DNST_INIT))
739
0
    return CURLE_BAD_FUNCTION_ARGUMENT;
740
741
7.45k
  curlx_str_assign(&name, entry->hostname, entry->hostlen);
742
7.45k
  dnsc_str2id(&id, entry->type, &name, entry->port);
743
7.45k
  dnsc_id2key(&key, &id);
744
745
  /* Store the resolved data in our DNS cache and up ref count */
746
7.45k
  dnscache_lock(data, dnscache);
747
7.45k
  if(!dnsc_add_entry(data, dnscache, &key, entry)) {
748
0
    dnscache_unlock(data, dnscache);
749
0
    return CURLE_OUT_OF_MEMORY;
750
0
  }
751
7.45k
  entry->refcount++;
752
7.45k
  dnscache_unlock(data, dnscache);
753
7.45k
  CURL_TRC_DNS(data, "cached entry for %s:%u queries=%s",
754
7.45k
               entry->hostname, entry->port,
755
7.45k
               Curl_resolv_query_str(entry->dns_queries));
756
7.45k
  return CURLE_OK;
757
7.45k
}
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
13.5k
{
816
13.5k
  if(*pdns) {
817
7.50k
    struct Curl_dnscache *dnscache = dnscache_get(data);
818
7.50k
    struct Curl_dns_entry *dns = *pdns;
819
7.50k
    *pdns = NULL;
820
7.50k
    dnscache_lock(data, dnscache);
821
7.50k
    dns->refcount--;
822
7.50k
    if(dns->refcount == 0)
823
42
      dnscache_entry_free(dns);
824
7.50k
    dnscache_unlock(data, dnscache);
825
7.50k
  }
826
13.5k
}
827
828
static void dnscache_entry_dtor(void *entry)
829
7.45k
{
830
7.45k
  struct Curl_dns_entry *dns = (struct Curl_dns_entry *)entry;
831
7.45k
  DEBUGASSERT(dns && (dns->refcount > 0));
832
7.45k
  dns->refcount--;
833
7.45k
  if(dns->refcount == 0)
834
7.45k
    dnscache_entry_free(dns);
835
7.45k
}
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
13.5k
{
842
13.5k
  Curl_hash_init(&dns->entries, size, CURL_HASH_TYPE_BYTES,
843
13.5k
                 dnscache_entry_dtor);
844
13.5k
}
845
846
void Curl_dnscache_destroy(struct Curl_dnscache *dns)
847
13.5k
{
848
13.5k
  Curl_hash_destroy(&dns->entries);
849
13.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
}