Newer
Older
/***************************************************************************
* _ _ ____ _
* Project ___| | | | _ \| |
* / __| | | | |_) | |
* | (__| |_| | _ <| |___
Daniel Stenberg
committed
* 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.
***************************************************************************/
#if defined(WIN32) && !defined(__GNUC__) || defined(__MINGW32__)
#else
#ifdef HAVE_SYS_TYPES_H
#include <sys/types.h>
#endif
#ifdef HAVE_SYS_SOCKET_H
#include <sys/socket.h>
#endif
#ifdef HAVE_ARPA_INET_H
#include <arpa/inet.h>
#endif
#include <stdlib.h> /* required for free() prototypes */
Daniel Stenberg
committed
#ifdef HAVE_UNISTD_H
#include <unistd.h> /* for the close() proto */
#endif
#include <inet.h>
#include <stdlib.h>
#endif
Daniel Stenberg
committed
#ifdef HAVE_SETJMP_H
#include <setjmp.h>
#endif
#ifdef WIN32
#include <process.h>
#endif
#include "hash.h"
#include "strerror.h"
Daniel Stenberg
committed
#include "url.h"
Daniel Stenberg
committed
#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 "memory.h"
/* The last #include file should be: */
#include "memdebug.h"
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
/*
* 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 */
Daniel Stenberg
committed
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
*/
Daniel Stenberg
committed
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) {
Daniel Stenberg
committed
Curl_hash_clean(&hostname_cache);
host_cache_initialized = 0;
}
}
Daniel Stenberg
committed
/*
* Return # of adresses in a Curl_addrinfo struct
*/
int Curl_num_addresses(const Curl_addrinfo *addr)
{
int i;
Daniel Stenberg
committed
for (i = 0; addr; addr = addr->ai_next, i++)
; /* empty loop */
Daniel Stenberg
committed
return i;
Daniel Stenberg
committed
}
/*
* 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.
Daniel Stenberg
committed
*
* If the conversion fails, it returns NULL.
*/
Daniel Stenberg
committed
const char *Curl_printable_address(const Curl_addrinfo *ip,
Daniel Stenberg
committed
char *buf, size_t bufsize)
{
const void *ip4 = &((const struct sockaddr_in*)ip->ai_addr)->sin_addr;
int af = ip->ai_family;
Daniel Stenberg
committed
#ifdef CURLRES_IPV6
const void *ip6 = &((const struct sockaddr_in6*)ip->ai_addr)->sin6_addr;
Daniel Stenberg
committed
#else
Daniel Stenberg
committed
const void *ip6 = NULL;
Daniel Stenberg
committed
#endif
Daniel Stenberg
committed
return Curl_inet_ntop(af, af == AF_INET ? ip4 : ip6, buf, bufsize);
Daniel Stenberg
committed
}
/*
* Return a hostcache id string for the providing host + port, to be used by
* the DNS caching.
*/
create_hostcache_id(char *server, int port)
/* create and return the new allocated entry */
return aprintf("%s:%d", server, port);
Daniel Stenberg
committed
struct hostcache_prune_data {
/*
* 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.
*/
hostcache_timestamp_remove(void *datap, void *hc)
Daniel Stenberg
committed
(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 */
/* fine, remove */
/*
* Prune the DNS cache. This assumes that a lock has already been taken.
*/
Daniel Stenberg
committed
hostcache_prune(struct curl_hash *hostcache, int cache_timeout, time_t now)
Daniel Stenberg
committed
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;
Daniel Stenberg
committed
Daniel Stenberg
committed
if((data->set.dns_cache_timeout == -1) || !data->hostcache)
/* cache forever means never prune, and NULL hostcache means
we can't do it */
Daniel Stenberg
committed
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->hostcache,
data->set.dns_cache_timeout,
now);
if(data->share)
Curl_share_unlock(data, CURL_LOCK_DATA_DNS);
}
Daniel Stenberg
committed
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
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->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->hostcache,
(void *) &user,
hostcache_timestamp_remove);
if(data->share)
Curl_share_unlock(data, CURL_LOCK_DATA_DNS);
return 1;
}
Daniel Stenberg
committed
#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. */
Daniel Stenberg
committed
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)
{
Daniel Stenberg
committed
char *entry_id;
Daniel Stenberg
committed
size_t entry_len;
Daniel Stenberg
committed
struct Curl_dns_entry *dns;
struct Curl_dns_entry *dns2;
Daniel Stenberg
committed
time_t now;
/* Create an entry id, based upon the hostname and port */
entry_id = create_hostcache_id(hostname, port);
Daniel Stenberg
committed
/* If we can't create the entry id, fail */
if (!entry_id)
return NULL;
entry_len = strlen(entry_id);
Daniel Stenberg
committed
/* Create a new cache entry */
dns = (struct Curl_dns_entry *) malloc(sizeof(struct Curl_dns_entry));
if (!dns) {
free(entry_id);
return NULL;
}
Daniel Stenberg
committed
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->hostcache, entry_id, entry_len+1, (void *)dns);
if(!dns2) {
/* Major badness, run away. */
free(dns);
Daniel Stenberg
committed
free(entry_id);
return NULL;
}
Daniel Stenberg
committed
time(&now);
dns = dns2;
Daniel Stenberg
committed
Daniel Stenberg
committed
dns->timestamp = now; /* used now */
Daniel Stenberg
committed
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:
*
Daniel Stenberg
committed
* CURLRESOLV_ERROR (-1) = error, no pointer
* CURLRESOLV_RESOLVED (0) = OK, pointer provided
* CURLRESOLV_PENDING (1) = waiting for response, no pointer
*/
Daniel Stenberg
committed
Daniel Stenberg
committed
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;
Daniel Stenberg
committed
size_t entry_len;
Daniel Stenberg
committed
int wait;
struct SessionHandle *data = conn->data;
CURLcode result;
int rc;
Daniel Stenberg
committed
*entry = NULL;
Sterling Hughes
committed
Daniel Stenberg
committed
#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)) {
Daniel Stenberg
committed
/* this is coming from a siglongjmp() */
Daniel Stenberg
committed
return CURLRESOLV_ERROR;
Daniel Stenberg
committed
}
#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)
Daniel Stenberg
committed
return CURLRESOLV_ERROR;
entry_len = strlen(entry_id);
if(data->share)
Curl_share_lock(data, CURL_LOCK_DATA_DNS, CURL_LOCK_ACCESS_SINGLE);
Sterling Hughes
committed
/* See if its already in our dns cache */
dns = Curl_hash_pick(data->hostcache, entry_id, entry_len+1);
Daniel Stenberg
committed
if(data->share)
Curl_share_unlock(data, CURL_LOCK_DATA_DNS);
Sterling Hughes
committed
Daniel Stenberg
committed
/* free the allocated entry_id again */
free(entry_id);
Daniel Stenberg
committed
/* 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) {
Daniel Stenberg
committed
/* 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))
Daniel Stenberg
committed
return CURLRESOLV_ERROR;
/* If Curl_getaddrinfo() returns NULL, 'wait' might be set to a non-zero
Daniel Stenberg
committed
value indicating that we need to wait for the response to the resolve
call */
addr = Curl_getaddrinfo(conn, hostname, port, &wait);
if (!addr) {
Daniel Stenberg
committed
if(wait) {
/* the response to our resolve call will come asynchronously at
Daniel Stenberg
committed
a later time, good or bad */
Daniel Stenberg
committed
/* First, check that we haven't received the info by now */
result = Curl_is_resolved(conn, &dns);
if(result) /* error detected */
Daniel Stenberg
committed
return CURLRESOLV_ERROR;
Daniel Stenberg
committed
if(dns)
Daniel Stenberg
committed
rc = CURLRESOLV_RESOLVED; /* pointer provided */
Daniel Stenberg
committed
else
Daniel Stenberg
committed
rc = CURLRESOLV_PENDING; /* no info yet */
Daniel Stenberg
committed
}
}
else {
Daniel Stenberg
committed
if(data->share)
Curl_share_lock(data, CURL_LOCK_DATA_DNS, CURL_LOCK_ACCESS_SINGLE);
Daniel Stenberg
committed
/* we got a response, store it in the cache */
dns = Curl_cache_addr(data, addr, hostname, port);
Daniel Stenberg
committed
if(data->share)
Curl_share_unlock(data, CURL_LOCK_DATA_DNS);
if(!dns)
/* returned failure, bail out nicely */
Curl_freeaddrinfo(addr);
Daniel Stenberg
committed
rc = CURLRESOLV_RESOLVED;
Daniel Stenberg
committed
}
}
else {
Daniel Stenberg
committed
if(data->share)
Curl_share_lock(data, CURL_LOCK_DATA_DNS, CURL_LOCK_ACCESS_SINGLE);
dns->inuse++; /* we use it! */
Daniel Stenberg
committed
if(data->share)
Curl_share_unlock(data, CURL_LOCK_DATA_DNS);
Daniel Stenberg
committed
rc = CURLRESOLV_RESOLVED;
}
Sterling Hughes
committed
Daniel Stenberg
committed
*entry = dns;
Daniel Stenberg
committed
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);
if(data->share)
Curl_share_unlock(data, CURL_LOCK_DATA_DNS);
}
Daniel Stenberg
committed
/*
* File-internal: free a cache dns entry.
Daniel Stenberg
committed
*/
static void freednsentry(void *freethis)
Daniel Stenberg
committed
{
struct Curl_dns_entry *p = (struct Curl_dns_entry *) freethis;
Curl_freeaddrinfo(p->addr);
Sterling Hughes
committed
free(p);
}
/*
* Curl_mk_dnscache() creates a new DNS cache and returns the handle for it.
*/
Daniel Stenberg
committed
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)))
Daniel Stenberg
committed
/*
* 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.
Daniel Stenberg
committed
*/
Daniel Stenberg
committed
Curl_addrinfo *Curl_addrinfo_copy(void *org, int port)
Daniel Stenberg
committed
{
Daniel Stenberg
committed
struct hostent *orig = org;
Daniel Stenberg
committed
Daniel Stenberg
committed
return Curl_he2ai(orig, port);
Daniel Stenberg
committed
}
#endif /* CURLRES_ADDRINFO_COPY */