Coverage Report

Created: 2026-01-10 07:08

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/curl/lib/hostip.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 <setjmp.h>  /* for sigjmp_buf, sigsetjmp() */
44
#include <signal.h>
45
46
#include "urldata.h"
47
#include "curl_trc.h"
48
#include "connect.h"
49
#include "hostip.h"
50
#include "hash.h"
51
#include "rand.h"
52
#include "curl_share.h"
53
#include "url.h"
54
#include "curlx/inet_ntop.h"
55
#include "curlx/inet_pton.h"
56
#include "multiif.h"
57
#include "doh.h"
58
#include "progress.h"
59
#include "select.h"
60
#include "strcase.h"
61
#include "easy_lock.h"
62
#include "curlx/strcopy.h"
63
#include "curlx/strparse.h"
64
65
#if defined(CURLRES_SYNCH) &&                   \
66
  defined(HAVE_ALARM) &&                        \
67
  defined(SIGALRM) &&                           \
68
  defined(HAVE_SIGSETJMP) &&                    \
69
  defined(GLOBAL_INIT_IS_THREADSAFE)
70
/* alarm-based timeouts can only be used with all the dependencies satisfied */
71
#define USE_ALARM_TIMEOUT
72
#endif
73
74
#define MAX_HOSTCACHE_LEN (255 + 7) /* max FQDN + colon + port number + zero */
75
76
135k
#define MAX_DNS_CACHE_SIZE 29999
77
78
/*
79
 * hostip.c explained
80
 * ==================
81
 *
82
 * The main COMPILE-TIME DEFINES to keep in mind when reading the host*.c
83
 * source file are these:
84
 *
85
 * CURLRES_IPV6 - this host has getaddrinfo() and family, and thus we use
86
 * that. The host may not be able to resolve IPv6, but we do not really have to
87
 * take that into account. Hosts that are not IPv6-enabled have CURLRES_IPV4
88
 * defined.
89
 *
90
 * CURLRES_ARES - is defined if libcurl is built to use c-ares for
91
 * asynchronous name resolves. This can be Windows or *nix.
92
 *
93
 * CURLRES_THREADED - is defined if libcurl is built to run under (native)
94
 * Windows, and then the name resolve will be done in a new thread, and the
95
 * supported API will be the same as for ares-builds.
96
 *
97
 * If any of the two previous are defined, CURLRES_ASYNCH is defined too. If
98
 * libcurl is not built to use an asynchronous resolver, CURLRES_SYNCH is
99
 * defined.
100
 *
101
 * The host*.c sources files are split up like this:
102
 *
103
 * hostip.c   - method-independent resolver functions and utility functions
104
 * hostip4.c  - IPv4 specific functions
105
 * hostip6.c  - IPv6 specific functions
106
 * asyn.h     - common functions for all async resolvers
107
 * The two asynchronous name resolver backends are implemented in:
108
 * asyn-ares.c - async resolver using c-ares
109
 * asyn-thread.c - async resolver using POSIX threads
110
 *
111
 * The hostip.h is the united header file for all this. It defines the
112
 * CURLRES_* defines based on the config*.h and curl_setup.h defines.
113
 */
114
115
static void dnscache_entry_free(struct Curl_dns_entry *dns);
116
117
#ifndef CURL_DISABLE_VERBOSE_STRINGS
118
static void show_resolve_info(struct Curl_easy *data,
119
                              struct Curl_dns_entry *dns);
120
#else
121
#define show_resolve_info(x, y) Curl_nop_stmt
122
#endif
123
124
/*
125
 * Curl_printable_address() stores a printable version of the 1st address
126
 * given in the 'ai' argument. The result will be stored in the buf that is
127
 * bufsize bytes big.
128
 *
129
 * If the conversion fails, the target buffer is empty.
130
 */
131
void Curl_printable_address(const struct Curl_addrinfo *ai, char *buf,
132
                            size_t bufsize)
133
965
{
134
965
  DEBUGASSERT(bufsize);
135
965
  buf[0] = 0;
136
137
965
  switch(ai->ai_family) {
138
548
  case AF_INET: {
139
548
    const struct sockaddr_in *sa4 = (const void *)ai->ai_addr;
140
548
    const struct in_addr *ipaddr4 = &sa4->sin_addr;
141
548
    (void)curlx_inet_ntop(ai->ai_family, (const void *)ipaddr4, buf, bufsize);
142
548
    break;
143
0
  }
144
0
#ifdef USE_IPV6
145
417
  case AF_INET6: {
146
417
    const struct sockaddr_in6 *sa6 = (const void *)ai->ai_addr;
147
417
    const struct in6_addr *ipaddr6 = &sa6->sin6_addr;
148
417
    (void)curlx_inet_ntop(ai->ai_family, (const void *)ipaddr6, buf, bufsize);
149
417
    break;
150
0
  }
151
0
#endif
152
0
  default:
153
0
    break;
154
965
  }
155
965
}
156
157
/*
158
 * Create a hostcache id string for the provided host + port, to be used by
159
 * the DNS caching. Without alloc. Return length of the id string.
160
 */
161
static size_t create_dnscache_id(const char *name,
162
                                 size_t nlen, /* 0 or actual name length */
163
                                 int port, char *ptr, size_t buflen)
164
3.18M
{
165
3.18M
  size_t len = nlen ? nlen : strlen(name);
166
3.18M
  DEBUGASSERT(buflen >= MAX_HOSTCACHE_LEN);
167
3.18M
  if(len > (buflen - 7))
168
221
    len = buflen - 7;
169
  /* store and lower case the name */
170
3.18M
  Curl_strntolower(ptr, name, len);
171
3.18M
  return curl_msnprintf(&ptr[len], 7, ":%u", port) + len;
172
3.18M
}
173
174
struct dnscache_prune_data {
175
  struct curltime now;
176
  timediff_t oldest_ms; /* oldest time in cache not pruned. */
177
  timediff_t max_age_ms;
178
};
179
180
/*
181
 * This function is set as a callback to be called for every entry in the DNS
182
 * cache when we want to prune old unused entries.
183
 *
184
 * Returning non-zero means remove the entry, return 0 to keep it in the
185
 * cache.
186
 */
187
static int dnscache_entry_is_stale(void *datap, void *hc)
188
133k
{
189
133k
  struct dnscache_prune_data *prune = (struct dnscache_prune_data *)datap;
190
133k
  struct Curl_dns_entry *dns = (struct Curl_dns_entry *)hc;
191
192
133k
  if(dns->timestamp.tv_sec || dns->timestamp.tv_usec) {
193
    /* get age in milliseconds */
194
133k
    timediff_t age = curlx_ptimediff_ms(&prune->now, &dns->timestamp);
195
133k
    if(!dns->addr)
196
1.74k
      age *= 2; /* negative entries age twice as fast */
197
133k
    if(age >= prune->max_age_ms)
198
192
      return TRUE;
199
133k
    if(age > prune->oldest_ms)
200
36.7k
      prune->oldest_ms = age;
201
133k
  }
202
133k
  return FALSE;
203
133k
}
204
205
/*
206
 * Prune the DNS cache. This assumes that a lock has already been taken.
207
 * Returns the 'age' of the oldest still kept entry - in milliseconds.
208
 */
209
static timediff_t dnscache_prune(struct Curl_hash *hostcache,
210
                                 timediff_t cache_timeout_ms,
211
                                 struct curltime now)
212
135k
{
213
135k
  struct dnscache_prune_data user;
214
215
135k
  user.max_age_ms = cache_timeout_ms;
216
135k
  user.now = now;
217
135k
  user.oldest_ms = 0;
218
219
135k
  Curl_hash_clean_with_criterium(hostcache,
220
135k
                                 (void *)&user,
221
135k
                                 dnscache_entry_is_stale);
222
223
135k
  return user.oldest_ms;
224
135k
}
225
226
static struct Curl_dnscache *dnscache_get(struct Curl_easy *data)
227
3.43M
{
228
3.43M
  if(data->share && data->share->specifier & (1 << CURL_LOCK_DATA_DNS))
229
0
    return &data->share->dnscache;
230
3.43M
  if(data->multi)
231
3.43M
    return &data->multi->dnscache;
232
0
  return NULL;
233
3.43M
}
234
235
static void dnscache_lock(struct Curl_easy *data,
236
                          struct Curl_dnscache *dnscache)
237
3.43M
{
238
3.43M
  if(data->share && dnscache == &data->share->dnscache)
239
0
    Curl_share_lock(data, CURL_LOCK_DATA_DNS, CURL_LOCK_ACCESS_SINGLE);
240
3.43M
}
241
242
static void dnscache_unlock(struct Curl_easy *data,
243
                            struct Curl_dnscache *dnscache)
