Coverage Report

Created: 2026-09-01 06:58

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