Coverage Report

Created: 2026-06-15 07:03

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