244
3.43M
{
245
3.43M
  if(data->share && dnscache == &data->share->dnscache)
246
0
    Curl_share_unlock(data, CURL_LOCK_DATA_DNS);
247
3.43M
}
248
249
/*
250
 * Library-wide function for pruning the DNS cache. This function takes and
251
 * returns the appropriate locks.
252
 */
253
void Curl_dnscache_prune(struct Curl_easy *data)
254
135k
{
255
135k
  struct Curl_dnscache *dnscache = dnscache_get(data);
256
  /* the timeout may be set -1 (forever) */
257
135k
  timediff_t timeout_ms = data->set.dns_cache_timeout_ms;
258
259
135k
  if(!dnscache || (timeout_ms == -1))
260
    /* NULL hostcache means we cannot do it */
261
0
    return;
262
263
135k
  dnscache_lock(data, dnscache);
264
265
135k
  do {
266
    /* Remove outdated and unused entries from the hostcache */
267
135k
    timediff_t oldest_ms =
268
135k
      dnscache_prune(&dnscache->entries, timeout_ms, *Curl_pgrs_now(data));
269
270
135k
    if(Curl_hash_count(&dnscache->entries) > MAX_DNS_CACHE_SIZE)
271
      /* prune the ones over half this age */
272
0
      timeout_ms = oldest_ms / 2;
273
135k
    else
274
135k
      break;
275
276
    /* if the cache size is still too big, use the oldest age as new prune
277
       limit */
278
135k
  } while(timeout_ms);
279
280
135k
  dnscache_unlock(data, dnscache);
281
135k
}
282
283
void Curl_dnscache_clear(struct Curl_easy *data)
284
0
{
285
0
  struct Curl_dnscache *dnscache = dnscache_get(data);
286
0
  if(dnscache) {
287
0
    dnscache_lock(data, dnscache);
288
0
    Curl_hash_clean(&dnscache->entries);
289
0
    dnscache_unlock(data, dnscache);
290
0
  }
291
0
}
292
293
#ifdef USE_ALARM_TIMEOUT
294
/* Beware this is a global and unique instance. This is used to store the
295
   return address that we can jump back to from inside a signal handler. This
296
   is not thread-safe stuff. */
297
static sigjmp_buf curl_jmpenv;
298
static curl_simple_lock curl_jmpenv_lock;
299
#endif
300
301
/* lookup address, returns entry if found and not stale */
302
static struct Curl_dns_entry *fetch_addr(struct Curl_easy *data,
303
                                         struct Curl_dnscache *dnscache,
304
                                         const char *hostname,
305
                                         int port,
306
                                         int ip_version)
307
3.07M
{
308
3.07M
  struct Curl_dns_entry *dns = NULL;
309
3.07M
  char entry_id[MAX_HOSTCACHE_LEN];
310
3.07M
  size_t entry_len;
311
312
3.07M
  if(!dnscache)
313
0
    return NULL;
314
315
  /* Create an entry id, based upon the hostname and port */
316
3.07M
  entry_len = create_dnscache_id(hostname, 0, port,
317
3.07M
                                 entry_id, sizeof(entry_id));
318
319
  /* See if it is already in our dns cache */
320
3.07M
  dns = Curl_hash_pick(&dnscache->entries, entry_id, entry_len + 1);
321
322
  /* No entry found in cache, check if we might have a wildcard entry */
323
3.07M
  if(!dns && data->state.wildcard_resolve) {
324
0
    entry_len = create_dnscache_id("*", 1, port, entry_id, sizeof(entry_id));
325
326
    /* See if it is already in our dns cache */
327
0
    dns = Curl_hash_pick(&dnscache->entries, entry_id, entry_len + 1);
328
0
  }
329
330
3.07M
  if(dns && (data->set.dns_cache_timeout_ms != -1)) {
331
    /* See whether the returned entry is stale. Done before we release lock */
332
6.09k
    struct dnscache_prune_data user;
333
334
6.09k
    user.now = *Curl_pgrs_now(data);
335
6.09k
    user.max_age_ms = data->set.dns_cache_timeout_ms;
336
6.09k
    user.oldest_ms = 0;
337
338
6.09k
    if(dnscache_entry_is_stale(&user, dns)) {
339
40
      infof(data, "Hostname in DNS cache was stale, zapped");
340
40
      dns = NULL; /* the memory deallocation is being handled by the hash */
341
40
      Curl_hash_delete(&dnscache->entries, entry_id, entry_len + 1);
342
40
    }
343
6.09k
  }
344
345
  /* See if the returned entry matches the required resolve mode */
346
3.07M
  if(dns && ip_version != CURL_IPRESOLVE_WHATEVER) {
347
242
    int pf = PF_INET;
348
242
    bool found = FALSE;
349
242
    struct Curl_addrinfo *addr = dns->addr;
350
351
242
#ifdef PF_INET6
352
242
    if(ip_version == CURL_IPRESOLVE_V6)
353
8
      pf = PF_INET6;
354
242
#endif
355
356
422
    while(addr) {
357
322
      if(addr->ai_family == pf) {
358
142
        found = TRUE;
359
142
        break;
360
142
      }
361
180
      addr = addr->ai_next;
362
180
    }
363
364
242
    if(!found) {
365
100
      infof(data, "Hostname in DNS cache does not have needed family, zapped");
366
100
      dns = NULL; /* the memory deallocation is being handled by the hash */
367
100
      Curl_hash_delete(&dnscache->entries, entry_id, entry_len + 1);
368
100
    }
369
242
  }
370
3.07M
  return dns;
371
3.07M
}
372
373
/*
374
 * Curl_dnscache_get() fetches a 'Curl_dns_entry' already in the DNS cache.
375
 *
376
 * Curl_resolv() checks initially and multi_runsingle() checks each time
377
 * it discovers the handle in the state WAITRESOLVE whether the hostname
378
 * has already been resolved and the address has already been stored in
379
 * the DNS cache. This short circuits waiting for a lot of pending
380
 * lookups for the same hostname requested by different handles.
381
 *
382
 * Returns the Curl_dns_entry entry pointer or NULL if not in the cache.
383
 *
384
 * The returned data *MUST* be "released" with Curl_resolv_unlink() after
385
 * use, or we will leak memory!
386
 */
387
struct Curl_dns_entry *Curl_dnscache_get(struct Curl_easy *data,
388
                                         const char *hostname,
389
                                         int port,
390
                                         int ip_version)
391
2.94M
{
392
2.94M
  struct Curl_dnscache *dnscache = dnscache_get(data);
393
2.94M
  struct Curl_dns_entry *dns = NULL;
394
395
2.94M
  dnscache_lock(data, dnscache);
396
397
2.94M
  dns = fetch_addr(data, dnscache, hostname, port, ip_version);
398
2.94M
  if(dns)
399
166
    dns->refcount++; /* we use it! */
400
401
2.94M
  dnscache_unlock(data, dnscache);
402
403
2.94M
  return dns;
404
2.94M
}
405
406
#ifndef CURL_DISABLE_SHUFFLE_DNS
407
/*
408
 * Return # of addresses in a Curl_addrinfo struct
409
 */
410
static int num_addresses(const struct Curl_addrinfo *addr)
411
234
{
412
234
  int i = 0;
413
571
  while(addr) {
414
337
    addr = addr->ai_next;
415
337
    i++;
416
337
  }
417
234
  return i;
418
234
}
419
420
UNITTEST CURLcode Curl_shuffle_addr(struct Curl_easy *data,
421
                                    struct Curl_addrinfo **addr);
422
/*
423
 * Curl_shuffle_addr() shuffles the order of addresses in a 'Curl_addrinfo'
424
 * struct by re-linking its linked list.
425
 *
426
 * The addr argument should be the address of a pointer to the head node of a
427
 * `Curl_addrinfo` list and it will be modified to point to the new head after
428
 * shuffling.
429
 *
430
 * Not declared static only to make it easy to use in a unit test!
431
 *
432
 * @unittest: 1608
433
 */
434
UNITTEST CURLcode Curl_shuffle_addr(struct Curl_easy *data,
435
                                    struct Curl_addrinfo **addr)
