ca319f63ad
the crash was that libcurl internally was a bit confused about who owned the DNS cache at all times so if you created an easy handle that uses a shared DNS cache and added that to a multi handle it would crash. Now we keep more careful internal track of exactly what kind of DNS cache each easy handle uses: None, Private (allocated for and used only by this single handle), Shared (points to a cache held by a shared object), Global (points to the global cache) or Multi (points to the cache within the multi handle that is automatically shared between all easy handles that are added with private caches).
562 lines
15 KiB
C
562 lines
15 KiB
C
/***************************************************************************
|
|
* _ _ ____ _
|
|
* Project ___| | | | _ \| |
|
|
* / __| | | | |_) | |
|
|
* | (__| |_| | _ <| |___
|
|
* \___|\___/|_| \_\_____|
|
|
*
|
|
* Copyright (C) 1998 - 2006, Daniel Stenberg, <daniel@haxx.se>, et al.
|
|
*
|
|
* This software is licensed as described in the file COPYING, which
|
|
* you should have received as part of this distribution. The terms
|
|
* are also available at http://curl.haxx.se/docs/copyright.html.
|
|
*
|
|
* You may opt to use, copy, modify, merge, publish, distribute and/or sell
|
|
* copies of the Software, and permit persons to whom the Software is
|
|
* furnished to do so, under the terms of the COPYING file.
|
|
*
|
|
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
|
|
* KIND, either express or implied.
|
|
*
|
|
* $Id$
|
|
***************************************************************************/
|
|
|
|
#include "setup.h"
|
|
|
|
#include <string.h>
|
|
|
|
#ifdef HAVE_MALLOC_H /* Win32 */
|
|
#include <malloc.h>
|
|
#endif
|
|
#ifdef HAVE_SYS_TYPES_H
|
|
#include <sys/types.h>
|
|
#endif
|
|
#ifdef HAVE_SYS_SOCKET_H
|
|
#include <sys/socket.h>
|
|
#endif
|
|
#ifdef HAVE_NETINET_IN_H
|
|
#include <netinet/in.h>
|
|
#endif
|
|
#ifdef HAVE_NETDB_H
|
|
#include <netdb.h>
|
|
#endif
|
|
#ifdef HAVE_ARPA_INET_H
|
|
#include <arpa/inet.h>
|
|
#endif
|
|
#ifdef HAVE_STDLIB_H
|
|
#include <stdlib.h> /* required for free() prototypes */
|
|
#endif
|
|
#ifdef HAVE_UNISTD_H
|
|
#include <unistd.h> /* for the close() proto */
|
|
#endif
|
|
#ifdef VMS
|
|
#include <in.h>
|
|
#include <inet.h>
|
|
#include <stdlib.h>
|
|
#endif
|
|
|
|
#ifdef HAVE_SETJMP_H
|
|
#include <setjmp.h>
|
|
#endif
|
|
|
|
#ifdef HAVE_PROCESS_H
|
|
#include <process.h>
|
|
#endif
|
|
|
|
#include "urldata.h"
|
|
#include "sendf.h"
|
|
#include "hostip.h"
|
|
#include "hash.h"
|
|
#include "share.h"
|
|
#include "strerror.h"
|
|
#include "url.h"
|
|
#include "inet_ntop.h"
|
|
|
|
#define _MPRINTF_REPLACE /* use our functions only */
|
|
#include <curl/mprintf.h>
|
|
|
|
#if defined(HAVE_INET_NTOA_R) && !defined(HAVE_INET_NTOA_R_DECL)
|
|
#include "inet_ntoa_r.h"
|
|
#endif
|
|
|
|
#include "memory.h"
|
|
/* The last #include file should be: */
|
|
#include "memdebug.h"
|
|
|
|
/*
|
|
* hostip.c explained
|
|
* ==================
|
|
*
|
|
* The main COMPILE-TIME DEFINES to keep in mind when reading the host*.c
|
|
* source file are these:
|
|
*
|
|
* CURLRES_IPV6 - this host has getaddrinfo() and family, and thus we use
|
|
* that. The host may not be able to resolve IPv6, but we don't really have to
|
|
* take that into account. Hosts that aren't IPv6-enabled have CURLRES_IPV4
|
|
* defined.
|
|
*
|
|
* CURLRES_ARES - is defined if libcurl is built to use c-ares for
|
|
* asynchronous name resolves. It cannot have ENABLE_IPV6 defined at the same
|
|
* time, as c-ares has no ipv6 support. This can be Windows or *nix.
|
|
*
|
|
* CURLRES_THREADED - is defined if libcurl is built to run under (native)
|
|
* Windows, and then the name resolve will be done in a new thread, and the
|
|
* supported API will be the same as for ares-builds.
|
|
*
|
|
* If any of the two previous are defined, CURLRES_ASYNCH is defined too. If
|
|
* libcurl is not built to use an asynchronous resolver, CURLRES_SYNCH is
|
|
* defined.
|
|
*
|
|
* The host*.c sources files are split up like this:
|
|
*
|
|
* hostip.c - method-independent resolver functions and utility functions
|
|
* hostasyn.c - functions for asynchronous name resolves
|
|
* hostsyn.c - functions for synchronous name resolves
|
|
* hostares.c - functions for ares-using name resolves
|
|
* hostthre.c - functions for threaded name resolves
|
|
* hostip4.c - ipv4-specific functions
|
|
* hostip6.c - ipv6-specific functions
|
|
*
|
|
* The hostip.h is the united header file for all this. It defines the
|
|
* CURLRES_* defines based on the config*.h and setup.h defines.
|
|
*/
|
|
|
|
/* These two symbols are for the global DNS cache */
|
|
static struct curl_hash hostname_cache;
|
|
static int host_cache_initialized;
|
|
|
|
static void freednsentry(void *freethis);
|
|
|
|
/*
|
|
* Curl_global_host_cache_init() initializes and sets up a global DNS cache.
|
|
* Global DNS cache is general badness. Do not use. This will be removed in
|
|
* a future version. Use the share interface instead!
|
|
*/
|
|
void Curl_global_host_cache_init(void)
|
|
{
|
|
if (!host_cache_initialized) {
|
|
Curl_hash_init(&hostname_cache, 7, freednsentry);
|
|
host_cache_initialized = 1;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Return a pointer to the global cache
|
|
*/
|
|
struct curl_hash *Curl_global_host_cache_get(void)
|
|
{
|
|
return &hostname_cache;
|
|
}
|
|
|
|
/*
|
|
* Destroy and cleanup the global DNS cache
|
|
*/
|
|
void Curl_global_host_cache_dtor(void)
|
|
{
|
|
if (host_cache_initialized) {
|
|
Curl_hash_clean(&hostname_cache);
|
|
host_cache_initialized = 0;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Return # of adresses in a Curl_addrinfo struct
|
|
*/
|
|
int Curl_num_addresses(const Curl_addrinfo *addr)
|
|
{
|
|
int i;
|
|
for (i = 0; addr; addr = addr->ai_next, i++)
|
|
; /* empty loop */
|
|
return i;
|
|
}
|
|
|
|
/*
|
|
* Curl_printable_address() returns a printable version of the 1st address
|
|
* given in the 'ip' argument. The result will be stored in the buf that is
|
|
* bufsize bytes big.
|
|
*
|
|
* If the conversion fails, it returns NULL.
|
|
*/
|
|
const char *Curl_printable_address(const Curl_addrinfo *ip,
|
|
char *buf, size_t bufsize)
|
|
{
|
|
const void *ip4 = &((const struct sockaddr_in*)ip->ai_addr)->sin_addr;
|
|
int af = ip->ai_family;
|
|
#ifdef CURLRES_IPV6
|
|
const void *ip6 = &((const struct sockaddr_in6*)ip->ai_addr)->sin6_addr;
|
|
#else
|
|
const void *ip6 = NULL;
|
|
#endif
|
|
|
|
return Curl_inet_ntop(af, af == AF_INET ? ip4 : ip6, buf, bufsize);
|
|
}
|
|
|
|
/*
|
|
* Return a hostcache id string for the providing host + port, to be used by
|
|
* the DNS caching.
|
|
*/
|
|
static char *
|
|
create_hostcache_id(char *server, int port)
|
|
{
|
|
/* create and return the new allocated entry */
|
|
return aprintf("%s:%d", server, port);
|
|
}
|
|
|
|
struct hostcache_prune_data {
|
|
int cache_timeout;
|
|
time_t now;
|
|
};
|
|
|
|
/*
|
|
* This function is set as a callback to be called for every entry in the DNS
|
|
* cache when we want to prune old unused entries.
|
|
*
|
|
* Returning non-zero means remove the entry, return 0 to keep it in the
|
|
* cache.
|
|
*/
|
|
static int
|
|
hostcache_timestamp_remove(void *datap, void *hc)
|
|
{
|
|
struct hostcache_prune_data *data =
|
|
(struct hostcache_prune_data *) datap;
|
|
struct Curl_dns_entry *c = (struct Curl_dns_entry *) hc;
|
|
|
|
if ((data->now - c->timestamp < data->cache_timeout) ||
|
|
c->inuse) {
|
|
/* please don't remove */
|
|
return 0;
|
|
}
|
|
|
|
/* fine, remove */
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
* Prune the DNS cache. This assumes that a lock has already been taken.
|
|
*/
|
|
static void
|
|
hostcache_prune(struct curl_hash *hostcache, int cache_timeout, time_t now)
|
|
{
|
|
struct hostcache_prune_data user;
|
|
|
|
user.cache_timeout = cache_timeout;
|
|
user.now = now;
|
|
|
|
Curl_hash_clean_with_criterium(hostcache,
|
|
(void *) &user,
|
|
hostcache_timestamp_remove);
|
|
}
|
|
|
|
/*
|
|
* Library-wide function for pruning the DNS cache. This function takes and
|
|
* returns the appropriate locks.
|
|
*/
|
|
void Curl_hostcache_prune(struct SessionHandle *data)
|
|
{
|
|
time_t now;
|
|
|
|
if((data->set.dns_cache_timeout == -1) || !data->dns.hostcache)
|
|
/* cache forever means never prune, and NULL hostcache means
|
|
we can't do it */
|
|
return;
|
|
|
|
if(data->share)
|
|
Curl_share_lock(data, CURL_LOCK_DATA_DNS, CURL_LOCK_ACCESS_SINGLE);
|
|
|
|
time(&now);
|
|
|
|
/* Remove outdated and unused entries from the hostcache */
|
|
hostcache_prune(data->dns.hostcache,
|
|
data->set.dns_cache_timeout,
|
|
now);
|
|
|
|
if(data->share)
|
|
Curl_share_unlock(data, CURL_LOCK_DATA_DNS);
|
|
}
|
|
|
|
static int
|
|
remove_entry_if_stale(struct SessionHandle *data, struct Curl_dns_entry *dns)
|
|
{
|
|
struct hostcache_prune_data user;
|
|
|
|
if( !dns || (data->set.dns_cache_timeout == -1) || !data->dns.hostcache)
|
|
/* cache forever means never prune, and NULL hostcache means
|
|
we can't do it */
|
|
return 0;
|
|
|
|
time(&user.now);
|
|
user.cache_timeout = data->set.dns_cache_timeout;
|
|
|
|
if ( !hostcache_timestamp_remove(&user,dns) )
|
|
return 0;
|
|
|
|
/* ok, we do need to clear the cache. although we need to remove just a
|
|
single entry we clean the entire hash, as no explicit delete function
|
|
is provided */
|
|
if(data->share)
|
|
Curl_share_lock(data, CURL_LOCK_DATA_DNS, CURL_LOCK_ACCESS_SINGLE);
|
|
|
|
Curl_hash_clean_with_criterium(data->dns.hostcache,
|
|
(void *) &user,
|
|
hostcache_timestamp_remove);
|
|
|
|
if(data->share)
|
|
Curl_share_unlock(data, CURL_LOCK_DATA_DNS);
|
|
|
|
return 1;
|
|
}
|
|
|
|
|
|
#ifdef HAVE_SIGSETJMP
|
|
/* Beware this is a global and unique instance. This is used to store the
|
|
return address that we can jump back to from inside a signal handler. This
|
|
is not thread-safe stuff. */
|
|
sigjmp_buf curl_jmpenv;
|
|
#endif
|
|
|
|
|
|
/*
|
|
* Curl_cache_addr() stores a 'Curl_addrinfo' struct in the DNS cache.
|
|
*
|
|
* When calling Curl_resolv() has resulted in a response with a returned
|
|
* address, we call this function to store the information in the dns
|
|
* cache etc
|
|
*
|
|
* Returns the Curl_dns_entry entry pointer or NULL if the storage failed.
|
|
*/
|
|
struct Curl_dns_entry *
|
|
Curl_cache_addr(struct SessionHandle *data,
|
|
Curl_addrinfo *addr,
|
|
char *hostname,
|
|
int port)
|
|
{
|
|
char *entry_id;
|
|
size_t entry_len;
|
|
struct Curl_dns_entry *dns;
|
|
struct Curl_dns_entry *dns2;
|
|
time_t now;
|
|
|
|
/* Create an entry id, based upon the hostname and port */
|
|
entry_id = create_hostcache_id(hostname, port);
|
|
/* If we can't create the entry id, fail */
|
|
if (!entry_id)
|
|
return NULL;
|
|
entry_len = strlen(entry_id);
|
|
|
|
/* Create a new cache entry */
|
|
dns = (struct Curl_dns_entry *) malloc(sizeof(struct Curl_dns_entry));
|
|
if (!dns) {
|
|
free(entry_id);
|
|
return NULL;
|
|
}
|
|
|
|
dns->inuse = 0; /* init to not used */
|
|
dns->addr = addr; /* this is the address(es) */
|
|
|
|
/* Store the resolved data in our DNS cache. This function may return a
|
|
pointer to an existing struct already present in the hash, and it may
|
|
return the same argument we pass in. Make no assumptions. */
|
|
dns2 = Curl_hash_add(data->dns.hostcache, entry_id, entry_len+1,
|
|
(void *)dns);
|
|
if(!dns2) {
|
|
/* Major badness, run away. */
|
|
free(dns);
|
|
free(entry_id);
|
|
return NULL;
|
|
}
|
|
time(&now);
|
|
dns = dns2;
|
|
|
|
dns->timestamp = now; /* used now */
|
|
dns->inuse++; /* mark entry as in-use */
|
|
|
|
/* free the allocated entry_id again */
|
|
free(entry_id);
|
|
|
|
return dns;
|
|
}
|
|
|
|
/*
|
|
* Curl_resolv() is the main name resolve function within libcurl. It resolves
|
|
* a name and returns a pointer to the entry in the 'entry' argument (if one
|
|
* is provided). This function might return immediately if we're using asynch
|
|
* resolves. See the return codes.
|
|
*
|
|
* The cache entry we return will get its 'inuse' counter increased when this
|
|
* function is used. You MUST call Curl_resolv_unlock() later (when you're
|
|
* done using this struct) to decrease the counter again.
|
|
*
|
|
* Return codes:
|
|
*
|
|
* CURLRESOLV_ERROR (-1) = error, no pointer
|
|
* CURLRESOLV_RESOLVED (0) = OK, pointer provided
|
|
* CURLRESOLV_PENDING (1) = waiting for response, no pointer
|
|
*/
|
|
|
|
int Curl_resolv(struct connectdata *conn,
|
|
char *hostname,
|
|
int port,
|
|
struct Curl_dns_entry **entry)
|
|
{
|
|
char *entry_id = NULL;
|
|
struct Curl_dns_entry *dns = NULL;
|
|
size_t entry_len;
|
|
int wait;
|
|
struct SessionHandle *data = conn->data;
|
|
CURLcode result;
|
|
int rc;
|
|
*entry = NULL;
|
|
|
|
#ifdef HAVE_SIGSETJMP
|
|
/* this allows us to time-out from the name resolver, as the timeout
|
|
will generate a signal and we will siglongjmp() from that here */
|
|
if(!data->set.no_signal && sigsetjmp(curl_jmpenv, 1)) {
|
|
/* this is coming from a siglongjmp() */
|
|
failf(data, "name lookup timed out");
|
|
return CURLRESOLV_ERROR;
|
|
}
|
|
#endif
|
|
|
|
/* Create an entry id, based upon the hostname and port */
|
|
entry_id = create_hostcache_id(hostname, port);
|
|
/* If we can't create the entry id, fail */
|
|
if (!entry_id)
|
|
return CURLRESOLV_ERROR;
|
|
|
|
entry_len = strlen(entry_id);
|
|
|
|
if(data->share)
|
|
Curl_share_lock(data, CURL_LOCK_DATA_DNS, CURL_LOCK_ACCESS_SINGLE);
|
|
|
|
/* See if its already in our dns cache */
|
|
dns = Curl_hash_pick(data->dns.hostcache, entry_id, entry_len+1);
|
|
|
|
if(data->share)
|
|
Curl_share_unlock(data, CURL_LOCK_DATA_DNS);
|
|
|
|
/* free the allocated entry_id again */
|
|
free(entry_id);
|
|
|
|
/* See whether the returned entry is stale. Deliberately done after the
|
|
locked block */
|
|
if ( remove_entry_if_stale(data,dns) )
|
|
dns = NULL; /* the memory deallocation is being handled by the hash */
|
|
|
|
rc = CURLRESOLV_ERROR; /* default to failure */
|
|
|
|
if (!dns) {
|
|
/* The entry was not in the cache. Resolve it to IP address */
|
|
|
|
Curl_addrinfo *addr;
|
|
|
|
/* Check what IP specifics the app has requested and if we can provide it.
|
|
* If not, bail out. */
|
|
if(!Curl_ipvalid(data))
|
|
return CURLRESOLV_ERROR;
|
|
|
|
/* If Curl_getaddrinfo() returns NULL, 'wait' might be set to a non-zero
|
|
value indicating that we need to wait for the response to the resolve
|
|
call */
|
|
addr = Curl_getaddrinfo(conn, hostname, port, &wait);
|
|
|
|
if (!addr) {
|
|
if(wait) {
|
|
/* the response to our resolve call will come asynchronously at
|
|
a later time, good or bad */
|
|
/* First, check that we haven't received the info by now */
|
|
result = Curl_is_resolved(conn, &dns);
|
|
if(result) /* error detected */
|
|
return CURLRESOLV_ERROR;
|
|
if(dns)
|
|
rc = CURLRESOLV_RESOLVED; /* pointer provided */
|
|
else
|
|
rc = CURLRESOLV_PENDING; /* no info yet */
|
|
}
|
|
}
|
|
else {
|
|
if(data->share)
|
|
Curl_share_lock(data, CURL_LOCK_DATA_DNS, CURL_LOCK_ACCESS_SINGLE);
|
|
|
|
/* we got a response, store it in the cache */
|
|
dns = Curl_cache_addr(data, addr, hostname, port);
|
|
|
|
if(data->share)
|
|
Curl_share_unlock(data, CURL_LOCK_DATA_DNS);
|
|
|
|
if(!dns)
|
|
/* returned failure, bail out nicely */
|
|
Curl_freeaddrinfo(addr);
|
|
else
|
|
rc = CURLRESOLV_RESOLVED;
|
|
}
|
|
}
|
|
else {
|
|
if(data->share)
|
|
Curl_share_lock(data, CURL_LOCK_DATA_DNS, CURL_LOCK_ACCESS_SINGLE);
|
|
dns->inuse++; /* we use it! */
|
|
if(data->share)
|
|
Curl_share_unlock(data, CURL_LOCK_DATA_DNS);
|
|
rc = CURLRESOLV_RESOLVED;
|
|
}
|
|
|
|
*entry = dns;
|
|
|
|
return rc;
|
|
}
|
|
|
|
/*
|
|
* Curl_resolv_unlock() unlocks the given cached DNS entry. When this has been
|
|
* made, the struct may be destroyed due to pruning. It is important that only
|
|
* one unlock is made for each Curl_resolv() call.
|
|
*/
|
|
void Curl_resolv_unlock(struct SessionHandle *data, struct Curl_dns_entry *dns)
|
|
{
|
|
curlassert(dns && (dns->inuse>0));
|
|
|
|
if(data->share)
|
|
Curl_share_lock(data, CURL_LOCK_DATA_DNS, CURL_LOCK_ACCESS_SINGLE);
|
|
|
|
dns->inuse--;
|
|
|
|
if(data->share)
|
|
Curl_share_unlock(data, CURL_LOCK_DATA_DNS);
|
|
}
|
|
|
|
/*
|
|
* File-internal: free a cache dns entry.
|
|
*/
|
|
static void freednsentry(void *freethis)
|
|
{
|
|
struct Curl_dns_entry *p = (struct Curl_dns_entry *) freethis;
|
|
|
|
Curl_freeaddrinfo(p->addr);
|
|
|
|
free(p);
|
|
}
|
|
|
|
/*
|
|
* Curl_mk_dnscache() creates a new DNS cache and returns the handle for it.
|
|
*/
|
|
struct curl_hash *Curl_mk_dnscache(void)
|
|
{
|
|
return Curl_hash_alloc(7, freednsentry);
|
|
}
|
|
|
|
#ifdef CURLRES_ADDRINFO_COPY
|
|
|
|
/* align on even 64bit boundaries */
|
|
#define MEMALIGN(x) ((x)+(8-(((unsigned long)(x))&0x7)))
|
|
|
|
/*
|
|
* Curl_addrinfo_copy() performs a "deep" copy of a hostent into a buffer and
|
|
* returns a pointer to the malloc()ed copy. You need to call free() on the
|
|
* returned buffer when you're done with it.
|
|
*/
|
|
Curl_addrinfo *Curl_addrinfo_copy(void *org, int port)
|
|
{
|
|
struct hostent *orig = org;
|
|
|
|
return Curl_he2ai(orig, port);
|
|
}
|
|
#endif /* CURLRES_ADDRINFO_COPY */
|