Annotation of embedaddon/curl/lib/hostip.c, revision 1.1
1.1 ! misho 1: /***************************************************************************
! 2: * _ _ ____ _
! 3: * Project ___| | | | _ \| |
! 4: * / __| | | | |_) | |
! 5: * | (__| |_| | _ <| |___
! 6: * \___|\___/|_| \_\_____|
! 7: *
! 8: * Copyright (C) 1998 - 2020, 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.haxx.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: ***************************************************************************/
! 22:
! 23: #include "curl_setup.h"
! 24:
! 25: #ifdef HAVE_NETINET_IN_H
! 26: #include <netinet/in.h>
! 27: #endif
! 28: #ifdef HAVE_NETINET_IN6_H
! 29: #include <netinet/in6.h>
! 30: #endif
! 31: #ifdef HAVE_NETDB_H
! 32: #include <netdb.h>
! 33: #endif
! 34: #ifdef HAVE_ARPA_INET_H
! 35: #include <arpa/inet.h>
! 36: #endif
! 37: #ifdef __VMS
! 38: #include <in.h>
! 39: #include <inet.h>
! 40: #endif
! 41:
! 42: #ifdef HAVE_SETJMP_H
! 43: #include <setjmp.h>
! 44: #endif
! 45: #ifdef HAVE_SIGNAL_H
! 46: #include <signal.h>
! 47: #endif
! 48:
! 49: #ifdef HAVE_PROCESS_H
! 50: #include <process.h>
! 51: #endif
! 52:
! 53: #include "urldata.h"
! 54: #include "sendf.h"
! 55: #include "hostip.h"
! 56: #include "hash.h"
! 57: #include "rand.h"
! 58: #include "share.h"
! 59: #include "strerror.h"
! 60: #include "url.h"
! 61: #include "inet_ntop.h"
! 62: #include "inet_pton.h"
! 63: #include "multiif.h"
! 64: #include "doh.h"
! 65: #include "warnless.h"
! 66: /* The last 3 #include files should be in this order */
! 67: #include "curl_printf.h"
! 68: #include "curl_memory.h"
! 69: #include "memdebug.h"
! 70:
! 71: #if defined(CURLRES_SYNCH) && \
! 72: defined(HAVE_ALARM) && defined(SIGALRM) && defined(HAVE_SIGSETJMP)
! 73: /* alarm-based timeouts can only be used with all the dependencies satisfied */
! 74: #define USE_ALARM_TIMEOUT
! 75: #endif
! 76:
! 77: #define MAX_HOSTCACHE_LEN (255 + 7) /* max FQDN + colon + port number + zero */
! 78:
! 79: /*
! 80: * hostip.c explained
! 81: * ==================
! 82: *
! 83: * The main COMPILE-TIME DEFINES to keep in mind when reading the host*.c
! 84: * source file are these:
! 85: *
! 86: * CURLRES_IPV6 - this host has getaddrinfo() and family, and thus we use
! 87: * that. The host may not be able to resolve IPv6, but we don't really have to
! 88: * take that into account. Hosts that aren't IPv6-enabled have CURLRES_IPV4
! 89: * defined.
! 90: *
! 91: * CURLRES_ARES - is defined if libcurl is built to use c-ares for
! 92: * asynchronous name resolves. This can be Windows or *nix.
! 93: *
! 94: * CURLRES_THREADED - is defined if libcurl is built to run under (native)
! 95: * Windows, and then the name resolve will be done in a new thread, and the
! 96: * supported API will be the same as for ares-builds.
! 97: *
! 98: * If any of the two previous are defined, CURLRES_ASYNCH is defined too. If
! 99: * libcurl is not built to use an asynchronous resolver, CURLRES_SYNCH is
! 100: * defined.
! 101: *
! 102: * The host*.c sources files are split up like this:
! 103: *
! 104: * hostip.c - method-independent resolver functions and utility functions
! 105: * hostasyn.c - functions for asynchronous name resolves
! 106: * hostsyn.c - functions for synchronous name resolves
! 107: * hostip4.c - IPv4 specific functions
! 108: * hostip6.c - IPv6 specific functions
! 109: *
! 110: * The two asynchronous name resolver backends are implemented in:
! 111: * asyn-ares.c - functions for ares-using name resolves
! 112: * asyn-thread.c - functions for threaded name resolves
! 113:
! 114: * The hostip.h is the united header file for all this. It defines the
! 115: * CURLRES_* defines based on the config*.h and curl_setup.h defines.
! 116: */
! 117:
! 118: static void freednsentry(void *freethis);
! 119:
! 120: /*
! 121: * Return # of addresses in a Curl_addrinfo struct
! 122: */
! 123: int Curl_num_addresses(const Curl_addrinfo *addr)
! 124: {
! 125: int i = 0;
! 126: while(addr) {
! 127: addr = addr->ai_next;
! 128: i++;
! 129: }
! 130: return i;
! 131: }
! 132:
! 133: /*
! 134: * Curl_printable_address() returns a printable version of the 1st address
! 135: * given in the 'ai' argument. The result will be stored in the buf that is
! 136: * bufsize bytes big.
! 137: *
! 138: * If the conversion fails, it returns NULL.
! 139: */
! 140: const char *
! 141: Curl_printable_address(const Curl_addrinfo *ai, char *buf, size_t bufsize)
! 142: {
! 143: const struct sockaddr_in *sa4;
! 144: const struct in_addr *ipaddr4;
! 145: #ifdef ENABLE_IPV6
! 146: const struct sockaddr_in6 *sa6;
! 147: const struct in6_addr *ipaddr6;
! 148: #endif
! 149:
! 150: switch(ai->ai_family) {
! 151: case AF_INET:
! 152: sa4 = (const void *)ai->ai_addr;
! 153: ipaddr4 = &sa4->sin_addr;
! 154: return Curl_inet_ntop(ai->ai_family, (const void *)ipaddr4, buf,
! 155: bufsize);
! 156: #ifdef ENABLE_IPV6
! 157: case AF_INET6:
! 158: sa6 = (const void *)ai->ai_addr;
! 159: ipaddr6 = &sa6->sin6_addr;
! 160: return Curl_inet_ntop(ai->ai_family, (const void *)ipaddr6, buf,
! 161: bufsize);
! 162: #endif
! 163: default:
! 164: break;
! 165: }
! 166: return NULL;
! 167: }
! 168:
! 169: /*
! 170: * Create a hostcache id string for the provided host + port, to be used by
! 171: * the DNS caching. Without alloc.
! 172: */
! 173: static void
! 174: create_hostcache_id(const char *name, int port, char *ptr, size_t buflen)
! 175: {
! 176: size_t len = strlen(name);
! 177: if(len > (buflen - 7))
! 178: len = buflen - 7;
! 179: /* store and lower case the name */
! 180: while(len--)
! 181: *ptr++ = (char)TOLOWER(*name++);
! 182: msnprintf(ptr, 7, ":%u", port);
! 183: }
! 184:
! 185: struct hostcache_prune_data {
! 186: long cache_timeout;
! 187: time_t now;
! 188: };
! 189:
! 190: /*
! 191: * This function is set as a callback to be called for every entry in the DNS
! 192: * cache when we want to prune old unused entries.
! 193: *
! 194: * Returning non-zero means remove the entry, return 0 to keep it in the
! 195: * cache.
! 196: */
! 197: static int
! 198: hostcache_timestamp_remove(void *datap, void *hc)
! 199: {
! 200: struct hostcache_prune_data *data =
! 201: (struct hostcache_prune_data *) datap;
! 202: struct Curl_dns_entry *c = (struct Curl_dns_entry *) hc;
! 203:
! 204: return (0 != c->timestamp)
! 205: && (data->now - c->timestamp >= data->cache_timeout);
! 206: }
! 207:
! 208: /*
! 209: * Prune the DNS cache. This assumes that a lock has already been taken.
! 210: */
! 211: static void
! 212: hostcache_prune(struct curl_hash *hostcache, long cache_timeout, time_t now)
! 213: {
! 214: struct hostcache_prune_data user;
! 215:
! 216: user.cache_timeout = cache_timeout;
! 217: user.now = now;
! 218:
! 219: Curl_hash_clean_with_criterium(hostcache,
! 220: (void *) &user,
! 221: hostcache_timestamp_remove);
! 222: }
! 223:
! 224: /*
! 225: * Library-wide function for pruning the DNS cache. This function takes and
! 226: * returns the appropriate locks.
! 227: */
! 228: void Curl_hostcache_prune(struct Curl_easy *data)
! 229: {
! 230: time_t now;
! 231:
! 232: if((data->set.dns_cache_timeout == -1) || !data->dns.hostcache)
! 233: /* cache forever means never prune, and NULL hostcache means
! 234: we can't do it */
! 235: return;
! 236:
! 237: if(data->share)
! 238: Curl_share_lock(data, CURL_LOCK_DATA_DNS, CURL_LOCK_ACCESS_SINGLE);
! 239:
! 240: time(&now);
! 241:
! 242: /* Remove outdated and unused entries from the hostcache */
! 243: hostcache_prune(data->dns.hostcache,
! 244: data->set.dns_cache_timeout,
! 245: now);
! 246:
! 247: if(data->share)
! 248: Curl_share_unlock(data, CURL_LOCK_DATA_DNS);
! 249: }
! 250:
! 251: #ifdef HAVE_SIGSETJMP
! 252: /* Beware this is a global and unique instance. This is used to store the
! 253: return address that we can jump back to from inside a signal handler. This
! 254: is not thread-safe stuff. */
! 255: sigjmp_buf curl_jmpenv;
! 256: #endif
! 257:
! 258: /* lookup address, returns entry if found and not stale */
! 259: static struct Curl_dns_entry *
! 260: fetch_addr(struct connectdata *conn,
! 261: const char *hostname,
! 262: int port)
! 263: {
! 264: struct Curl_dns_entry *dns = NULL;
! 265: size_t entry_len;
! 266: struct Curl_easy *data = conn->data;
! 267: char entry_id[MAX_HOSTCACHE_LEN];
! 268:
! 269: /* Create an entry id, based upon the hostname and port */
! 270: create_hostcache_id(hostname, port, entry_id, sizeof(entry_id));
! 271: entry_len = strlen(entry_id);
! 272:
! 273: /* See if its already in our dns cache */
! 274: dns = Curl_hash_pick(data->dns.hostcache, entry_id, entry_len + 1);
! 275:
! 276: /* No entry found in cache, check if we might have a wildcard entry */
! 277: if(!dns && data->change.wildcard_resolve) {
! 278: create_hostcache_id("*", port, entry_id, sizeof(entry_id));
! 279: entry_len = strlen(entry_id);
! 280:
! 281: /* See if it's already in our dns cache */
! 282: dns = Curl_hash_pick(data->dns.hostcache, entry_id, entry_len + 1);
! 283: }
! 284:
! 285: if(dns && (data->set.dns_cache_timeout != -1)) {
! 286: /* See whether the returned entry is stale. Done before we release lock */
! 287: struct hostcache_prune_data user;
! 288:
! 289: time(&user.now);
! 290: user.cache_timeout = data->set.dns_cache_timeout;
! 291:
! 292: if(hostcache_timestamp_remove(&user, dns)) {
! 293: infof(data, "Hostname in DNS cache was stale, zapped\n");
! 294: dns = NULL; /* the memory deallocation is being handled by the hash */
! 295: Curl_hash_delete(data->dns.hostcache, entry_id, entry_len + 1);
! 296: }
! 297: }
! 298:
! 299: return dns;
! 300: }
! 301:
! 302: /*
! 303: * Curl_fetch_addr() fetches a 'Curl_dns_entry' already in the DNS cache.
! 304: *
! 305: * Curl_resolv() checks initially and multi_runsingle() checks each time
! 306: * it discovers the handle in the state WAITRESOLVE whether the hostname
! 307: * has already been resolved and the address has already been stored in
! 308: * the DNS cache. This short circuits waiting for a lot of pending
! 309: * lookups for the same hostname requested by different handles.
! 310: *
! 311: * Returns the Curl_dns_entry entry pointer or NULL if not in the cache.
! 312: *
! 313: * The returned data *MUST* be "unlocked" with Curl_resolv_unlock() after
! 314: * use, or we'll leak memory!
! 315: */
! 316: struct Curl_dns_entry *
! 317: Curl_fetch_addr(struct connectdata *conn,
! 318: const char *hostname,
! 319: int port)
! 320: {
! 321: struct Curl_easy *data = conn->data;
! 322: struct Curl_dns_entry *dns = NULL;
! 323:
! 324: if(data->share)
! 325: Curl_share_lock(data, CURL_LOCK_DATA_DNS, CURL_LOCK_ACCESS_SINGLE);
! 326:
! 327: dns = fetch_addr(conn, hostname, port);
! 328:
! 329: if(dns)
! 330: dns->inuse++; /* we use it! */
! 331:
! 332: if(data->share)
! 333: Curl_share_unlock(data, CURL_LOCK_DATA_DNS);
! 334:
! 335: return dns;
! 336: }
! 337:
! 338: #ifndef CURL_DISABLE_SHUFFLE_DNS
! 339: UNITTEST CURLcode Curl_shuffle_addr(struct Curl_easy *data,
! 340: Curl_addrinfo **addr);
! 341: /*
! 342: * Curl_shuffle_addr() shuffles the order of addresses in a 'Curl_addrinfo'
! 343: * struct by re-linking its linked list.
! 344: *
! 345: * The addr argument should be the address of a pointer to the head node of a
! 346: * `Curl_addrinfo` list and it will be modified to point to the new head after
! 347: * shuffling.
! 348: *
! 349: * Not declared static only to make it easy to use in a unit test!
! 350: *
! 351: * @unittest: 1608
! 352: */
! 353: UNITTEST CURLcode Curl_shuffle_addr(struct Curl_easy *data,
! 354: Curl_addrinfo **addr)
! 355: {
! 356: CURLcode result = CURLE_OK;
! 357: const int num_addrs = Curl_num_addresses(*addr);
! 358:
! 359: if(num_addrs > 1) {
! 360: Curl_addrinfo **nodes;
! 361: infof(data, "Shuffling %i addresses", num_addrs);
! 362:
! 363: nodes = malloc(num_addrs*sizeof(*nodes));
! 364: if(nodes) {
! 365: int i;
! 366: unsigned int *rnd;
! 367: const size_t rnd_size = num_addrs * sizeof(*rnd);
! 368:
! 369: /* build a plain array of Curl_addrinfo pointers */
! 370: nodes[0] = *addr;
! 371: for(i = 1; i < num_addrs; i++) {
! 372: nodes[i] = nodes[i-1]->ai_next;
! 373: }
! 374:
! 375: rnd = malloc(rnd_size);
! 376: if(rnd) {
! 377: /* Fisher-Yates shuffle */
! 378: if(Curl_rand(data, (unsigned char *)rnd, rnd_size) == CURLE_OK) {
! 379: Curl_addrinfo *swap_tmp;
! 380: for(i = num_addrs - 1; i > 0; i--) {
! 381: swap_tmp = nodes[rnd[i] % (i + 1)];
! 382: nodes[rnd[i] % (i + 1)] = nodes[i];
! 383: nodes[i] = swap_tmp;
! 384: }
! 385:
! 386: /* relink list in the new order */
! 387: for(i = 1; i < num_addrs; i++) {
! 388: nodes[i-1]->ai_next = nodes[i];
! 389: }
! 390:
! 391: nodes[num_addrs-1]->ai_next = NULL;
! 392: *addr = nodes[0];
! 393: }
! 394: free(rnd);
! 395: }
! 396: else
! 397: result = CURLE_OUT_OF_MEMORY;
! 398: free(nodes);
! 399: }
! 400: else
! 401: result = CURLE_OUT_OF_MEMORY;
! 402: }
! 403: return result;
! 404: }
! 405: #endif
! 406:
! 407: /*
! 408: * Curl_cache_addr() stores a 'Curl_addrinfo' struct in the DNS cache.
! 409: *
! 410: * When calling Curl_resolv() has resulted in a response with a returned
! 411: * address, we call this function to store the information in the dns
! 412: * cache etc
! 413: *
! 414: * Returns the Curl_dns_entry entry pointer or NULL if the storage failed.
! 415: */
! 416: struct Curl_dns_entry *
! 417: Curl_cache_addr(struct Curl_easy *data,
! 418: Curl_addrinfo *addr,
! 419: const char *hostname,
! 420: int port)
! 421: {
! 422: char entry_id[MAX_HOSTCACHE_LEN];
! 423: size_t entry_len;
! 424: struct Curl_dns_entry *dns;
! 425: struct Curl_dns_entry *dns2;
! 426:
! 427: #ifndef CURL_DISABLE_SHUFFLE_DNS
! 428: /* shuffle addresses if requested */
! 429: if(data->set.dns_shuffle_addresses) {
! 430: CURLcode result = Curl_shuffle_addr(data, &addr);
! 431: if(result)
! 432: return NULL;
! 433: }
! 434: #endif
! 435:
! 436: /* Create a new cache entry */
! 437: dns = calloc(1, sizeof(struct Curl_dns_entry));
! 438: if(!dns) {
! 439: return NULL;
! 440: }
! 441:
! 442: /* Create an entry id, based upon the hostname and port */
! 443: create_hostcache_id(hostname, port, entry_id, sizeof(entry_id));
! 444: entry_len = strlen(entry_id);
! 445:
! 446: dns->inuse = 1; /* the cache has the first reference */
! 447: dns->addr = addr; /* this is the address(es) */
! 448: time(&dns->timestamp);
! 449: if(dns->timestamp == 0)
! 450: dns->timestamp = 1; /* zero indicates CURLOPT_RESOLVE entry */
! 451:
! 452: /* Store the resolved data in our DNS cache. */
! 453: dns2 = Curl_hash_add(data->dns.hostcache, entry_id, entry_len + 1,
! 454: (void *)dns);
! 455: if(!dns2) {
! 456: free(dns);
! 457: return NULL;
! 458: }
! 459:
! 460: dns = dns2;
! 461: dns->inuse++; /* mark entry as in-use */
! 462: return dns;
! 463: }
! 464:
! 465: /*
! 466: * Curl_resolv() is the main name resolve function within libcurl. It resolves
! 467: * a name and returns a pointer to the entry in the 'entry' argument (if one
! 468: * is provided). This function might return immediately if we're using asynch
! 469: * resolves. See the return codes.
! 470: *
! 471: * The cache entry we return will get its 'inuse' counter increased when this
! 472: * function is used. You MUST call Curl_resolv_unlock() later (when you're
! 473: * done using this struct) to decrease the counter again.
! 474: *
! 475: * In debug mode, we specifically test for an interface name "LocalHost"
! 476: * and resolve "localhost" instead as a means to permit test cases
! 477: * to connect to a local test server with any host name.
! 478: *
! 479: * Return codes:
! 480: *
! 481: * CURLRESOLV_ERROR (-1) = error, no pointer
! 482: * CURLRESOLV_RESOLVED (0) = OK, pointer provided
! 483: * CURLRESOLV_PENDING (1) = waiting for response, no pointer
! 484: */
! 485:
! 486: enum resolve_t Curl_resolv(struct connectdata *conn,
! 487: const char *hostname,
! 488: int port,
! 489: bool allowDOH,
! 490: struct Curl_dns_entry **entry)
! 491: {
! 492: struct Curl_dns_entry *dns = NULL;
! 493: struct Curl_easy *data = conn->data;
! 494: CURLcode result;
! 495: enum resolve_t rc = CURLRESOLV_ERROR; /* default to failure */
! 496:
! 497: *entry = NULL;
! 498:
! 499: if(data->share)
! 500: Curl_share_lock(data, CURL_LOCK_DATA_DNS, CURL_LOCK_ACCESS_SINGLE);
! 501:
! 502: dns = fetch_addr(conn, hostname, port);
! 503:
! 504: if(dns) {
! 505: infof(data, "Hostname %s was found in DNS cache\n", hostname);
! 506: dns->inuse++; /* we use it! */
! 507: rc = CURLRESOLV_RESOLVED;
! 508: }
! 509:
! 510: if(data->share)
! 511: Curl_share_unlock(data, CURL_LOCK_DATA_DNS);
! 512:
! 513: if(!dns) {
! 514: /* The entry was not in the cache. Resolve it to IP address */
! 515:
! 516: Curl_addrinfo *addr = NULL;
! 517: int respwait = 0;
! 518: #ifndef USE_RESOLVE_ON_IPS
! 519: struct in_addr in;
! 520: #endif
! 521:
! 522: /* notify the resolver start callback */
! 523: if(data->set.resolver_start) {
! 524: int st;
! 525: Curl_set_in_callback(data, true);
! 526: st = data->set.resolver_start(data->state.resolver, NULL,
! 527: data->set.resolver_start_client);
! 528: Curl_set_in_callback(data, false);
! 529: if(st)
! 530: return CURLRESOLV_ERROR;
! 531: }
! 532:
! 533: #ifndef USE_RESOLVE_ON_IPS
! 534: /* First check if this is an IPv4 address string */
! 535: if(Curl_inet_pton(AF_INET, hostname, &in) > 0)
! 536: /* This is a dotted IP address 123.123.123.123-style */
! 537: addr = Curl_ip2addr(AF_INET, &in, hostname, port);
! 538: #ifdef ENABLE_IPV6
! 539: if(!addr) {
! 540: struct in6_addr in6;
! 541: /* check if this is an IPv6 address string */
! 542: if(Curl_inet_pton(AF_INET6, hostname, &in6) > 0)
! 543: /* This is an IPv6 address literal */
! 544: addr = Curl_ip2addr(AF_INET6, &in6, hostname, port);
! 545: }
! 546: #endif /* ENABLE_IPV6 */
! 547: #endif /* !USE_RESOLVE_ON_IPS */
! 548:
! 549: if(!addr) {
! 550: /* Check what IP specifics the app has requested and if we can provide
! 551: * it. If not, bail out. */
! 552: if(!Curl_ipvalid(conn))
! 553: return CURLRESOLV_ERROR;
! 554:
! 555: if(allowDOH && data->set.doh) {
! 556: addr = Curl_doh(conn, hostname, port, &respwait);
! 557: }
! 558: else {
! 559: /* If Curl_getaddrinfo() returns NULL, 'respwait' might be set to a
! 560: non-zero value indicating that we need to wait for the response to
! 561: the resolve call */
! 562: addr = Curl_getaddrinfo(conn,
! 563: #ifdef DEBUGBUILD
! 564: (data->set.str[STRING_DEVICE]
! 565: && !strcmp(data->set.str[STRING_DEVICE],
! 566: "LocalHost"))?"localhost":
! 567: #endif
! 568: hostname, port, &respwait);
! 569: }
! 570: }
! 571: if(!addr) {
! 572: if(respwait) {
! 573: /* the response to our resolve call will come asynchronously at
! 574: a later time, good or bad */
! 575: /* First, check that we haven't received the info by now */
! 576: result = Curl_resolv_check(conn, &dns);
! 577: if(result) /* error detected */
! 578: return CURLRESOLV_ERROR;
! 579: if(dns)
! 580: rc = CURLRESOLV_RESOLVED; /* pointer provided */
! 581: else
! 582: rc = CURLRESOLV_PENDING; /* no info yet */
! 583: }
! 584: }
! 585: else {
! 586: if(data->share)
! 587: Curl_share_lock(data, CURL_LOCK_DATA_DNS, CURL_LOCK_ACCESS_SINGLE);
! 588:
! 589: /* we got a response, store it in the cache */
! 590: dns = Curl_cache_addr(data, addr, hostname, port);
! 591:
! 592: if(data->share)
! 593: Curl_share_unlock(data, CURL_LOCK_DATA_DNS);
! 594:
! 595: if(!dns)
! 596: /* returned failure, bail out nicely */
! 597: Curl_freeaddrinfo(addr);
! 598: else
! 599: rc = CURLRESOLV_RESOLVED;
! 600: }
! 601: }
! 602:
! 603: *entry = dns;
! 604:
! 605: return rc;
! 606: }
! 607:
! 608: #ifdef USE_ALARM_TIMEOUT
! 609: /*
! 610: * This signal handler jumps back into the main libcurl code and continues
! 611: * execution. This effectively causes the remainder of the application to run
! 612: * within a signal handler which is nonportable and could lead to problems.
! 613: */
! 614: static
! 615: RETSIGTYPE alarmfunc(int sig)
! 616: {
! 617: /* this is for "-ansi -Wall -pedantic" to stop complaining! (rabe) */
! 618: (void)sig;
! 619: siglongjmp(curl_jmpenv, 1);
! 620: }
! 621: #endif /* USE_ALARM_TIMEOUT */
! 622:
! 623: /*
! 624: * Curl_resolv_timeout() is the same as Curl_resolv() but specifies a
! 625: * timeout. This function might return immediately if we're using asynch
! 626: * resolves. See the return codes.
! 627: *
! 628: * The cache entry we return will get its 'inuse' counter increased when this
! 629: * function is used. You MUST call Curl_resolv_unlock() later (when you're
! 630: * done using this struct) to decrease the counter again.
! 631: *
! 632: * If built with a synchronous resolver and use of signals is not
! 633: * disabled by the application, then a nonzero timeout will cause a
! 634: * timeout after the specified number of milliseconds. Otherwise, timeout
! 635: * is ignored.
! 636: *
! 637: * Return codes:
! 638: *
! 639: * CURLRESOLV_TIMEDOUT(-2) = warning, time too short or previous alarm expired
! 640: * CURLRESOLV_ERROR (-1) = error, no pointer
! 641: * CURLRESOLV_RESOLVED (0) = OK, pointer provided
! 642: * CURLRESOLV_PENDING (1) = waiting for response, no pointer
! 643: */
! 644:
! 645: enum resolve_t Curl_resolv_timeout(struct connectdata *conn,
! 646: const char *hostname,
! 647: int port,
! 648: struct Curl_dns_entry **entry,
! 649: timediff_t timeoutms)
! 650: {
! 651: #ifdef USE_ALARM_TIMEOUT
! 652: #ifdef HAVE_SIGACTION
! 653: struct sigaction keep_sigact; /* store the old struct here */
! 654: volatile bool keep_copysig = FALSE; /* whether old sigact has been saved */
! 655: struct sigaction sigact;
! 656: #else
! 657: #ifdef HAVE_SIGNAL
! 658: void (*keep_sigact)(int); /* store the old handler here */
! 659: #endif /* HAVE_SIGNAL */
! 660: #endif /* HAVE_SIGACTION */
! 661: volatile long timeout;
! 662: volatile unsigned int prev_alarm = 0;
! 663: struct Curl_easy *data = conn->data;
! 664: #endif /* USE_ALARM_TIMEOUT */
! 665: enum resolve_t rc;
! 666:
! 667: *entry = NULL;
! 668:
! 669: if(timeoutms < 0)
! 670: /* got an already expired timeout */
! 671: return CURLRESOLV_TIMEDOUT;
! 672:
! 673: #ifdef USE_ALARM_TIMEOUT
! 674: if(data->set.no_signal)
! 675: /* Ignore the timeout when signals are disabled */
! 676: timeout = 0;
! 677: else
! 678: timeout = (timeoutms > LONG_MAX) ? LONG_MAX : (long)timeoutms;
! 679:
! 680: if(!timeout)
! 681: /* USE_ALARM_TIMEOUT defined, but no timeout actually requested */
! 682: return Curl_resolv(conn, hostname, port, TRUE, entry);
! 683:
! 684: if(timeout < 1000) {
! 685: /* The alarm() function only provides integer second resolution, so if
! 686: we want to wait less than one second we must bail out already now. */
! 687: failf(data,
! 688: "remaining timeout of %ld too small to resolve via SIGALRM method",
! 689: timeout);
! 690: return CURLRESOLV_TIMEDOUT;
! 691: }
! 692: /* This allows us to time-out from the name resolver, as the timeout
! 693: will generate a signal and we will siglongjmp() from that here.
! 694: This technique has problems (see alarmfunc).
! 695: This should be the last thing we do before calling Curl_resolv(),
! 696: as otherwise we'd have to worry about variables that get modified
! 697: before we invoke Curl_resolv() (and thus use "volatile"). */
! 698: if(sigsetjmp(curl_jmpenv, 1)) {
! 699: /* this is coming from a siglongjmp() after an alarm signal */
! 700: failf(data, "name lookup timed out");
! 701: rc = CURLRESOLV_ERROR;
! 702: goto clean_up;
! 703: }
! 704: else {
! 705: /*************************************************************
! 706: * Set signal handler to catch SIGALRM
! 707: * Store the old value to be able to set it back later!
! 708: *************************************************************/
! 709: #ifdef HAVE_SIGACTION
! 710: sigaction(SIGALRM, NULL, &sigact);
! 711: keep_sigact = sigact;
! 712: keep_copysig = TRUE; /* yes, we have a copy */
! 713: sigact.sa_handler = alarmfunc;
! 714: #ifdef SA_RESTART
! 715: /* HPUX doesn't have SA_RESTART but defaults to that behaviour! */
! 716: sigact.sa_flags &= ~SA_RESTART;
! 717: #endif
! 718: /* now set the new struct */
! 719: sigaction(SIGALRM, &sigact, NULL);
! 720: #else /* HAVE_SIGACTION */
! 721: /* no sigaction(), revert to the much lamer signal() */
! 722: #ifdef HAVE_SIGNAL
! 723: keep_sigact = signal(SIGALRM, alarmfunc);
! 724: #endif
! 725: #endif /* HAVE_SIGACTION */
! 726:
! 727: /* alarm() makes a signal get sent when the timeout fires off, and that
! 728: will abort system calls */
! 729: prev_alarm = alarm(curlx_sltoui(timeout/1000L));
! 730: }
! 731:
! 732: #else
! 733: #ifndef CURLRES_ASYNCH
! 734: if(timeoutms)
! 735: infof(conn->data, "timeout on name lookup is not supported\n");
! 736: #else
! 737: (void)timeoutms; /* timeoutms not used with an async resolver */
! 738: #endif
! 739: #endif /* USE_ALARM_TIMEOUT */
! 740:
! 741: /* Perform the actual name resolution. This might be interrupted by an
! 742: * alarm if it takes too long.
! 743: */
! 744: rc = Curl_resolv(conn, hostname, port, TRUE, entry);
! 745:
! 746: #ifdef USE_ALARM_TIMEOUT
! 747: clean_up:
! 748:
! 749: if(!prev_alarm)
! 750: /* deactivate a possibly active alarm before uninstalling the handler */
! 751: alarm(0);
! 752:
! 753: #ifdef HAVE_SIGACTION
! 754: if(keep_copysig) {
! 755: /* we got a struct as it looked before, now put that one back nice
! 756: and clean */
! 757: sigaction(SIGALRM, &keep_sigact, NULL); /* put it back */
! 758: }
! 759: #else
! 760: #ifdef HAVE_SIGNAL
! 761: /* restore the previous SIGALRM handler */
! 762: signal(SIGALRM, keep_sigact);
! 763: #endif
! 764: #endif /* HAVE_SIGACTION */
! 765:
! 766: /* switch back the alarm() to either zero or to what it was before minus
! 767: the time we spent until now! */
! 768: if(prev_alarm) {
! 769: /* there was an alarm() set before us, now put it back */
! 770: timediff_t elapsed_secs = Curl_timediff(Curl_now(),
! 771: conn->created) / 1000;
! 772:
! 773: /* the alarm period is counted in even number of seconds */
! 774: unsigned long alarm_set = (unsigned long)(prev_alarm - elapsed_secs);
! 775:
! 776: if(!alarm_set ||
! 777: ((alarm_set >= 0x80000000) && (prev_alarm < 0x80000000)) ) {
! 778: /* if the alarm time-left reached zero or turned "negative" (counted
! 779: with unsigned values), we should fire off a SIGALRM here, but we
! 780: won't, and zero would be to switch it off so we never set it to
! 781: less than 1! */
! 782: alarm(1);
! 783: rc = CURLRESOLV_TIMEDOUT;
! 784: failf(data, "Previous alarm fired off!");
! 785: }
! 786: else
! 787: alarm((unsigned int)alarm_set);
! 788: }
! 789: #endif /* USE_ALARM_TIMEOUT */
! 790:
! 791: return rc;
! 792: }
! 793:
! 794: /*
! 795: * Curl_resolv_unlock() unlocks the given cached DNS entry. When this has been
! 796: * made, the struct may be destroyed due to pruning. It is important that only
! 797: * one unlock is made for each Curl_resolv() call.
! 798: *
! 799: * May be called with 'data' == NULL for global cache.
! 800: */
! 801: void Curl_resolv_unlock(struct Curl_easy *data, struct Curl_dns_entry *dns)
! 802: {
! 803: if(data && data->share)
! 804: Curl_share_lock(data, CURL_LOCK_DATA_DNS, CURL_LOCK_ACCESS_SINGLE);
! 805:
! 806: freednsentry(dns);
! 807:
! 808: if(data && data->share)
! 809: Curl_share_unlock(data, CURL_LOCK_DATA_DNS);
! 810: }
! 811:
! 812: /*
! 813: * File-internal: release cache dns entry reference, free if inuse drops to 0
! 814: */
! 815: static void freednsentry(void *freethis)
! 816: {
! 817: struct Curl_dns_entry *dns = (struct Curl_dns_entry *) freethis;
! 818: DEBUGASSERT(dns && (dns->inuse>0));
! 819:
! 820: dns->inuse--;
! 821: if(dns->inuse == 0) {
! 822: Curl_freeaddrinfo(dns->addr);
! 823: free(dns);
! 824: }
! 825: }
! 826:
! 827: /*
! 828: * Curl_mk_dnscache() inits a new DNS cache and returns success/failure.
! 829: */
! 830: int Curl_mk_dnscache(struct curl_hash *hash)
! 831: {
! 832: return Curl_hash_init(hash, 7, Curl_hash_str, Curl_str_key_compare,
! 833: freednsentry);
! 834: }
! 835:
! 836: /*
! 837: * Curl_hostcache_clean()
! 838: *
! 839: * This _can_ be called with 'data' == NULL but then of course no locking
! 840: * can be done!
! 841: */
! 842:
! 843: void Curl_hostcache_clean(struct Curl_easy *data,
! 844: struct curl_hash *hash)
! 845: {
! 846: if(data && data->share)
! 847: Curl_share_lock(data, CURL_LOCK_DATA_DNS, CURL_LOCK_ACCESS_SINGLE);
! 848:
! 849: Curl_hash_clean(hash);
! 850:
! 851: if(data && data->share)
! 852: Curl_share_unlock(data, CURL_LOCK_DATA_DNS);
! 853: }
! 854:
! 855:
! 856: CURLcode Curl_loadhostpairs(struct Curl_easy *data)
! 857: {
! 858: struct curl_slist *hostp;
! 859: char hostname[256];
! 860: int port = 0;
! 861:
! 862: /* Default is no wildcard found */
! 863: data->change.wildcard_resolve = false;
! 864:
! 865: for(hostp = data->change.resolve; hostp; hostp = hostp->next) {
! 866: char entry_id[MAX_HOSTCACHE_LEN];
! 867: if(!hostp->data)
! 868: continue;
! 869: if(hostp->data[0] == '-') {
! 870: size_t entry_len;
! 871:
! 872: if(2 != sscanf(hostp->data + 1, "%255[^:]:%d", hostname, &port)) {
! 873: infof(data, "Couldn't parse CURLOPT_RESOLVE removal entry '%s'!\n",
! 874: hostp->data);
! 875: continue;
! 876: }
! 877:
! 878: /* Create an entry id, based upon the hostname and port */
! 879: create_hostcache_id(hostname, port, entry_id, sizeof(entry_id));
! 880: entry_len = strlen(entry_id);
! 881:
! 882: if(data->share)
! 883: Curl_share_lock(data, CURL_LOCK_DATA_DNS, CURL_LOCK_ACCESS_SINGLE);
! 884:
! 885: /* delete entry, ignore if it didn't exist */
! 886: Curl_hash_delete(data->dns.hostcache, entry_id, entry_len + 1);
! 887:
! 888: if(data->share)
! 889: Curl_share_unlock(data, CURL_LOCK_DATA_DNS);
! 890: }
! 891: else {
! 892: struct Curl_dns_entry *dns;
! 893: Curl_addrinfo *head = NULL, *tail = NULL;
! 894: size_t entry_len;
! 895: char address[64];
! 896: #if !defined(CURL_DISABLE_VERBOSE_STRINGS)
! 897: char *addresses = NULL;
! 898: #endif
! 899: char *addr_begin;
! 900: char *addr_end;
! 901: char *port_ptr;
! 902: char *end_ptr;
! 903: char *host_end;
! 904: unsigned long tmp_port;
! 905: bool error = true;
! 906:
! 907: host_end = strchr(hostp->data, ':');
! 908: if(!host_end ||
! 909: ((host_end - hostp->data) >= (ptrdiff_t)sizeof(hostname)))
! 910: goto err;
! 911:
! 912: memcpy(hostname, hostp->data, host_end - hostp->data);
! 913: hostname[host_end - hostp->data] = '\0';
! 914:
! 915: port_ptr = host_end + 1;
! 916: tmp_port = strtoul(port_ptr, &end_ptr, 10);
! 917: if(tmp_port > USHRT_MAX || end_ptr == port_ptr || *end_ptr != ':')
! 918: goto err;
! 919:
! 920: port = (int)tmp_port;
! 921: #if !defined(CURL_DISABLE_VERBOSE_STRINGS)
! 922: addresses = end_ptr + 1;
! 923: #endif
! 924:
! 925: while(*end_ptr) {
! 926: size_t alen;
! 927: Curl_addrinfo *ai;
! 928:
! 929: addr_begin = end_ptr + 1;
! 930: addr_end = strchr(addr_begin, ',');
! 931: if(!addr_end)
! 932: addr_end = addr_begin + strlen(addr_begin);
! 933: end_ptr = addr_end;
! 934:
! 935: /* allow IP(v6) address within [brackets] */
! 936: if(*addr_begin == '[') {
! 937: if(addr_end == addr_begin || *(addr_end - 1) != ']')
! 938: goto err;
! 939: ++addr_begin;
! 940: --addr_end;
! 941: }
! 942:
! 943: alen = addr_end - addr_begin;
! 944: if(!alen)
! 945: continue;
! 946:
! 947: if(alen >= sizeof(address))
! 948: goto err;
! 949:
! 950: memcpy(address, addr_begin, alen);
! 951: address[alen] = '\0';
! 952:
! 953: #ifndef ENABLE_IPV6
! 954: if(strchr(address, ':')) {
! 955: infof(data, "Ignoring resolve address '%s', missing IPv6 support.\n",
! 956: address);
! 957: continue;
! 958: }
! 959: #endif
! 960:
! 961: ai = Curl_str2addr(address, port);
! 962: if(!ai) {
! 963: infof(data, "Resolve address '%s' found illegal!\n", address);
! 964: goto err;
! 965: }
! 966:
! 967: if(tail) {
! 968: tail->ai_next = ai;
! 969: tail = tail->ai_next;
! 970: }
! 971: else {
! 972: head = tail = ai;
! 973: }
! 974: }
! 975:
! 976: if(!head)
! 977: goto err;
! 978:
! 979: error = false;
! 980: err:
! 981: if(error) {
! 982: infof(data, "Couldn't parse CURLOPT_RESOLVE entry '%s'!\n",
! 983: hostp->data);
! 984: Curl_freeaddrinfo(head);
! 985: continue;
! 986: }
! 987:
! 988: /* Create an entry id, based upon the hostname and port */
! 989: create_hostcache_id(hostname, port, entry_id, sizeof(entry_id));
! 990: entry_len = strlen(entry_id);
! 991:
! 992: if(data->share)
! 993: Curl_share_lock(data, CURL_LOCK_DATA_DNS, CURL_LOCK_ACCESS_SINGLE);
! 994:
! 995: /* See if its already in our dns cache */
! 996: dns = Curl_hash_pick(data->dns.hostcache, entry_id, entry_len + 1);
! 997:
! 998: if(dns) {
! 999: infof(data, "RESOLVE %s:%d is - old addresses discarded!\n",
! 1000: hostname, port);
! 1001: /* delete old entry entry, there are two reasons for this
! 1002: 1. old entry may have different addresses.
! 1003: 2. even if entry with correct addresses is already in the cache,
! 1004: but if it is close to expire, then by the time next http
! 1005: request is made, it can get expired and pruned because old
! 1006: entry is not necessarily marked as added by CURLOPT_RESOLVE. */
! 1007:
! 1008: Curl_hash_delete(data->dns.hostcache, entry_id, entry_len + 1);
! 1009: }
! 1010:
! 1011: /* put this new host in the cache */
! 1012: dns = Curl_cache_addr(data, head, hostname, port);
! 1013: if(dns) {
! 1014: dns->timestamp = 0; /* mark as added by CURLOPT_RESOLVE */
! 1015: /* release the returned reference; the cache itself will keep the
! 1016: * entry alive: */
! 1017: dns->inuse--;
! 1018: }
! 1019:
! 1020: if(data->share)
! 1021: Curl_share_unlock(data, CURL_LOCK_DATA_DNS);
! 1022:
! 1023: if(!dns) {
! 1024: Curl_freeaddrinfo(head);
! 1025: return CURLE_OUT_OF_MEMORY;
! 1026: }
! 1027: infof(data, "Added %s:%d:%s to DNS cache\n",
! 1028: hostname, port, addresses);
! 1029:
! 1030: /* Wildcard hostname */
! 1031: if(hostname[0] == '*' && hostname[1] == '\0') {
! 1032: infof(data, "RESOLVE %s:%d is wildcard, enabling wildcard checks\n",
! 1033: hostname, port);
! 1034: data->change.wildcard_resolve = true;
! 1035: }
! 1036: }
! 1037: }
! 1038: data->change.resolve = NULL; /* dealt with now */
! 1039:
! 1040: return CURLE_OK;
! 1041: }
! 1042:
! 1043: CURLcode Curl_resolv_check(struct connectdata *conn,
! 1044: struct Curl_dns_entry **dns)
! 1045: {
! 1046: #if defined(CURL_DISABLE_DOH) && !defined(CURLRES_ASYNCH)
! 1047: (void)dns;
! 1048: #endif
! 1049:
! 1050: if(conn->data->set.doh)
! 1051: return Curl_doh_is_resolved(conn, dns);
! 1052: return Curl_resolver_is_resolved(conn, dns);
! 1053: }
! 1054:
! 1055: int Curl_resolv_getsock(struct connectdata *conn,
! 1056: curl_socket_t *socks)
! 1057: {
! 1058: #ifdef CURLRES_ASYNCH
! 1059: if(conn->data->set.doh)
! 1060: /* nothing to wait for during DOH resolve, those handles have their own
! 1061: sockets */
! 1062: return GETSOCK_BLANK;
! 1063: return Curl_resolver_getsock(conn, socks);
! 1064: #else
! 1065: (void)conn;
! 1066: (void)socks;
! 1067: return GETSOCK_BLANK;
! 1068: #endif
! 1069: }
! 1070:
! 1071: /* Call this function after Curl_connect() has returned async=TRUE and
! 1072: then a successful name resolve has been received.
! 1073:
! 1074: Note: this function disconnects and frees the conn data in case of
! 1075: resolve failure */
! 1076: CURLcode Curl_once_resolved(struct connectdata *conn,
! 1077: bool *protocol_done)
! 1078: {
! 1079: CURLcode result;
! 1080:
! 1081: if(conn->async.dns) {
! 1082: conn->dns_entry = conn->async.dns;
! 1083: conn->async.dns = NULL;
! 1084: }
! 1085:
! 1086: result = Curl_setup_conn(conn, protocol_done);
! 1087:
! 1088: if(result)
! 1089: /* We're not allowed to return failure with memory left allocated
! 1090: in the connectdata struct, free those here */
! 1091: Curl_disconnect(conn->data, conn, TRUE); /* close the connection */
! 1092:
! 1093: return result;
! 1094: }
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>