436
234
{
437
234
  CURLcode result = CURLE_OK;
438
234
  const int num_addrs = num_addresses(*addr);
439
440
234
  if(num_addrs > 1) {
441
115
    struct Curl_addrinfo **nodes;
442
115
    infof(data, "Shuffling %i addresses", num_addrs);
443
444
115
    nodes = curlx_malloc(num_addrs * sizeof(*nodes));
445
115
    if(nodes) {
446
115
      int i;
447
115
      unsigned int *rnd;
448
115
      const size_t rnd_size = num_addrs * sizeof(*rnd);
449
450
      /* build a plain array of Curl_addrinfo pointers */
451
115
      nodes[0] = *addr;
452
230
      for(i = 1; i < num_addrs; i++) {
453
115
        nodes[i] = nodes[i - 1]->ai_next;
454
115
      }
455
456
115
      rnd = curlx_malloc(rnd_size);
457
115
      if(rnd) {
458
        /* Fisher-Yates shuffle */
459
115
        if(Curl_rand(data, (unsigned char *)rnd, rnd_size) == CURLE_OK) {
460
115
          struct Curl_addrinfo *swap_tmp;
461
230
          for(i = num_addrs - 1; i > 0; i--) {
462
115
            swap_tmp = nodes[rnd[i] % (unsigned int)(i + 1)];
463
115
            nodes[rnd[i] % (unsigned int)(i + 1)] = nodes[i];
464
115
            nodes[i] = swap_tmp;
465
115
          }
466
467
          /* relink list in the new order */
468
230
          for(i = 1; i < num_addrs; i++) {
469
115
            nodes[i - 1]->ai_next = nodes[i];
470
115
          }
471
472
115
          nodes[num_addrs - 1]->ai_next = NULL;
473
115
          *addr = nodes[0];
474
115
        }
475
115
        curlx_free(rnd);
476
115
      }
477
0
      else
478
0
        result = CURLE_OUT_OF_MEMORY;
479
115
      curlx_free(nodes);
480
115
    }
481
0
    else
482
0
      result = CURLE_OUT_OF_MEMORY;
483
115
  }
484
234
  return result;
485
234
}
486
#endif
487
488
struct Curl_dns_entry *
489
Curl_dnscache_mk_entry(struct Curl_easy *data,
490
                       struct Curl_addrinfo *addr,
491
                       const char *hostname,
492
                       size_t hostlen, /* length or zero */
493
                       int port,
494
                       bool permanent)
495
114k
{
496
114k
  struct Curl_dns_entry *dns;
497
498
114k
#ifndef CURL_DISABLE_SHUFFLE_DNS
499
  /* shuffle addresses if requested */
500
114k
  if(data->set.dns_shuffle_addresses) {
501
234
    CURLcode result = Curl_shuffle_addr(data, &addr);
502
234
    if(result)
503
0
      return NULL;
504
234
  }
505
#else
506
  (void)data;
507
#endif
508
114k
  if(!hostlen)
509
114k
    hostlen = strlen(hostname);
510
511
  /* Create a new cache entry */
512
114k
  dns = curlx_calloc(1, sizeof(struct Curl_dns_entry) + hostlen);
513
114k
  if(!dns)
514
0
    return NULL;
515
516
114k
  dns->refcount = 1; /* the cache has the first reference */
517
114k
  dns->addr = addr; /* this is the address(es) */
518
114k
  if(permanent) {
519
0
    dns->timestamp.tv_sec = 0; /* an entry that never goes stale */
520
0
    dns->timestamp.tv_usec = 0; /* an entry that never goes stale */
521
0
  }
522
114k
  else {
523
114k
    dns->timestamp = *Curl_pgrs_now(data);
524
114k
  }
525
114k
  dns->hostport = port;
526
114k
  if(hostlen)
527
114k
    memcpy(dns->hostname, hostname, hostlen);
528
529
114k
  return dns;
530
114k
}
531
532
static struct Curl_dns_entry *
533
dnscache_add_addr(struct Curl_easy *data,
534
                  struct Curl_dnscache *dnscache,
535
                  struct Curl_addrinfo *addr,
536
                  const char *hostname,
537
                  size_t hlen, /* length or zero */
538
                  int port,
539
                  bool permanent)
540
4.95k
{
541
4.95k
  char entry_id[MAX_HOSTCACHE_LEN];
542
4.95k
  size_t entry_len;
543
4.95k
  struct Curl_dns_entry *dns;
544
4.95k
  struct Curl_dns_entry *dns2;
545
546
4.95k
  dns = Curl_dnscache_mk_entry(data, addr, hostname, hlen, port, permanent);
547
4.95k
  if(!dns)
548
0
    return NULL;
549
550
  /* Create an entry id, based upon the hostname and port */
551
4.95k
  entry_len = create_dnscache_id(hostname, hlen, port,
552
4.95k
                                 entry_id, sizeof(entry_id));
553
554
  /* Store the resolved data in our DNS cache. */
555
4.95k
  dns2 = Curl_hash_add(&dnscache->entries, entry_id, entry_len + 1,
556
4.95k
                       (void *)dns);
557
4.95k
  if(!dns2) {
558
0
    dns->addr = NULL;
559
0
    dnscache_entry_free(dns);
560
0
    return NULL;
561
0
  }
562
563
4.95k
  dns = dns2;
564
4.95k
  dns->refcount++;         /* mark entry as in-use */
565
4.95k
  return dns;
566
4.95k
}
567
568
CURLcode Curl_dnscache_add(struct Curl_easy *data,
569
                           struct Curl_dns_entry *entry)
570
109k
{
571
109k
  struct Curl_dnscache *dnscache = dnscache_get(data);
572
109k
  char id[MAX_HOSTCACHE_LEN];
573
109k
  size_t idlen;
574
575
109k
  if(!dnscache)
576
0
    return CURLE_FAILED_INIT;
577
  /* Create an entry id, based upon the hostname and port */
578
109k
  idlen = create_dnscache_id(entry->hostname, 0, entry->hostport,
579
109k
                             id, sizeof(id));
580
581
  /* Store the resolved data in our DNS cache and up ref count */
582
109k
  dnscache_lock(data, dnscache);
583
109k
  if(!Curl_hash_add(&dnscache->entries, id, idlen + 1, (void *)entry)) {
584
0
    dnscache_unlock(data, dnscache);
585
0
    return CURLE_OUT_OF_MEMORY;
586
0
  }
587
109k
  entry->refcount++;
588
109k
  dnscache_unlock(data, dnscache);
589
109k
  return CURLE_OK;
590
109k
}
591
592
#ifdef USE_IPV6
593
/* return a static IPv6 ::1 for the name */
594
static struct Curl_addrinfo *get_localhost6(int port, const char *name)
595
763
{
596
763
  struct Curl_addrinfo *ca;
597
763
  const size_t ss_size = sizeof(struct sockaddr_in6);
598
763
  const size_t hostlen = strlen(name);
599
763
  struct sockaddr_in6 sa6;
600
763
  unsigned char ipv6[16];
601
763
  unsigned short port16 = (unsigned short)(port & 0xffff);
602
763
  ca = curlx_calloc(1, sizeof(struct Curl_addrinfo) + ss_size + hostlen + 1);
603
763
  if(!ca)
604
0
    return NULL;
605
606
763
  sa6.sin6_family = AF_INET6;
607
763
  sa6.sin6_port = htons(port16);
608
763
  sa6.sin6_flowinfo = 0;
609
763
#ifdef HAVE_SOCKADDR_IN6_SIN6_SCOPE_ID
610
763
  sa6.sin6_scope_id = 0;
611
763
#endif
612
613
763
  (void)curlx_inet_pton(AF_INET6, "::1", ipv6);
614
763
  memcpy(&sa6.sin6_addr, ipv6, sizeof(ipv6));
615
616
763
  ca->ai_flags     = 0;
617
763
  ca->ai_family    = AF_INET6;
618
763
  ca->ai_socktype  = SOCK_STREAM;
619
763
  ca->ai_protocol  = IPPROTO_TCP;
620
763
  ca->ai_addrlen   = (curl_socklen_t)ss_size;
621
763
  ca->ai_next      = NULL;
622
763
  ca->ai_addr = (void *)((char *)ca + sizeof(struct Curl_addrinfo));
623
763
  memcpy(ca->ai_addr, &sa6, ss_size);
624
763
  ca->ai_canonname = (char *)ca->ai_addr + ss_size;
625
763
  curlx_strcopy(ca->ai_canonname, hostlen + 1, name, hostlen);
626
763
  return ca;
627
763
}
628
#else
629
#define get_localhost6(x, y) NULL
630
#endif
631
632
/* return a static IPv4 127.0.0.1 for the given name */
633
static struct Curl_addrinfo *get_localhost(int port, const char *name)
634
763
{
635
763
  struct Curl_addrinfo *ca;
636
763
  struct Curl_addrinfo *ca6;
637
763
  const size_t ss_size = sizeof(struct sockaddr_in);
638
763
  const size_t hostlen = strlen(name);
639
763
  struct sockaddr_in sa;
640
763
  unsigned int ipv4;
641
763
  unsigned short port16 = (unsigned short)(port & 0xffff);
642
643
  /* memset to clear the sa.sin_zero field */
644
763
  memset(&sa, 0, sizeof(sa));
645
763
  sa.sin_family = AF_INET;
646
763
  sa.sin_port = htons(port16);
647
763
  if(curlx_inet_pton(AF_INET, "127.0.0.1", (char *)&ipv4) < 1)
648
0
    return NULL;
649
763
  memcpy(&sa.sin_addr, &ipv4, sizeof(ipv4));
650
651
763
  ca = curlx_calloc(1, sizeof(struct Curl_addrinfo) + ss_size + hostlen + 1);
652
763
  if(!ca)
653
0
    return NULL;
654
763
  ca->ai_flags     = 0;
655
763
  ca->ai_family    = AF_INET;
656
763
  ca->ai_socktype  = SOCK_STREAM;
657
763
  ca->ai_protocol  = IPPROTO_TCP;
658
763
  ca->ai_addrlen   = (curl_socklen_t)ss_size;
659
763
  ca->ai_addr = (void *)((char *)ca + sizeof(struct Curl_addrinfo));
660
763
  memcpy(ca->ai_addr, &sa, ss_size);
661
763
  ca->ai_canonname = (char *)ca->ai_addr + ss_size;
662
763
  curlx_strcopy(ca->ai_canonname, hostlen + 1, name, hostlen);
663
664
763
  ca6 = get_localhost6(port, name);
665
763
  if(!ca6)
666
0
    return ca;
667
763
  ca6->ai_next = ca;
668
763
  return ca6;
669
763
}
670
671
#ifdef USE_IPV6
672
/*
673
 * Curl_ipv6works() returns TRUE if IPv6 seems to work.
674
 */
