Coverage Report

Created: 2026-07-30 07:26

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