Coverage Report

Created: 2026-10-28 06:38

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