675
bool Curl_ipv6works(struct Curl_easy *data)
676
14.8k
{
677
14.8k
  if(data) {
678
    /* the nature of most system is that IPv6 status does not come and go
679
       during a program's lifetime so we only probe the first time and then we
680
       have the info kept for fast reuse */
681
8.78k
    DEBUGASSERT(data);
682
8.78k
    DEBUGASSERT(data->multi);
683
8.78k
    if(data->multi->ipv6_up == IPV6_UNKNOWN) {
684
6.06k
      bool works = Curl_ipv6works(NULL);
685
6.06k
      data->multi->ipv6_up = works ? IPV6_WORKS : IPV6_DEAD;
686
6.06k
    }
687
8.78k
    return data->multi->ipv6_up == IPV6_WORKS;
688
8.78k
  }
689
6.06k
  else {
690
6.06k
    int ipv6_works = -1;
691
    /* probe to see if we have a working IPv6 stack */
692
6.06k
    curl_socket_t s = CURL_SOCKET(PF_INET6, SOCK_DGRAM, 0);
693
6.06k
    if(s == CURL_SOCKET_BAD)
694
      /* an IPv6 address was requested but we cannot get/use one */
695
0
      ipv6_works = 0;
696
6.06k
    else {
697
6.06k
      ipv6_works = 1;
698
6.06k
      sclose(s);
699
6.06k
    }
700
6.06k
    return ipv6_works > 0;
701
6.06k
  }
702
14.8k
}
703
#endif /* USE_IPV6 */
704
705
/*
706
 * Curl_host_is_ipnum() returns TRUE if the given string is a numerical IPv4
707
 * (or IPv6 if supported) address.
708
 */
709
bool Curl_host_is_ipnum(const char *hostname)
710
162k
{
711
162k
  struct in_addr in;
712
162k
#ifdef USE_IPV6
713
162k
  struct in6_addr in6;
714
162k
#endif
715
162k
  if(curlx_inet_pton(AF_INET, hostname, &in) > 0
716
127k
#ifdef USE_IPV6
717
127k
     || curlx_inet_pton(AF_INET6, hostname, &in6) > 0
718
162k
#endif
719
162k
    )
720
36.4k
    return TRUE;
721
125k
  return FALSE;
722
162k
}
723
724
/* return TRUE if 'part' is a case insensitive tail of 'full' */
725
static bool tailmatch(const char *full, size_t flen,
726
                      const char *part, size_t plen)
727
19.7k
{
728
19.7k
  if(plen > flen)
729
17.9k
    return FALSE;
730
1.80k
  return curl_strnequal(part, &full[flen - plen], plen);
731
19.7k
}
732
733
static bool can_resolve_ip_version(struct Curl_easy *data, int ip_version)
734
4.73k
{
735
4.73k
#ifdef CURLRES_IPV6
736
4.73k
  if(ip_version == CURL_IPRESOLVE_V6 && !Curl_ipv6works(data))
737
0
    return FALSE;
738
#elif defined(CURLRES_IPV4)
739
  (void)data;
740
  if(ip_version == CURL_IPRESOLVE_V6)
741
    return FALSE;
742
#else
743
#error either CURLRES_IPV6 or CURLRES_IPV4 need to be defined
744
#endif
745
4.73k
  return TRUE;
746
4.73k
}
747
748
static CURLcode store_negative_resolve(struct Curl_easy *data,
749
                                       const char *host,
750
                                       int port)
751
4.95k
{
752
4.95k
  struct Curl_dnscache *dnscache = dnscache_get(data);
753
4.95k
  struct Curl_dns_entry *dns;
754
4.95k
  DEBUGASSERT(dnscache);
755
4.95k
  if(!dnscache)
756
0
    return CURLE_FAILED_INIT;
757
758
  /* put this new host in the cache */
759
4.95k
  dns = dnscache_add_addr(data, dnscache, NULL, host, 0, port, FALSE);
760
4.95k
  if(dns) {
761
    /* release the returned reference; the cache itself will keep the
762
     * entry alive: */
763
4.95k
    dns->refcount--;
764
4.95k
    infof(data, "Store negative name resolve for %s:%d", host, port);
765
4.95k
    return CURLE_OK;
766
4.95k
  }
767
0
  return CURLE_OUT_OF_MEMORY;
768
4.95k
}
769
770
/*
771
 * Curl_resolv() is the main name resolve function within libcurl. It resolves
772
 * a name and returns a pointer to the entry in the 'entry' argument. This
773
 * function might return immediately if we are using asynch resolves. See the
774
 * return codes.
775
 *
776
 * The cache entry we return will get its 'inuse' counter increased when this
777
 * function is used. You MUST call Curl_resolv_unlink() later (when you are
778
 * done using this struct) to decrease the reference counter again.
779
 *
780
 * Return codes:
781
 * CURLE_OK = success, *entry set to non-NULL
782
 * CURLE_AGAIN = resolving in progress, *entry == NULL
783
 * CURLE_COULDNT_RESOLVE_HOST = error, *entry == NULL
784
 * CURLE_OPERATION_TIMEDOUT = timeout expired, *entry == NULL
785
 */
786
CURLcode Curl_resolv(struct Curl_easy *data,
787
                     const char *hostname,
788
                     int port,
789
                     int ip_version,
790
                     bool allowDOH,
791
                     struct Curl_dns_entry **entry)
792
124k
{
793
124k
  struct Curl_dnscache *dnscache = dnscache_get(data);
794
124k
  struct Curl_dns_entry *dns = NULL;
795
124k
  struct Curl_addrinfo *addr = NULL;
796
124k
  bool respwait = FALSE;
797
124k
  size_t hostname_len;
798
124k
  CURLcode result = CURLE_COULDNT_RESOLVE_HOST;
799
800
124k
  *entry = NULL;
801
802
124k
#ifndef CURL_DISABLE_DOH
803
124k
  data->conn->bits.doh = FALSE; /* default is not */
804
#else
805
  (void)allowDOH;
806
#endif
807
124k
  DEBUGASSERT(dnscache);
808
124k
  if(!dnscache) {
809
0
    result = CURLE_BAD_FUNCTION_ARGUMENT;
810
0
    goto error;
811
0
  }
812
813
  /* We should intentionally error and not resolve .onion TLDs */
814
124k
  hostname_len = strlen(hostname);
815
124k
  DEBUGASSERT(hostname_len);
816
124k
  if(hostname_len >= 7 &&
817
113k
     (curl_strequal(&hostname[hostname_len - 6], ".onion") ||
818
113k
      curl_strequal(&hostname[hostname_len - 7], ".onion."))) {
819
0
    failf(data, "Not resolving .onion address (RFC 7686)");
820
0
    goto error;
821
0
  }
822
823
  /* Let's check our DNS cache first */
824
124k
  dnscache_lock(data, dnscache);
825
124k
  dns = fetch_addr(data, dnscache, hostname, port, ip_version);
826
124k
  if(dns)
827
5.78k
    dns->refcount++; /* we pass out the reference. */
828
124k
  dnscache_unlock(data, dnscache);
829
124k
  if(dns) {
830
5.78k
    infof(data, "Hostname %s was found in DNS cache", hostname);
831
5.78k
    result = CURLE_OK;
832
5.78k
    goto out;
833
5.78k
  }
834
835
  /* No luck, we need to resolve hostname. Notify user callback. */
836
118k
  if(data->set.resolver_start) {
837
0
    void *resolver = NULL;
838
0
    int st;
839
0
#ifdef CURLRES_ASYNCH
840
0
    result = Curl_async_get_impl(data, &resolver);
841
0
    if(result)
842
0
      goto error;
843
0
#endif
844
0
    Curl_set_in_callback(data, TRUE);
845
0
    st = data->set.resolver_start(resolver, NULL,
846
0
                                  data->set.resolver_start_client);
847
0
    Curl_set_in_callback(data, FALSE);
848
0
    if(st) {
849
0
      result = CURLE_ABORTED_BY_CALLBACK;
850
0
      goto error;
851
0
    }
852
0
  }
853
854
118k
  if(Curl_is_ipaddr(hostname)) {
855
107k
#ifndef USE_RESOLVE_ON_IPS
856
    /* shortcut literal IP addresses, if we are not told to resolve them. */
857
107k
    result = Curl_str2addr(hostname, port, &addr);
858
107k
    if(result)
859
0
      goto error;
860
107k
    goto out;
861
107k
#endif
862
107k
  }
863
864
10.4k
  if(curl_strequal(hostname, "localhost") ||
865
10.0k
     curl_strequal(hostname, "localhost.") ||
866
9.95k
     tailmatch(hostname, hostname_len, STRCONST(".localhost")) ||
867
9.79k
     tailmatch(hostname, hostname_len, STRCONST(".localhost."))) {
868
763
    addr = get_localhost(port, hostname);
869
763
    result = addr ? CURLE_OK : CURLE_OUT_OF_MEMORY;
870
763
  }
871
9.71k
#ifndef CURL_DISABLE_DOH
872
9.71k
  else if(!Curl_is_ipaddr(hostname) && allowDOH && data->set.doh) {
873
4.98k
    result = Curl_doh(data, hostname, port, ip_version);
874
4.98k
    respwait = TRUE;
875
4.98k
  }
876
4.73k
#endif
877
4.73k
  else {
878
    /* Can we provide the requested IP specifics in resolving? */
879
4.73k
    if(!can_resolve_ip_version(data, ip_version)) {
880
0
      result = CURLE_COULDNT_RESOLVE_HOST;
881
0
      goto error;
882
0
    }
883
884
4.73k
#ifdef CURLRES_ASYNCH
885
4.73k
    result = Curl_async_getaddrinfo(data, hostname, port, ip_version);
886
4.73k
    respwait = TRUE;
887
#else
888
    respwait = FALSE; /* no async waiting here */
889
    addr = Curl_sync_getaddrinfo(data, hostname, port, ip_version);
890
    if(addr)
891
      result = CURLE_OK;
892
#endif
893
4.73k
  }
894
895
124k
out:
896
  /* We either have found a `dns` or looked up the `addr` or `respwait` is set
897
   * for an async operation. Everything else is a failure to resolve. */
898
124k
  if(result)
899
106
    ;
900
123k
  else if(dns) {
901
5.78k
    if(!dns->addr) {
902
1.32k
      infof(data, "Negative DNS entry");
903
1.32k
      dns->refcount--;
904
1.32k
      return CURLE_COULDNT_RESOLVE_HOST;
905
1.32k
    }
906
4.46k
    *entry = dns;
907
4.46k
    return CURLE_OK;
908
5.78k
  }
909
118k
  else if(addr) {
910
    /* we got a response, create a dns entry, add to cache, return */
911
108k
    dns = Curl_dnscache_mk_entry(data, addr, hostname, 0, port, FALSE);
912
108k
    if(!dns || Curl_dnscache_add(data, dns)) {
913
      /* this is OOM or similar, do not store such negative resolves */
914
0
      Curl_freeaddrinfo(addr);
915
0
      if(dns)
916
        /* avoid a dangling pointer to addr in the dying dns entry */
917
0
        dns->addr = NULL;
918
0
      result = CURLE_OUT_OF_MEMORY;
919
0
      goto error;
920
0
    }
921
108k
    show_resolve_info(data, dns);
922
108k
    *entry = dns;
923
108k
    return CURLE_OK;
924
108k
  }
925
9.61k
  else if(respwait) {
926
9.61k
    if(!Curl_resolv_check(data, &dns)) {
927
9.61k
      *entry = dns;
928
9.61k
      return dns ? CURLE_OK : CURLE_AGAIN;
929
9.61k
    }
930
0
    result = CURLE_COULDNT_RESOLVE_HOST;
931
0
  }
932
106
error:
933
106
  if(dns)
934
0
    Curl_resolv_unlink(data, &dns);
935
106
  Curl_async_shutdown(data);
936
106
  if(result == CURLE_COULDNT_RESOLVE_HOST)
937
0
    store_negative_resolve(data, hostname, port);
938
106
  DEBUGASSERT(result);
939
106
  return result;
940
106
}
941
942
CURLcode Curl_resolv_blocking(struct Curl_easy *data,
943
                              const char *hostname,
944
                              int port,
945
                              int ip_version,
946
                              struct Curl_dns_entry **dnsentry)
947
2.74k
{
948
2.74k
  CURLcode result;
949
2.74k
  DEBUGASSERT(hostname && *hostname);
950
2.74k
  *dnsentry = NULL;
951
2.74k
  result = Curl_resolv(data, hostname, port, ip_version, FALSE, dnsentry);
952
2.74k
  switch(result) {
953
1.66k
  case CURLE_OK:
954
1.66k
    DEBUGASSERT(*dnsentry);
955
1.66k
    return CURLE_OK;
956
1.08k
  case CURLE_AGAIN:
957
1.08k
    DEBUGASSERT(!*dnsentry);
958
1.08k
    result = Curl_async_await(data, dnsentry);
959
1.08k
    if(result || !*dnsentry) {
960
      /* close the connection, since we cannot return failure here without
961
         cleaning up this connection properly. */
962
769
      connclose(data->conn, "async resolve failed");
963
769
    }
964
1.08k
    return result;
965
0
  default:
966
0
    return result;
967
2.74k
  }
968
2.74k
}
969
970
#ifdef USE_ALARM_TIMEOUT
971
/*
972
 * This signal handler jumps back into the main libcurl code and continues
973
 * execution. This effectively causes the remainder of the application to run
974
 * within a signal handler which is nonportable and could lead to problems.
975
 */
976
CURL_NORETURN static void alarmfunc(int sig)
977
{
978
  (void)sig;
979
  siglongjmp(curl_jmpenv, 1);
980
}
981
#endif /* USE_ALARM_TIMEOUT */
982
983
/*
984
 * Curl_resolv_timeout() is the same as Curl_resolv() but specifies a
985
 * timeout. This function might return immediately if we are using asynch
986
 * resolves. See the return codes.
987
 *
988
 * The cache entry we return will get its 'inuse' counter increased when this
989
 * function is used. You MUST call Curl_resolv_unlink() later (when you are
990
 * done using this struct) to decrease the reference counter again.
991
 *
992
 * If built with a synchronous resolver and use of signals is not
993
 * disabled by the application, then a nonzero timeout will cause a
994
 * timeout after the specified number of milliseconds. Otherwise, timeout
995
 * is ignored.
996
 *
997
 * Return codes:
998
 * CURLE_OK = success, *entry set to non-NULL
999
 * CURLE_AGAIN = resolving in progress, *entry == NULL
1000
 * CURLE_COULDNT_RESOLVE_HOST = error, *entry == NULL
1001
 * CURLE_OPERATION_TIMEDOUT = timeout expired, *entry == NULL
1002
 */
1003
1004
CURLcode Curl_resolv_timeout(struct Curl_easy *data,
1005
                             const char *hostname,
1006
                             int port,
1007
                             int ip_version,
1008
                             struct Curl_dns_entry **entry,
1009
                             timediff_t timeoutms)
1010
120k
{
1011
#ifdef USE_ALARM_TIMEOUT
1012
#ifdef HAVE_SIGACTION
1013
  struct sigaction keep_sigact; /* store the old struct here */
1014
  volatile bool keep_copysig = FALSE; /* whether old sigact has been saved */
1015
  struct sigaction sigact;
1016
#else
1017
#ifdef HAVE_SIGNAL
1018
  void (*keep_sigact)(int);       /* store the old handler here */
1019
#endif /* HAVE_SIGNAL */
1020
#endif /* HAVE_SIGACTION */
1021
  volatile long timeout;
1022
  volatile unsigned int prev_alarm = 0;
1023
#endif /* USE_ALARM_TIMEOUT */
1024
120k
  CURLcode result;
1025
1026
120k
  DEBUGASSERT(hostname && *hostname);
1027
120k
  *entry = NULL;
1028
1029
120k
  if(timeoutms < 0)
1030
    /* got an already expired timeout */
1031
0
    return CURLE_OPERATION_TIMEDOUT;
1032
1033
#ifdef USE_ALARM_TIMEOUT
1034
  if(data->set.no_signal)
1035
    /* Ignore the timeout when signals are disabled */
1036
    timeout = 0;
1037
  else
1038
    timeout = (timeoutms > LONG_MAX) ? LONG_MAX : (long)timeoutms;
1039
1040
  if(!timeout
1041
#ifndef CURL_DISABLE_DOH
1042
     || data->set.doh
1043
#endif
1044
    )
1045
    /* USE_ALARM_TIMEOUT defined, but no timeout actually requested or resolve
1046
       done using DoH */
1047
    return Curl_resolv(data, hostname, port, ip_version, TRUE, entry);
1048
1049
  if(timeout < 1000) {
1050
    /* The alarm() function only provides integer second resolution, so if
1051
       we want to wait less than one second we must bail out already now. */
1052
    failf(data,
1053
          "remaining timeout of %ld too small to resolve via SIGALRM method",
1054
          timeout);
1055
    return CURLE_OPERATION_TIMEDOUT;
1056
  }
1057
  /* This allows us to time-out from the name resolver, as the timeout
1058
     will generate a signal and we will siglongjmp() from that here.
1059
     This technique has problems (see alarmfunc).
1060
     This should be the last thing we do before calling Curl_resolv(),
1061
     as otherwise we would have to worry about variables that get modified
1062
     before we invoke Curl_resolv() (and thus use "volatile"). */
1063
  curl_simple_lock_lock(&curl_jmpenv_lock);
1064
1065
  if(sigsetjmp(curl_jmpenv, 1)) {
1066
    /* this is coming from a siglongjmp() after an alarm signal */
1067
    failf(data, "name lookup timed out");
1068
    result = CURLE_OPERATION_TIMEDOUT;
1069
    goto clean_up;
1070
  }
1071
  else {
1072
    /*************************************************************
1073
     * Set signal handler to catch SIGALRM
1074
     * Store the old value to be able to set it back later!
1075
     *************************************************************/
1076
#ifdef HAVE_SIGACTION
1077
    sigaction(SIGALRM, NULL, &sigact);
1078
    keep_sigact = sigact;
1079
    keep_copysig = TRUE; /* yes, we have a copy */
1080
    sigact.sa_handler = alarmfunc;
1081
#ifdef SA_RESTART
1082
    /* HP-UX does not have SA_RESTART but defaults to that behavior! */
1083
    sigact.sa_flags &= ~SA_RESTART;
1084
#endif
1085
    /* now set the new struct */
1086
    sigaction(SIGALRM, &sigact, NULL);
1087
#else /* HAVE_SIGACTION */
1088
    /* no sigaction(), revert to the much lamer signal() */
1089
#ifdef HAVE_SIGNAL
1090
    keep_sigact = signal(SIGALRM, alarmfunc);
1091
#endif
1092
#endif /* HAVE_SIGACTION */
1093
1094
    /* alarm() makes a signal get sent when the timeout fires off, and that
1095
       will abort system calls */
1096
    prev_alarm = alarm(curlx_sltoui(timeout / 1000L));
1097
  }
1098
1099
#else /* !USE_ALARM_TIMEOUT */
1100
#ifndef CURLRES_ASYNCH
1101
  if(timeoutms)
1102
    infof(data, "timeout on name lookup is not supported");
1103
#else
1104
120k
  (void)timeoutms;
1105
120k
#endif
1106
120k
#endif /* USE_ALARM_TIMEOUT */
1107
1108
  /* Perform the actual name resolution. This might be interrupted by an
1109
   * alarm if it takes too long.
1110
   */
1111
120k
  result = Curl_resolv(data, hostname, port, ip_version, TRUE, entry);
1112
1113
#ifdef USE_ALARM_TIMEOUT
1114
clean_up:
1115
1116
  if(!prev_alarm)
1117
    /* deactivate a possibly active alarm before uninstalling the handler */
1118
    alarm(0);
1119
1120
#ifdef HAVE_SIGACTION
1121
  if(keep_copysig) {
1122
    /* we got a struct as it looked before, now put that one back nice
1123
       and clean */
1124
    sigaction(SIGALRM, &keep_sigact, NULL); /* put it back */
1125
  }
1126
#else
1127
#ifdef HAVE_SIGNAL
1128
  /* restore the previous SIGALRM handler */
1129
  signal(SIGALRM, keep_sigact);
1130
#endif
1131
#endif /* HAVE_SIGACTION */
1132
1133
  curl_simple_lock_unlock(&curl_jmpenv_lock);
1134
1135
  /* switch back the alarm() to either zero or to what it was before minus
1136
     the time we spent until now! */
1137
  if(prev_alarm) {
1138
    /* there was an alarm() set before us, now put it back */
1139
    timediff_t elapsed_secs = curlx_ptimediff_ms(Curl_pgrs_now(data),
1140
                                                 &data->conn->created) / 1000;
1141
1142
    /* the alarm period is counted in even number of seconds */
1143
    unsigned long alarm_set = (unsigned long)(prev_alarm - elapsed_secs);
1144
1145
    if(!alarm_set ||
1146
       ((alarm_set >= 0x80000000) && (prev_alarm < 0x80000000))) {
1147
      /* if the alarm time-left reached zero or turned "negative" (counted
1148
         with unsigned values), we should fire off a SIGALRM here, but we
1149
         will not, and zero would be to switch it off so we never set it to
1150
         less than 1! */
1151
      alarm(1);
1152
      result = CURLE_OPERATION_TIMEDOUT;
1153
      failf(data, "Previous alarm fired off");
1154
    }
1155
    else
1156
      alarm((unsigned int)alarm_set);
1157
  }
1158
#endif /* USE_ALARM_TIMEOUT */
1159
1160
120k
  return result;
1161
120k
}
1162
1163
static void dnscache_entry_free(struct Curl_dns_entry *dns)
1164
115k
{
1165
115k
  Curl_freeaddrinfo(dns->addr);
1166
#ifdef USE_HTTPSRR
1167
  if(dns->hinfo) {
1168
    Curl_httpsrr_cleanup(dns->hinfo);
1169
    curlx_free(dns->hinfo);
1170
  }
1171
#endif
1172
115k
  curlx_free(dns);
1173
115k
}
1174
1175
/*
1176
 * Curl_resolv_unlink() releases a reference to the given cached DNS entry.
1177
 * When the reference count reaches 0, the entry is destroyed. It is important
1178
 * that only one unlink is made for each Curl_resolv() call.
1179
 *
1180
 * May be called with 'data' == NULL for global cache.
1181
 */
1182
void Curl_resolv_unlink(struct Curl_easy *data, struct Curl_dns_entry **pdns)
1183
1.17M
{
1184
1.17M
  if(*pdns) {
1185
115k
    struct Curl_dnscache *dnscache = dnscache_get(data);
1186
115k
    struct Curl_dns_entry *dns = *pdns;
1187
115k
    *pdns = NULL;
1188
115k
    dnscache_lock(data, dnscache);
1189
115k
    dns->refcount--;
1190
115k
    if(dns->refcount == 0)
1191
1.17k
      dnscache_entry_free(dns);
1192
115k
    dnscache_unlock(data, dnscache);
1193
115k
  }
1194
1.17M
}
1195
1196
static void dnscache_entry_dtor(void *entry)
1197
114k
{
1198
114k
  struct Curl_dns_entry *dns = (struct Curl_dns_entry *)entry;
1199
114k
  DEBUGASSERT(dns && (dns->refcount > 0));
1200
114k
  dns->refcount--;
1201
114k
  if(dns->refcount == 0)
1202
114k
    dnscache_entry_free(dns);
1203
114k
}
1204
1205
/*
1206
 * Curl_dnscache_init() inits a new DNS cache.
1207
 */
1208
void Curl_dnscache_init(struct Curl_dnscache *dns, size_t size)
1209
174k
{
1210
174k
  Curl_hash_init(&dns->entries, size, Curl_hash_str, curlx_str_key_compare,
1211
174k
                 dnscache_entry_dtor);
1212
174k
}
1213
1214
void Curl_dnscache_destroy(struct Curl_dnscache *dns)
1215
174k
{
1216
174k
  Curl_hash_destroy(&dns->entries);
1217
174k
}
1218
1219
CURLcode Curl_loadhostpairs(struct Curl_easy *data)
1220
0
{
1221
0
  struct Curl_dnscache *dnscache = dnscache_get(data);
1222
0
  struct curl_slist *hostp;
1223
1224
0
  if(!dnscache)
1225
0
    return CURLE_FAILED_INIT;
1226
1227
  /* Default is no wildcard found */
1228
0
  data->state.wildcard_resolve = FALSE;
1229
1230
0
  for(hostp = data->state.resolve; hostp; hostp = hostp->next) {
1231
0
    char entry_id[MAX_HOSTCACHE_LEN];
1232
0
    const char *host = hostp->data;
1233
0
    struct Curl_str source;
1234
0
    if(!host)
1235
0
      continue;
1236
0
    if(*host == '-') {
1237
0
      curl_off_t num = 0;
1238
0
      size_t entry_len;
1239
0
      host++;
1240
0
      if(!curlx_str_single(&host, '[')) {
1241
0
        if(curlx_str_until(&host, &source, MAX_IPADR_LEN, ']') ||
1242
0
           curlx_str_single(&host, ']') ||
1243
0
           curlx_str_single(&host, ':'))
1244
0
          continue;
1245
0
      }
1246
0
      else {
1247
0
        if(curlx_str_until(&host, &source, 4096, ':') ||
1248
0
           curlx_str_single(&host, ':')) {
1249
0
          continue;
1250
0
        }
1251
0
      }
1252
1253
0
      if(!curlx_str_number(&host, &num, 0xffff)) {
1254
        /* Create an entry id, based upon the hostname and port */
1255
0
        entry_len = create_dnscache_id(curlx_str(&source),
1256
0
                                       curlx_strlen(&source), (int)num,
1257
0
                                       entry_id, sizeof(entry_id));
1258
0
        dnscache_lock(data, dnscache);
1259
        /* delete entry, ignore if it did not exist */
1260
0
        Curl_hash_delete(&dnscache->entries, entry_id, entry_len + 1);
1261
0
        dnscache_unlock(data, dnscache);
1262
0
      }
1263
0
    }
1264
0
    else {
1265
0
      struct Curl_dns_entry *dns;
1266
0
      struct Curl_addrinfo *head = NULL, *tail = NULL;
1267
0
      size_t entry_len;
1268
0
      char address[64];
1269
0
#ifndef CURL_DISABLE_VERBOSE_STRINGS
1270
0
      const char *addresses = NULL;
1271
0
#endif
1272
0
      curl_off_t port = 0;
1273
0
      bool permanent = TRUE;
1274
0
      bool error = TRUE;
1275
1276
0
      if(*host == '+') {
1277
0
        host++;
1278
0
        permanent = FALSE;
1279
0
      }
1280
0
      if(!curlx_str_single(&host, '[')) {
1281
0
        if(curlx_str_until(&host, &source, MAX_IPADR_LEN, ']') ||
1282
0
           curlx_str_single(&host, ']'))
1283
0
          continue;
1284
0
      }
1285
0
      else {
1286
0
        if(curlx_str_until(&host, &source, 4096, ':'))
1287
0
          continue;
1288
0
      }
1289
0
      if(curlx_str_single(&host, ':') ||
1290
0
         curlx_str_number(&host, &port, 0xffff) ||
1291
0
         curlx_str_single(&host, ':'))
1292
0
        goto err;
1293
1294
0
#ifndef CURL_DISABLE_VERBOSE_STRINGS
1295
0
      addresses = host;
1296
0
#endif
1297
1298
      /* start the address section */
1299
0
      while(*host) {
1300
0
        struct Curl_str target;
1301
0
        struct Curl_addrinfo *ai;
1302
0
        CURLcode result;
1303
1304
0
        if(!curlx_str_single(&host, '[')) {
1305
0
          if(curlx_str_until(&host, &target, MAX_IPADR_LEN, ']') ||
1306
0
             curlx_str_single(&host, ']'))
1307
0
            goto err;
1308
0
        }
1309
0
        else {
1310
0
          if(curlx_str_until(&host, &target, 4096, ',')) {
1311
0
            if(curlx_str_single(&host, ','))
1312
0
              goto err;
1313
            /* survive nothing but just a comma */
1314
0
            continue;
1315
0
          }
1316
0
        }
1317
#ifndef USE_IPV6
1318
        if(memchr(curlx_str(&target), ':', curlx_strlen(&target))) {
1319
          infof(data, "Ignoring resolve address '%.*s', missing IPv6 support.",
1320
                (int)curlx_strlen(&target), curlx_str(&target));
1321
          if(curlx_str_single(&host, ','))
1322
            goto err;
1323
          continue;
1324
        }
1325
#endif
1326
1327
0
        if(curlx_strlen(&target) >= sizeof(address))
1328
0
          goto err;
1329
1330
0
        memcpy(address, curlx_str(&target), curlx_strlen(&target));
1331
0
        address[curlx_strlen(&target)] = '\0';
1332
1333
0
        result = Curl_str2addr(address, (int)port, &ai);
1334
0
        if(result) {
1335
0
          infof(data, "Resolve address '%s' found illegal", address);
1336
0
          goto err;
1337
0
        }
1338
1339
0
        if(tail) {
1340
0
          tail->ai_next = ai;
1341
0
          tail = tail->ai_next;
1342
0
        }
1343
0
        else {
1344
0
          head = tail = ai;
1345
0
        }
1346
0
        if(curlx_str_single(&host, ','))
1347
0
          break;
1348
0
      }
1349
1350
0
      if(!head)
1351
0
        goto err;
1352
1353
0
      error = FALSE;
1354
0
err:
1355
0
      if(error) {
1356
0
        failf(data, "Could not parse CURLOPT_RESOLVE entry '%s'", hostp->data);
1357
0
        Curl_freeaddrinfo(head);
1358
0
        return CURLE_SETOPT_OPTION_SYNTAX;
1359
0
      }
1360
1361
      /* Create an entry id, based upon the hostname and port */
1362
0
      entry_len = create_dnscache_id(curlx_str(&source), curlx_strlen(&source),
1363
0
                                     (int)port,
1364
0
                                     entry_id, sizeof(entry_id));
1365
1366
0
      dnscache_lock(data, dnscache);
1367
1368
      /* See if it is already in our dns cache */
1369
0
      dns = Curl_hash_pick(&dnscache->entries, entry_id, entry_len + 1);
1370
1371
0
      if(dns) {
1372
0
        infof(data, "RESOLVE %.*s:%" CURL_FORMAT_CURL_OFF_T
1373
0
              " - old addresses discarded", (int)curlx_strlen(&source),
1374
0
              curlx_str(&source), port);
1375
        /* delete old entry, there are two reasons for this
1376
         1. old entry may have different addresses.
1377
         2. even if entry with correct addresses is already in the cache,
1378
            but if it is close to expire, then by the time next http
1379
            request is made, it can get expired and pruned because old
1380
            entry is not necessarily marked as permanent.
1381
         3. when adding a non-permanent entry, we want it to remove and
1382
            replace an existing permanent entry.
1383
         4. when adding a non-permanent entry, we want it to get a "fresh"
1384
            timeout that starts _now_. */
1385
1386
0
        Curl_hash_delete(&dnscache->entries, entry_id, entry_len + 1);
1387
0
      }
1388
1389
      /* put this new host in the cache */
1390
0
      dns = dnscache_add_addr(data, dnscache, head, curlx_str(&source),
1391
0
                              curlx_strlen(&source), (int)port, permanent);
1392
0
      if(dns)
1393
        /* release the returned reference; the cache itself will keep the
1394
         * entry alive: */
1395
0
        dns->refcount--;
1396
0
      else
1397
0
        Curl_freeaddrinfo(head);
1398
1399
0
      dnscache_unlock(data, dnscache);
1400
1401
0
      if(!dns)
1402
0
        return CURLE_OUT_OF_MEMORY;
1403
1404
0
#ifndef CURL_DISABLE_VERBOSE_STRINGS
1405
0
      infof(data, "Added %.*s:%" CURL_FORMAT_CURL_OFF_T ":%s to DNS cache%s",
1406
0
            (int)curlx_strlen(&source), curlx_str(&source), port, addresses,
1407
0
            permanent ? "" : " (non-permanent)");
1408
0
#endif
1409
1410
      /* Wildcard hostname */
1411
0
      if(curlx_str_casecompare(&source, "*")) {
1412
0
        infof(data, "RESOLVE *:%" CURL_FORMAT_CURL_OFF_T " using wildcard",
1413
0
              port);
1414
0
        data->state.wildcard_resolve = TRUE;
1415
0
      }
1416
0
    }
1417
0
  }
1418
0
  data->state.resolve = NULL; /* dealt with now */
1419
1420
0
  return CURLE_OK;
1421
0
}
1422
1423
#ifndef CURL_DISABLE_VERBOSE_STRINGS
1424
static void show_resolve_info(struct Curl_easy *data,
1425
                              struct Curl_dns_entry *dns)
1426
109k
{
1427
109k
  struct Curl_addrinfo *a;
1428
109k
  CURLcode result = CURLE_OK;
1429
109k
#ifdef CURLRES_IPV6
1430
109k
  struct dynbuf out[2];
1431
#else
1432
  struct dynbuf out[1];
1433
#endif
1434
109k
  DEBUGASSERT(data);
1435
109k
  DEBUGASSERT(dns);
1436
1437
109k
  if(!data->set.verbose ||
1438
     /* ignore no name or numerical IP addresses */
1439
0
     !dns->hostname[0] || Curl_host_is_ipnum(dns->hostname))
1440
109k
    return;
1441
1442
0
  a = dns->addr;
1443
1444
0
  infof(data, "Host %s:%d was resolved.",
1445
0
        (dns->hostname[0] ? dns->hostname : "(none)"), dns->hostport);
1446
1447
0
  curlx_dyn_init(&out[0], 1024);
1448
0
#ifdef CURLRES_IPV6
1449
0
  curlx_dyn_init(&out[1], 1024);
1450
0
#endif
1451
1452
0
  while(a) {
1453
0
    if(
1454
0
#ifdef CURLRES_IPV6
1455
0
       a->ai_family == PF_INET6 ||
1456
0
#endif
1457
0
       a->ai_family == PF_INET) {
1458
0
      char buf[MAX_IPADR_LEN];
1459
0
      struct dynbuf *d = &out[(a->ai_family != PF_INET)];
1460
0
      Curl_printable_address(a, buf, sizeof(buf));
1461
0
      if(curlx_dyn_len(d))
1462
0
        result = curlx_dyn_addn(d, ", ", 2);
1463
0
      if(!result)
1464
0
        result = curlx_dyn_add(d, buf);
1465
0
      if(result) {
1466
0
        infof(data, "too many IP, cannot show");
1467
0
        goto fail;
1468
0
      }
1469
0
    }
1470
0
    a = a->ai_next;
1471
0
  }
1472
1473
0
#ifdef CURLRES_IPV6
1474
0
  infof(data, "IPv6: %s",
1475
0
        (curlx_dyn_len(&out[1]) ? curlx_dyn_ptr(&out[1]) : "(none)"));
1476
0
#endif
1477
0
  infof(data, "IPv4: %s",
1478
0
        (curlx_dyn_len(&out[0]) ? curlx_dyn_ptr(&out[0]) : "(none)"));
1479
1480
0
fail:
1481
0
  curlx_dyn_free(&out[0]);
1482
0
#ifdef CURLRES_IPV6
1483
0
  curlx_dyn_free(&out[1]);
1484
0
#endif
1485
0
}
1486
#endif
1487
1488
#ifdef USE_CURL_ASYNC
1489
CURLcode Curl_resolv_check(struct Curl_easy *data,
1490
                           struct Curl_dns_entry **dns)
1491
2.94M
{
1492
2.94M
  CURLcode result;
1493
1494
  /* If async resolving is ongoing, this must be set */
1495
2.94M
  if(!data->state.async.hostname)
1496
0
    return CURLE_FAILED_INIT;
1497
1498
  /* check if we have the name resolved by now (from someone else) */
1499
2.94M
  *dns = Curl_dnscache_get(data, data->state.async.hostname,
1500
2.94M
                           data->state.async.port,
1501
2.94M
                           data->state.async.ip_version);
1502
2.94M
  if(*dns) {
1503
    /* Tell a possibly async resolver we no longer need the results. */
1504
166
    infof(data, "Hostname '%s' was found in DNS cache",
1505
166
          data->state.async.hostname);
1506
166
    Curl_async_shutdown(data);
1507
166
    data->state.async.dns = *dns;
1508
166
    data->state.async.done = TRUE;
1509
166
    return CURLE_OK;
1510
166
  }
1511
1512
2.94M
#ifndef CURL_DISABLE_DOH
1513
2.94M
  if(data->conn->bits.doh) {
1514
1.96M
    result = Curl_doh_is_resolved(data, dns);
1515
1.96M
    if(result)
1516
3.16k
      Curl_resolver_error(data, NULL);
1517
1.96M
  }
1518
983k
  else
1519
983k
#endif
1520
983k
  result = Curl_async_is_resolved(data, dns);
1521
2.94M
  if(*dns)
1522
465
    show_resolve_info(data, *dns);
1523
2.94M
  if((result == CURLE_COULDNT_RESOLVE_HOST) ||
1524
2.94M
     (result == CURLE_COULDNT_RESOLVE_PROXY))
1525
4.95k
    store_negative_resolve(data, data->state.async.hostname,
1526
4.95k
                           data->state.async.port);
1527
2.94M
  return result;
1528
2.94M
}
1529
#endif
1530
1531
CURLcode Curl_resolv_pollset(struct Curl_easy *data,
1532
                             struct easy_pollset *ps)
1533
709k
{
1534
709k
#ifdef CURLRES_ASYNCH
1535
709k
#ifndef CURL_DISABLE_DOH
1536
709k
  if(data->conn->bits.doh)
1537
    /* nothing to wait for during DoH resolve, those handles have their own
1538
       sockets */
1539
8.79k
    return CURLE_OK;
1540
701k
#endif
1541
701k
  return Curl_async_pollset(data, ps);
1542
#else
1543
  (void)data;
1544
  (void)ps;
1545
  return CURLE_OK;
1546
#endif
1547
709k
}
1548
1549
/* Call this function after Curl_connect() has returned async=TRUE and
1550
   then a successful name resolve has been received.
1551
1552
   Note: this function disconnects and frees the conn data in case of
1553
   resolve failure */
1554
CURLcode Curl_once_resolved(struct Curl_easy *data,
1555
                            struct Curl_dns_entry *dns,
1556
                            bool *protocol_done)
1557
618
{
1558
618
  CURLcode result;
1559
618
  struct connectdata *conn = data->conn;
1560
1561
618
#ifdef USE_CURL_ASYNC
1562
618
  if(data->state.async.dns) {
1563
618
    DEBUGASSERT(data->state.async.dns == dns);
1564
618
    data->state.async.dns = NULL;
1565
618
  }
1566
618
#endif
1567
1568
618
  result = Curl_setup_conn(data, dns, protocol_done);
1569
1570
618
  if(result) {
1571
0
    Curl_detach_connection(data);
1572
0
    Curl_conn_terminate(data, conn, TRUE);
1573
0
  }
1574
618
  return result;
1575
618
}
1576
1577
/*
1578
 * Curl_resolver_error() calls failf() with the appropriate message after a
1579
 * resolve error
1580
 */
1581
1582
#ifdef USE_CURL_ASYNC
1583
CURLcode Curl_resolver_error(struct Curl_easy *data, const char *detail)
1584
5.72k
{
1585
5.72k
  struct connectdata *conn = data->conn;
1586
5.72k
  const char *host_or_proxy = "host";
1587
5.72k
  const char *name = conn->host.dispname;
1588
5.72k
  CURLcode result = CURLE_COULDNT_RESOLVE_HOST;
1589
1590
5.72k
#ifndef CURL_DISABLE_PROXY
1591
5.72k
  if(conn->bits.proxy) {
1592
3.86k
    host_or_proxy = "proxy";
1593
3.86k
    result = CURLE_COULDNT_RESOLVE_PROXY;
1594
3.86k
    name = conn->socks_proxy.host.name ? conn->socks_proxy.host.dispname :
1595
3.86k
      conn->http_proxy.host.dispname;
1596
3.86k
  }
1597
5.72k
#endif
1598
1599
5.72k
  failf(data, "Could not resolve %s: %s%s%s%s", host_or_proxy, name,
1600
5.72k
        detail ? " (" : "", detail ? detail : "", detail ? ")" : "");
1601
5.72k
  return result;
1602
5.72k
}
1603
#endif /* USE_CURL_ASYNC */