From 648e82f05d8bce097f9f81b78d9df40f647f6170 Mon Sep 17 00:00:00 2001 From: Daniel Stenberg Date: Mon, 26 Apr 2004 07:20:11 +0000 Subject: [PATCH] Major hostip.c cleanup and split into multiple files and easier #ifdef usage. --- lib/Makefile.am | 3 +- lib/hostares.c | 298 +++++++++++++ lib/hostasyn.c | 152 +++++++ lib/hostip.c | 1139 +++++------------------------------------------ lib/hostip.h | 141 +++++- lib/hostip4.c | 400 +++++++++++++++++ lib/hostip6.c | 284 ++++++++++++ lib/hostsyn.c | 150 +++++++ lib/hostthre.c | 556 +++++++++++++++++++++++ lib/inet_ntop.c | 189 ++++++++ lib/inet_ntop.h | 37 ++ lib/memdebug.h | 7 +- lib/multi.c | 5 +- lib/setup.h | 12 +- lib/url.c | 130 +++--- lib/url.h | 3 +- lib/urldata.h | 17 +- 17 files changed, 2400 insertions(+), 1123 deletions(-) create mode 100644 lib/hostares.c create mode 100644 lib/hostasyn.c create mode 100644 lib/hostip4.c create mode 100644 lib/hostip6.c create mode 100644 lib/hostsyn.c create mode 100644 lib/hostthre.c create mode 100644 lib/inet_ntop.c create mode 100644 lib/inet_ntop.h diff --git a/lib/Makefile.am b/lib/Makefile.am index e50cd6b7b..5dbaaf0cb 100644 --- a/lib/Makefile.am +++ b/lib/Makefile.am @@ -93,7 +93,8 @@ libcurl_la_SOURCES = arpa_telnet.h file.c netrc.h timeval.c base64.c \ content_encoding.c content_encoding.h share.c share.h http_digest.c \ md5.c md5.h http_digest.h http_negotiate.c http_negotiate.h \ http_ntlm.c http_ntlm.h ca-bundle.h inet_pton.c inet_pton.h \ - strtoofft.c strtoofft.h strerror.c strerror.h + strtoofft.c strtoofft.h strerror.c strerror.h hostares.c hostasyn.c \ + hostip4.c hostip6.c hostsyn.c hostthre.c inet_ntop.c inet_ntop.h noinst_HEADERS = setup.h transfer.h diff --git a/lib/hostares.c b/lib/hostares.c new file mode 100644 index 000000000..1db87e483 --- /dev/null +++ b/lib/hostares.c @@ -0,0 +1,298 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 1998 - 2004, Daniel Stenberg, , 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 +#include + +#define _REENTRANT + +#if defined(WIN32) && !defined(__GNUC__) || defined(__MINGW32__) +#include +#else +#ifdef HAVE_SYS_TYPES_H +#include +#endif +#ifdef HAVE_SYS_SOCKET_H +#include +#endif +#ifdef HAVE_NETINET_IN_H +#include +#endif +#ifdef HAVE_NETDB_H +#include +#endif +#ifdef HAVE_ARPA_INET_H +#include +#endif +#ifdef HAVE_STDLIB_H +#include /* required for free() prototypes */ +#endif +#ifdef HAVE_UNISTD_H +#include /* for the close() proto */ +#endif +#ifdef VMS +#include +#include +#include +#endif +#endif + +#ifdef HAVE_SETJMP_H +#include +#endif + +#ifdef WIN32 +#include +#endif + +#if (defined(NETWARE) && defined(__NOVELL_LIBC__)) +#undef in_addr_t +#define in_addr_t unsigned long +#endif + +#include "urldata.h" +#include "sendf.h" +#include "hostip.h" +#include "hash.h" +#include "share.h" +#include "strerror.h" +#include "url.h" + +#define _MPRINTF_REPLACE /* use our functions only */ +#include + +#if defined(HAVE_INET_NTOA_R) && !defined(HAVE_INET_NTOA_R_DECL) +#include "inet_ntoa_r.h" +#endif + +/* The last #include file should be: */ +#ifdef CURLDEBUG +#include "memdebug.h" +#endif + +/*********************************************************************** + * Only for ares-enabled builds + **********************************************************************/ + +#ifdef CURLRES_ARES + +/* + * Curl_fdset() is called when someone from the outside world (using + * curl_multi_fdset()) wants to get our fd_set setup and we're talking with + * ares. The caller must make sure that this function is only called when we + * have a working ares channel. + * + * Returns: CURLE_OK always! + */ + +CURLcode Curl_fdset(struct connectdata *conn, + fd_set *read_fd_set, + fd_set *write_fd_set, + int *max_fdp) + +{ + int max = ares_fds(conn->data->state.areschannel, + read_fd_set, write_fd_set); + *max_fdp = max; + + return CURLE_OK; +} + +/* + * Curl_is_resolved() is called repeatedly to check if a previous name resolve + * request has completed. It should also make sure to time-out if the + * operation seems to take too long. + * + * Returns normal CURLcode errors. + */ +CURLcode Curl_is_resolved(struct connectdata *conn, + struct Curl_dns_entry **dns) +{ + fd_set read_fds, write_fds; + struct timeval tv={0,0}; + int count; + struct SessionHandle *data = conn->data; + int nfds; + + FD_ZERO(&read_fds); + FD_ZERO(&write_fds); + + nfds = ares_fds(data->state.areschannel, &read_fds, &write_fds); + + count = select(nfds, &read_fds, &write_fds, NULL, + (struct timeval *)&tv); + + /* Call ares_process() unconditonally here, even if we simply timed out + above, as otherwise the ares name resolve won't timeout! */ + ares_process(data->state.areschannel, &read_fds, &write_fds); + + *dns = NULL; + + if(conn->async.done) { + /* we're done, kill the ares handle */ + if(!conn->async.dns) + return CURLE_COULDNT_RESOLVE_HOST; + *dns = conn->async.dns; + } + + return CURLE_OK; +} + +/* + * Curl_wait_for_resolv() waits for a resolve to finish. This function should + * be avoided since using this risk getting the multi interface to "hang". + * + * If 'entry' is non-NULL, make it point to the resolved dns entry + * + * Returns CURLE_COULDNT_RESOLVE_HOST if the host was not resolved, and + * CURLE_OPERATION_TIMEDOUT if a time-out occurred. + */ +CURLcode Curl_wait_for_resolv(struct connectdata *conn, + struct Curl_dns_entry **entry) +{ + CURLcode rc=CURLE_OK; + struct SessionHandle *data = conn->data; + long timeout = CURL_TIMEOUT_RESOLVE; /* default name resolve timeout */ + + /* now, see if there's a connect timeout or a regular timeout to + use instead of the default one */ + if(conn->data->set.connecttimeout) + timeout = conn->data->set.connecttimeout; + else if(conn->data->set.timeout) + timeout = conn->data->set.timeout; + + /* We convert the number of seconds into number of milliseconds here: */ + if(timeout < 2147483) + /* maximum amount of seconds that can be multiplied with 1000 and + still fit within 31 bits */ + timeout *= 1000; + else + timeout = 0x7fffffff; /* ridiculous amount of time anyway */ + + /* Wait for the name resolve query to complete. */ + while (1) { + int nfds=0; + fd_set read_fds, write_fds; + struct timeval *tvp, tv, store; + int count; + struct timeval now = Curl_tvnow(); + long timediff; + + store.tv_sec = (int)timeout/1000; + store.tv_usec = (timeout%1000)*1000; + + FD_ZERO(&read_fds); + FD_ZERO(&write_fds); + nfds = ares_fds(data->state.areschannel, &read_fds, &write_fds); + if (nfds == 0) + /* no file descriptors means we're done waiting */ + break; + tvp = ares_timeout(data->state.areschannel, &store, &tv); + count = select(nfds, &read_fds, &write_fds, NULL, tvp); + if (count < 0 && errno != EINVAL) + break; + + ares_process(data->state.areschannel, &read_fds, &write_fds); + + timediff = Curl_tvdiff(Curl_tvnow(), now); /* spent time */ + timeout -= timediff?timediff:1; /* always deduct at least 1 */ + if (timeout < 0) { + /* our timeout, so we cancel the ares operation */ + ares_cancel(data->state.areschannel); + break; + } + } + + /* Operation complete, if the lookup was successful we now have the entry + in the cache. */ + + if(entry) + *entry = conn->async.dns; + + if(!conn->async.dns) { + /* a name was not resolved */ + if((timeout < 0) || (conn->async.status == ARES_ETIMEOUT)) { + failf(data, "Resolving host timed out: %s", conn->hostname); + rc = CURLE_OPERATION_TIMEDOUT; + } + else if(conn->async.done) { + failf(data, "Could not resolve host: %s (%s)", conn->hostname, + ares_strerror(conn->async.status)); + rc = CURLE_COULDNT_RESOLVE_HOST; + } + else + rc = CURLE_OPERATION_TIMEDOUT; + + /* close the connection, since we can't return failure here without + cleaning up this connection properly */ + Curl_disconnect(conn); + } + + return rc; +} + +/* + * Curl_getaddrinfo() - when using ares + * + * Returns name information about the given hostname and port number. If + * successful, the 'hostent' is returned and the forth argument will point to + * memory we need to free after use. That memory *MUST* be freed with + * Curl_freeaddrinfo(), nothing else. + */ +Curl_addrinfo *Curl_getaddrinfo(struct connectdata *conn, + char *hostname, + int port, + int *waitp) +{ + char *bufp; + struct SessionHandle *data = conn->data; + in_addr_t in = inet_addr(hostname); + + *waitp = FALSE; + + if (in != CURL_INADDR_NONE) + /* This is a dotted IP address 123.123.123.123-style */ + return Curl_ip2addr(in, hostname); + + bufp = strdup(hostname); + + if(bufp) { + Curl_safefree(conn->async.hostname); + conn->async.hostname = bufp; + conn->async.port = port; + conn->async.done = FALSE; /* not done */ + conn->async.status = 0; /* clear */ + conn->async.dns = NULL; /* clear */ + + /* areschannel is already setup in the Curl_open() function */ + ares_gethostbyname(data->state.areschannel, hostname, PF_INET, + Curl_addrinfo_callback, conn); + + *waitp = TRUE; /* please wait for the response */ + } + return NULL; /* no struct yet */ +} + +#endif /* CURLRES_ARES */ diff --git a/lib/hostasyn.c b/lib/hostasyn.c new file mode 100644 index 000000000..a38c772d2 --- /dev/null +++ b/lib/hostasyn.c @@ -0,0 +1,152 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 1998 - 2004, Daniel Stenberg, , 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 +#include + +#define _REENTRANT + +#if defined(WIN32) && !defined(__GNUC__) || defined(__MINGW32__) +#include +#else +#ifdef HAVE_SYS_TYPES_H +#include +#endif +#ifdef HAVE_SYS_SOCKET_H +#include +#endif +#ifdef HAVE_NETINET_IN_H +#include +#endif +#ifdef HAVE_NETDB_H +#include +#endif +#ifdef HAVE_ARPA_INET_H +#include +#endif +#ifdef HAVE_STDLIB_H +#include /* required for free() prototypes */ +#endif +#ifdef HAVE_UNISTD_H +#include /* for the close() proto */ +#endif +#ifdef VMS +#include +#include +#include +#endif +#endif + +#ifdef HAVE_SETJMP_H +#include +#endif + +#ifdef WIN32 +#include +#endif + +#if (defined(NETWARE) && defined(__NOVELL_LIBC__)) +#undef in_addr_t +#define in_addr_t unsigned long +#endif + +#include "urldata.h" +#include "sendf.h" +#include "hostip.h" +#include "hash.h" +#include "share.h" +#include "strerror.h" +#include "url.h" + +#define _MPRINTF_REPLACE /* use our functions only */ +#include + +#if defined(HAVE_INET_NTOA_R) && !defined(HAVE_INET_NTOA_R_DECL) +#include "inet_ntoa_r.h" +#endif + +/* The last #include file should be: */ +#ifdef CURLDEBUG +#include "memdebug.h" +#endif + +/*********************************************************************** + * Only for builds using asynchronous name resolves + **********************************************************************/ +#ifdef CURLRES_ASYNCH +/* + * Curl_addrinfo_callback() gets called by ares/gethostbyname_thread() when we + * got the name resolved (or not!). + * + * If the status argument is CURL_ASYNC_SUCCESS, we might need to copy the + * address field since it might be freed when this function returns. This + * operation stores the resolved data in the DNS cache. + * + * NOTE: for IPv6 operations, Curl_addrinfo_copy() returns the same + * pointer it is given as argument! + * + * The storage operation locks and unlocks the DNS cache. + */ +void Curl_addrinfo_callback(void *arg, /* "struct connectdata *" */ + int status, + Curl_addrinfo *hostent) +{ + struct connectdata *conn = (struct connectdata *)arg; + struct Curl_dns_entry *dns = NULL; + + conn->async.done = TRUE; + conn->async.status = status; + + if(CURL_ASYNC_SUCCESS == status) { + + /* + * IPv4: Curl_addrinfo_copy() copies the address and returns an allocated + * version. + * + * IPv6: Curl_addrinfo_copy() returns the input pointer! + */ + Curl_addrinfo *he = Curl_addrinfo_copy(hostent); + if(he) { + struct SessionHandle *data = conn->data; + + if(data->share) + Curl_share_lock(data, CURL_LOCK_DATA_DNS, CURL_LOCK_ACCESS_SINGLE); + + dns = Curl_cache_addr(data, he, + conn->async.hostname, + conn->async.port); + + if(data->share) + Curl_share_unlock(data, CURL_LOCK_DATA_DNS); + } + } + + conn->async.dns = dns; + + /* ipv4: The input hostent struct will be freed by ares when we return from + this function */ +} + +#endif /* CURLRES_ASYNC */ diff --git a/lib/hostip.c b/lib/hostip.c index 32bb03de3..4b57051d7 100644 --- a/lib/hostip.c +++ b/lib/hostip.c @@ -92,58 +92,50 @@ #include "memdebug.h" #endif -#ifndef ARES_SUCCESS -#define ARES_SUCCESS CURLE_OK -#endif - -#define CURL_TIMEOUT_RESOLVE 300 /* when using asynch methods, we allow this - many seconds for a name resolve */ +/* + * 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 curl_hash hostname_cache; static int host_cache_initialized; - static void freednsentry(void *freethis); -/* - * my_getaddrinfo() is the generic low-level name resolve API within this - * source file. There exist three versions of this function - for different - * name resolve layers (selected at build-time). They all take this same set - * of arguments - */ -static Curl_addrinfo *my_getaddrinfo(struct connectdata *conn, - char *hostname, - int port, - int *waitp); - -#if (!defined(HAVE_GETHOSTBYNAME_R) || defined(USE_ARES) || \ - defined(USE_THREADING_GETHOSTBYNAME)) && \ - !defined(ENABLE_IPV6) -static struct hostent* pack_hostent(char** buf, struct hostent* orig); -#endif - -#ifdef USE_THREADING_GETHOSTBYNAME -#ifdef DEBUG_THREADING_GETHOSTBYNAME -/* If this is defined, provide tracing */ -#define TRACE(args) \ - do { trace_it("%u: ", __LINE__); trace_it args; } while (0) - -static void trace_it (const char *fmt, ...); -#else -#define TRACE(x) -#endif - -static bool init_gethostbyname_thread (struct connectdata *conn, - const char *hostname, int port); -struct thread_data { - HANDLE thread_hnd; - unsigned thread_id; - DWORD thread_status; - curl_socket_t dummy_sock; /* dummy for Curl_multi_ares_fdset() */ -}; -#endif - /* * 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 @@ -177,8 +169,8 @@ void Curl_global_host_cache_dtor(void) } /* - * Minor utility-function: - * Count the number of characters that an integer takes up. + * Count the number of characters that an integer would use in a string + * (base 10). */ static int _num_chars(int i) { @@ -201,8 +193,8 @@ static int _num_chars(int i) } /* - * Minor utility-function: - * Create a hostcache id string for the DNS caching. + * 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, size_t *entry_len) @@ -305,7 +297,7 @@ sigjmp_buf curl_jmpenv; /* - * cache_resolv_response() stores a 'Curl_addrinfo' struct in the DNS cache. + * 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 @@ -313,11 +305,11 @@ sigjmp_buf curl_jmpenv; * * Returns the Curl_dns_entry entry pointer or NULL if the storage failed. */ -static struct Curl_dns_entry * -cache_resolv_response(struct SessionHandle *data, - Curl_addrinfo *addr, - char *hostname, - int port) +struct Curl_dns_entry * +Curl_cache_addr(struct SessionHandle *data, + Curl_addrinfo *addr, + char *hostname, + int port) { char *entry_id; size_t entry_len; @@ -424,11 +416,18 @@ int Curl_resolv(struct connectdata *conn, if (!dns) { /* The entry was not in the cache. Resolve it to IP address */ - - /* If my_getaddrinfo() returns NULL, 'wait' might be set to a non-zero + + 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 -1; + + /* 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 */ - Curl_addrinfo *addr = my_getaddrinfo(conn, hostname, port, &wait); + addr = Curl_getaddrinfo(conn, hostname, port, &wait); if (!addr) { if(wait) { @@ -449,7 +448,7 @@ int Curl_resolv(struct connectdata *conn, Curl_share_lock(data, CURL_LOCK_DATA_DNS, CURL_LOCK_ACCESS_SINGLE); /* we got a response, store it in the cache */ - dns = cache_resolv_response(data, addr, hostname, port); + dns = Curl_cache_addr(data, addr, hostname, port); if(data->share) Curl_share_unlock(data, CURL_LOCK_DATA_DNS); @@ -478,36 +477,17 @@ int Curl_resolv(struct connectdata *conn, */ 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--; -#ifdef CURLDEBUG - if(dns->inuse < 0) { - infof(data, "Interal host cache screw-up!"); - *(char **)0=NULL; - } -#endif - if(data->share) Curl_share_unlock(data, CURL_LOCK_DATA_DNS); } -/* - * This is a wrapper function for freeing name information in a protocol - * independent way. This takes care of using the appropriate underlaying - * function. - */ -void Curl_freeaddrinfo(Curl_addrinfo *p) -{ -#ifdef ENABLE_IPV6 - freeaddrinfo(p); -#else - free(p); /* works fine for the ARES case too */ -#endif -} - /* * File-internal: free a cache dns entry. */ @@ -528,470 +508,66 @@ curl_hash *Curl_mk_dnscache(void) return Curl_hash_alloc(7, freednsentry); } -/* --- resolve name or IP-number --- */ - -/* Allocate enough memory to hold the full name information structs and - * everything. OSF1 is known to require at least 8872 bytes. The buffer - * required for storing all possible aliases and IP numbers is according to - * Stevens' Unix Network Programming 2nd edition, p. 304: 8192 bytes! - */ -#define CURL_NAMELOOKUP_SIZE 9000 - -#ifdef USE_ARES - +#ifdef CURLRES_HOSTENT_RELOCATE /* - * Curl_multi_ares_fdset() is called when someone from the outside world - * (using curl_multi_fdset()) wants to get our fd_set setup and we're talking - * with ares. The caller must make sure that this function is only called when - * we have a working ares channel. - * - * Returns: CURLE_OK always! + * Curl_hostent_relocate() ajusts all pointers in the given hostent struct + * according to the offset. This is typically used when a hostent has been + * reallocated and needs to be setup properly on the new address. */ - -CURLcode Curl_multi_ares_fdset(struct connectdata *conn, - fd_set *read_fd_set, - fd_set *write_fd_set, - int *max_fdp) - +void Curl_hostent_relocate(struct hostent *h, long offset) { - int max = ares_fds(conn->data->state.areschannel, - read_fd_set, write_fd_set); - *max_fdp = max; + int i=0; - return CURLE_OK; -} - -/* - * Curl_is_resolved() is called repeatedly to check if a previous name resolve - * request has completed. It should also make sure to time-out if the - * operation seems to take too long. - * - * Returns normal CURLcode errors. - */ -CURLcode Curl_is_resolved(struct connectdata *conn, - struct Curl_dns_entry **dns) -{ - fd_set read_fds, write_fds; - struct timeval tv={0,0}; - int count; - struct SessionHandle *data = conn->data; - int nfds; - - FD_ZERO(&read_fds); - FD_ZERO(&write_fds); - - nfds = ares_fds(data->state.areschannel, &read_fds, &write_fds); - - count = select(nfds, &read_fds, &write_fds, NULL, - (struct timeval *)&tv); - - /* Call ares_process() unconditonally here, even if we simply timed out - above, as otherwise the ares name resolve won't timeout! */ - ares_process(data->state.areschannel, &read_fds, &write_fds); - - *dns = NULL; - - if(conn->async.done) { - /* we're done, kill the ares handle */ - if(!conn->async.dns) - return CURLE_COULDNT_RESOLVE_HOST; - *dns = conn->async.dns; - } - - return CURLE_OK; -} - -/* - * Curl_wait_for_resolv() waits for a resolve to finish. This function should - * be avoided since using this risk getting the multi interface to "hang". - * - * If 'entry' is non-NULL, make it point to the resolved dns entry - * - * Returns CURLE_COULDNT_RESOLVE_HOST if the host was not resolved, and - * CURLE_OPERATION_TIMEDOUT if a time-out occurred. - */ -CURLcode Curl_wait_for_resolv(struct connectdata *conn, - struct Curl_dns_entry **entry) -{ - CURLcode rc=CURLE_OK; - struct SessionHandle *data = conn->data; - long timeout = CURL_TIMEOUT_RESOLVE; /* default name resolve timeout */ - - /* now, see if there's a connect timeout or a regular timeout to - use instead of the default one */ - if(conn->data->set.connecttimeout) - timeout = conn->data->set.connecttimeout; - else if(conn->data->set.timeout) - timeout = conn->data->set.timeout; - - /* We convert the number of seconds into number of milliseconds here: */ - if(timeout < 2147483) - /* maximum amount of seconds that can be multiplied with 1000 and - still fit within 31 bits */ - timeout *= 1000; - else - timeout = 0x7fffffff; /* ridiculous amount of time anyway */ - - /* Wait for the name resolve query to complete. */ - while (1) { - int nfds=0; - fd_set read_fds, write_fds; - struct timeval *tvp, tv, store; - int count; - struct timeval now = Curl_tvnow(); - long timediff; - - store.tv_sec = (int)timeout/1000; - store.tv_usec = (timeout%1000)*1000; - - FD_ZERO(&read_fds); - FD_ZERO(&write_fds); - nfds = ares_fds(data->state.areschannel, &read_fds, &write_fds); - if (nfds == 0) - /* no file descriptors means we're done waiting */ - break; - tvp = ares_timeout(data->state.areschannel, &store, &tv); - count = select(nfds, &read_fds, &write_fds, NULL, tvp); - if (count < 0 && errno != EINVAL) - break; - - ares_process(data->state.areschannel, &read_fds, &write_fds); - - timediff = Curl_tvdiff(Curl_tvnow(), now); /* spent time */ - timeout -= timediff?timediff:1; /* always deduct at least 1 */ - if (timeout < 0) { - /* our timeout, so we cancel the ares operation */ - ares_cancel(data->state.areschannel); - break; + h->h_name=(char *)((long)h->h_name+offset); + if(h->h_aliases) { + /* only relocate aliases if there are any! */ + h->h_aliases=(char **)((long)h->h_aliases+offset); + while(h->h_aliases[i]) { + h->h_aliases[i]=(char *)((long)h->h_aliases[i]+offset); + i++; } } - /* Operation complete, if the lookup was successful we now have the entry - in the cache. */ - - if(entry) - *entry = conn->async.dns; - - if(!conn->async.dns) { - /* a name was not resolved */ - if((timeout < 0) || (conn->async.status == ARES_ETIMEOUT)) { - failf(data, "Resolving host timed out: %s", conn->hostname); - rc = CURLE_OPERATION_TIMEDOUT; - } - else if(conn->async.done) { - failf(data, "Could not resolve host: %s (%s)", conn->hostname, - ares_strerror(conn->async.status)); - rc = CURLE_COULDNT_RESOLVE_HOST; - } - else - rc = CURLE_OPERATION_TIMEDOUT; - - /* close the connection, since we can't return failure here without - cleaning up this connection properly */ - Curl_disconnect(conn); + h->h_addr_list=(char **)((long)h->h_addr_list+offset); + i=0; + while(h->h_addr_list[i]) { + h->h_addr_list[i]=(char *)((long)h->h_addr_list[i]+offset); + i++; } - - return rc; } -#endif +#endif /* CURLRES_HOSTENT_RELOCATE */ -#if defined(USE_ARES) || defined(USE_THREADING_GETHOSTBYNAME) +#ifdef CURLRES_ADDRINFO_COPY + +/* align on even 64bit boundaries */ +#define MEMALIGN(x) ((x)+(8-(((unsigned long)(x))&0x7))) /* - * host_callback() gets called by ares/gethostbyname_thread() when we got the - * name resolved (or not!). - * - * If the status argument is ARES_SUCCESS, we must copy the hostent field - * since ares will free it when this function returns. This operation stores - * the resolved data in the DNS cache. - * - * The storage operation locks and unlocks the DNS cache. + * 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. */ -static void host_callback(void *arg, /* "struct connectdata *" */ - int status, - struct hostent *hostent) +Curl_addrinfo *Curl_addrinfo_copy(Curl_addrinfo *orig) { - struct connectdata *conn = (struct connectdata *)arg; - struct Curl_dns_entry *dns = NULL; - - conn->async.done = TRUE; - conn->async.status = status; - - if(ARES_SUCCESS == status) { - /* we got a resolved name in 'hostent' */ - char *bufp = (char *)malloc(CURL_NAMELOOKUP_SIZE); - if(bufp) { - - /* pack_hostent() copies to and shrinks the target buffer */ - struct hostent *he = pack_hostent(&bufp, hostent); - - struct SessionHandle *data = conn->data; - - if(data->share) - Curl_share_lock(data, CURL_LOCK_DATA_DNS, CURL_LOCK_ACCESS_SINGLE); - - dns = cache_resolv_response(data, he, - conn->async.hostname, conn->async.port); - - if(data->share) - Curl_share_unlock(data, CURL_LOCK_DATA_DNS); - } - } - - conn->async.dns = dns; - - /* The input hostent struct will be freed by ares when we return from this - function */ -} -#endif - -#ifdef USE_ARES -/* - * my_getaddrinfo() when using ares for name resolves. - * - * Returns name information about the given hostname and port number. If - * successful, the 'hostent' is returned and the forth argument will point to - * memory we need to free after use. That memory *MUST* be freed with - * Curl_freeaddrinfo(), nothing else. - */ -static Curl_addrinfo *my_getaddrinfo(struct connectdata *conn, - char *hostname, - int port, - int *waitp) -{ - char *bufp; - struct SessionHandle *data = conn->data; - - *waitp = FALSE; - - if(data->set.ip_version == CURL_IPRESOLVE_V6) - /* an ipv6 address was requested and we can't get/use one */ - return NULL; - - bufp = strdup(hostname); - - if(bufp) { - Curl_safefree(conn->async.hostname); - conn->async.hostname = bufp; - conn->async.port = port; - conn->async.done = FALSE; /* not done */ - conn->async.status = 0; /* clear */ - conn->async.dns = NULL; /* clear */ - - /* areschannel is already setup in the Curl_open() function */ - ares_gethostbyname(data->state.areschannel, hostname, PF_INET, - host_callback, conn); - - *waitp = TRUE; /* please wait for the response */ - } - return NULL; /* no struct yet */ -} -#endif - -#if !defined(USE_ARES) && !defined(USE_THREADING_GETHOSTBYNAME) - -/* - * Curl_wait_for_resolv() for builds without ARES and threaded gethostbyname, - * Curl_resolv() can never return wait==TRUE, so this function will never be - * called. If it still gets called, we return failure at once. - * - * We provide this function only to allow multi.c to remain unaware if we are - * doing asynch resolves or not. - */ -CURLcode Curl_wait_for_resolv(struct connectdata *conn, - struct Curl_dns_entry **entry) -{ - (void)conn; - *entry=NULL; - return CURLE_COULDNT_RESOLVE_HOST; -} - -/* - * This function will never be called when built with ares or threaded - * resolves. If it still gets called, we return failure at once. - * - * We provide this function only to allow multi.c to remain unaware if we are - * doing asynch resolves or not. - */ -CURLcode Curl_is_resolved(struct connectdata *conn, - struct Curl_dns_entry **dns) -{ - (void)conn; - *dns = NULL; - - return CURLE_COULDNT_RESOLVE_HOST; -} -#endif - -#if !defined(USE_ARES) -/* - * Non-ares build. If we are using threading gethostbyname, then this must - * set the fd_set for the threaded resolve socket. If not, we just return OK. - */ -CURLcode Curl_multi_ares_fdset(struct connectdata *conn, - fd_set *read_fd_set, - fd_set *write_fd_set, - int *max_fdp) -{ -#ifdef USE_THREADING_GETHOSTBYNAME - const struct thread_data *td = - (const struct thread_data *) conn->async.os_specific; - - if (td && td->dummy_sock != CURL_SOCKET_BAD) { - FD_SET(td->dummy_sock,write_fd_set); - *max_fdp = td->dummy_sock; - } -#else /* if not USE_THREADING_GETHOSTBYNAME */ - (void)conn; - (void)read_fd_set; - (void)write_fd_set; - (void)max_fdp; -#endif - return CURLE_OK; -} -#endif /* !USE_ARES */ - -#if defined(ENABLE_IPV6) && !defined(USE_ARES) - -#ifdef CURLDEBUG -/* These two are strictly for memory tracing and are using the same - * style as the family otherwise present in memdebug.c. I put these ones - * here since they require a bunch of struct types I didn't wanna include - * in memdebug.c - */ -int curl_getaddrinfo(char *hostname, char *service, - struct addrinfo *hints, - struct addrinfo **result, - int line, const char *source) -{ - int res=(getaddrinfo)(hostname, service, hints, result); - if(0 == res) { - /* success */ - if(logfile) - fprintf(logfile, "ADDR %s:%d getaddrinfo() = %p\n", - source, line, (void *)*result); - } - else { - if(logfile) - fprintf(logfile, "ADDR %s:%d getaddrinfo() failed\n", - source, line); - } - return res; -} - -void curl_freeaddrinfo(struct addrinfo *freethis, - int line, const char *source) -{ - (freeaddrinfo)(freethis); - if(logfile) - fprintf(logfile, "ADDR %s:%d freeaddrinfo(%p)\n", - source, line, (void *)freethis); -} - -#endif - -/* - * my_getaddrinfo() when built ipv6-enabled. - * - * Returns name information about the given hostname and port number. If - * successful, the 'addrinfo' is returned and the forth argument will point to - * memory we need to free after use. That memory *MUST* be freed with - * Curl_freeaddrinfo(), nothing else. - */ -static Curl_addrinfo *my_getaddrinfo(struct connectdata *conn, - char *hostname, - int port, - int *waitp) -{ - struct addrinfo hints, *res; - int error; - char sbuf[NI_MAXSERV]; - int s, pf; - struct SessionHandle *data = conn->data; - - *waitp=0; /* don't wait, we have the response now */ - - /* see if we have an IPv6 stack */ - s = socket(PF_INET6, SOCK_DGRAM, 0); - if (s < 0) { - /* Some non-IPv6 stacks have been found to make very slow name resolves - * when PF_UNSPEC is used, so thus we switch to a mere PF_INET lookup if - * the stack seems to be a non-ipv6 one. */ - - if(data->set.ip_version == CURL_IPRESOLVE_V6) - /* an ipv6 address was requested and we can't get/use one */ - return NULL; - - pf = PF_INET; - } - else { - /* This seems to be an IPv6-capable stack, use PF_UNSPEC for the widest - * possible checks. And close the socket again. - */ - sclose(s); - - /* - * Check if a more limited name resolve has been requested. - */ - switch(data->set.ip_version) { - case CURL_IPRESOLVE_V4: - pf = PF_INET; - break; - case CURL_IPRESOLVE_V6: - pf = PF_INET6; - break; - default: - pf = PF_UNSPEC; - break; - } - } - - memset(&hints, 0, sizeof(hints)); - hints.ai_family = pf; - hints.ai_socktype = SOCK_STREAM; - hints.ai_flags = AI_CANONNAME; - snprintf(sbuf, sizeof(sbuf), "%d", port); - error = getaddrinfo(hostname, sbuf, &hints, &res); - if (error) { - infof(data, "getaddrinfo(3) failed for %s:%d\n", hostname, port); - return NULL; - } - - return res; -} -#else /* following code is IPv4-only */ - -#if !defined(HAVE_GETHOSTBYNAME_R) || defined(USE_ARES) || \ - defined(USE_THREADING_GETHOSTBYNAME) -static void hostcache_fixoffset(struct hostent *h, long offset); - -/* - * pack_hostent() is a file-local function that performs a "deep" copy of a - * hostent into a buffer (returns a pointer to the copy). Make absolutely sure - * the destination buffer is big enough! - */ -static struct hostent* pack_hostent(char** buf, struct hostent* orig) -{ - char *bufptr; char *newbuf; - struct hostent* copy; - + Curl_addrinfo *copy; int i; char *str; size_t len; + char *aptr = (char *)malloc(CURL_HOSTENT_SIZE); + char *bufptr = aptr; - bufptr = *buf; - copy = (struct hostent*)bufptr; + if(!bufptr) + return NULL; /* major bad */ - bufptr += sizeof(struct hostent); + copy = (Curl_addrinfo *)bufptr; + + bufptr += sizeof(Curl_addrinfo); copy->h_name = bufptr; len = strlen(orig->h_name) + 1; strncpy(bufptr, orig->h_name, len); bufptr += len; - /* we align on even 64bit boundaries for safety */ -#define MEMALIGN(x) ((x)+(8-(((unsigned long)(x))&0x7))) - /* This must be aligned properly to work on many CPU architectures! */ bufptr = MEMALIGN(bufptr); @@ -1044,528 +620,15 @@ static struct hostent* pack_hostent(char** buf, struct hostent* orig) /* now, shrink the allocated buffer to the size we actually need, which most often is only a fraction of the original alloc */ - newbuf=(char *)realloc(*buf, (long)(bufptr-*buf)); + newbuf=(char *)realloc(aptr, (long)(bufptr-aptr)); - /* if the alloc moved, we need to adjust things again */ - if(newbuf != *buf) - hostcache_fixoffset((struct hostent*)newbuf, (long)(newbuf-*buf)); + /* if the alloc moved, we need to adjust the hostent struct */ + if(newbuf != aptr) + Curl_hostent_relocate((struct hostent*)newbuf, (long)(newbuf-aptr)); /* setup the return */ - *buf = newbuf; - copy = (struct hostent*)newbuf; + copy = (Curl_addrinfo *)newbuf; return copy; } -#endif - -/* - * hostcache_fixoffset() is a utility-function that corrects all pointers in - * the given hostent struct according to the offset. This is typically used - * when a hostent has been reallocated and needs to be setup properly on the - * new address. - */ -static void hostcache_fixoffset(struct hostent *h, long offset) -{ - int i=0; - - h->h_name=(char *)((long)h->h_name+offset); - if(h->h_aliases) { - /* only relocate aliases if there are any! */ - h->h_aliases=(char **)((long)h->h_aliases+offset); - while(h->h_aliases[i]) { - h->h_aliases[i]=(char *)((long)h->h_aliases[i]+offset); - i++; - } - } - - h->h_addr_list=(char **)((long)h->h_addr_list+offset); - i=0; - while(h->h_addr_list[i]) { - h->h_addr_list[i]=(char *)((long)h->h_addr_list[i]+offset); - i++; - } -} - -#ifndef USE_ARES - -/* - * MakeIP() converts the input binary ipv4-address to an ascii string in the - * dotted numerical format. 'addr' is a pointer to a buffer that is 'addr_len' - * bytes big. 'num' is the 32 bit IP number. - */ -static char *MakeIP(unsigned long num, char *addr, int addr_len) -{ -#if defined(HAVE_INET_NTOA) || defined(HAVE_INET_NTOA_R) - struct in_addr in; - in.s_addr = htonl(num); - -#if defined(HAVE_INET_NTOA_R) - inet_ntoa_r(in,addr,addr_len); -#else - strncpy(addr,inet_ntoa(in),addr_len); -#endif -#else - unsigned char *paddr; - - num = htonl(num); /* htonl() added to avoid endian probs */ - paddr = (unsigned char *)# - sprintf(addr, "%u.%u.%u.%u", paddr[0], paddr[1], paddr[2], paddr[3]); -#endif - return (addr); -} - -/* - * my_getaddrinfo() - the ipv4 "traditional" version. - * - * The original code to this function was once stolen from the Dancer source - * code, written by Bjorn Reese, it has since been patched and modified - * considerably. - */ -static Curl_addrinfo *my_getaddrinfo(struct connectdata *conn, - char *hostname, - int port, - int *waitp) -{ - struct hostent *h = NULL; - in_addr_t in; - struct SessionHandle *data = conn->data; - (void)port; /* unused in IPv4 code */ - - *waitp = 0; /* don't wait, we act synchronously */ - - if(data->set.ip_version == CURL_IPRESOLVE_V6) - /* an ipv6 address was requested and we can't get/use one */ - return NULL; - - in=inet_addr(hostname); - if (in != CURL_INADDR_NONE) { - struct in_addr *addrentry; - struct namebuf { - struct hostent hostentry; - char *h_addr_list[2]; - struct in_addr addrentry; - char h_name[128]; - } *buf = (struct namebuf *)malloc(sizeof(struct namebuf)); - if(!buf) - return NULL; /* major failure */ - - h = &buf->hostentry; - h->h_addr_list = &buf->h_addr_list[0]; - addrentry = &buf->addrentry; - addrentry->s_addr = in; - h->h_addr_list[0] = (char*)addrentry; - h->h_addr_list[1] = NULL; - h->h_addrtype = AF_INET; - h->h_length = sizeof(*addrentry); - h->h_name = &buf->h_name[0]; - MakeIP(ntohl(in), (char *)h->h_name, sizeof(buf->h_name)); - } -#if defined(HAVE_GETHOSTBYNAME_R) - else { - int h_errnop; - int res=ERANGE; - int step_size=200; - int *buf = (int *)malloc(CURL_NAMELOOKUP_SIZE); - if(!buf) - return NULL; /* major failure */ - - /* Workaround for gethostbyname_r bug in qnx nto. It is also _required_ - for some of these functions. */ - memset(buf, 0, CURL_NAMELOOKUP_SIZE); -#ifdef HAVE_GETHOSTBYNAME_R_5 - /* Solaris, IRIX and more */ - (void)res; /* prevent compiler warning */ - while(!h) { - h = gethostbyname_r(hostname, - (struct hostent *)buf, - (char *)buf + sizeof(struct hostent), - step_size - sizeof(struct hostent), - &h_errnop); - - /* If the buffer is too small, it returns NULL and sets errno to - ERANGE. The errno is thread safe if this is compiled with - -D_REENTRANT as then the 'errno' variable is a macro defined to - get used properly for threads. */ - - if(h || (errno != ERANGE)) - break; - - step_size+=200; - } - -#ifdef CURLDEBUG - infof(data, "gethostbyname_r() uses %d bytes\n", step_size); -#endif - - if(h) { - int offset; - h=(struct hostent *)realloc(buf, step_size); - offset=(long)h-(long)buf; - hostcache_fixoffset(h, offset); - buf=(int *)h; - } - else -#endif /* HAVE_GETHOSTBYNAME_R_5 */ -#ifdef HAVE_GETHOSTBYNAME_R_6 - /* Linux */ - do { - res=gethostbyname_r(hostname, - (struct hostent *)buf, - (char *)buf + sizeof(struct hostent), - step_size - sizeof(struct hostent), - &h, /* DIFFERENCE */ - &h_errnop); - /* Redhat 8, using glibc 2.2.93 changed the behavior. Now all of a - sudden this function returns EAGAIN if the given buffer size is too - small. Previous versions are known to return ERANGE for the same - problem. - - This wouldn't be such a big problem if older versions wouldn't - sometimes return EAGAIN on a common failure case. Alas, we can't - assume that EAGAIN *or* ERANGE means ERANGE for any given version of - glibc. - - For now, we do that and thus we may call the function repeatedly and - fail for older glibc versions that return EAGAIN, until we run out - of buffer size (step_size grows beyond CURL_NAMELOOKUP_SIZE). - - If anyone has a better fix, please tell us! - - ------------------------------------------------------------------- - - On October 23rd 2003, Dan C dug up more details on the mysteries of - gethostbyname_r() in glibc: - - In glibc 2.2.5 the interface is different (this has also been - discovered in glibc 2.1.1-6 as shipped by Redhat 6). What I can't - explain, is that tests performed on glibc 2.2.4-34 and 2.2.4-32 - (shipped/upgraded by Redhat 7.2) don't show this behavior! - - In this "buggy" version, the return code is -1 on error and 'errno' - is set to the ERANGE or EAGAIN code. Note that 'errno' is not a - thread-safe variable. - - */ - - if(((ERANGE == res) || (EAGAIN == res)) || - ((res<0) && ((ERANGE == errno) || (EAGAIN == errno)))) - step_size+=200; - else - break; - } while(step_size <= CURL_NAMELOOKUP_SIZE); - - if(!h) /* failure */ - res=1; - -#ifdef CURLDEBUG - infof(data, "gethostbyname_r() uses %d bytes\n", step_size); -#endif - if(!res) { - int offset; - h=(struct hostent *)realloc(buf, step_size); - offset=(long)h-(long)buf; - hostcache_fixoffset(h, offset); - buf=(int *)h; - } - else -#endif/* HAVE_GETHOSTBYNAME_R_6 */ -#ifdef HAVE_GETHOSTBYNAME_R_3 - /* AIX, Digital Unix/Tru64, HPUX 10, more? */ - - /* For AIX 4.3 or later, we don't use gethostbyname_r() at all, because of - the plain fact that it does not return unique full buffers on each - call, but instead several of the pointers in the hostent structs will - point to the same actual data! This have the unfortunate down-side that - our caching system breaks down horribly. Luckily for us though, AIX 4.3 - and more recent versions have a completely thread-safe libc where all - the data is stored in thread-specific memory areas making calls to the - plain old gethostbyname() work fine even for multi-threaded programs. - - This AIX 4.3 or later detection is all made in the configure script. - - Troels Walsted Hansen helped us work this out on March 3rd, 2003. */ - - if(CURL_NAMELOOKUP_SIZE >= - (sizeof(struct hostent)+sizeof(struct hostent_data))) { - - /* August 22nd, 2000: Albert Chin-A-Young brought an updated version - * that should work! September 20: Richard Prescott worked on the buffer - * size dilemma. */ - - res = gethostbyname_r(hostname, - (struct hostent *)buf, - (struct hostent_data *)((char *)buf + - sizeof(struct hostent))); - h_errnop= errno; /* we don't deal with this, but set it anyway */ - } - else - res = -1; /* failure, too smallish buffer size */ - - if(!res) { /* success */ - - h = (struct hostent*)buf; /* result expected in h */ - - /* This is the worst kind of the different gethostbyname_r() interfaces. - Since we don't know how big buffer this particular lookup required, - we can't realloc down the huge alloc without doing closer analysis of - the returned data. Thus, we always use CURL_NAMELOOKUP_SIZE for every - name lookup. Fixing this would require an extra malloc() and then - calling pack_hostent() that subsequent realloc()s down the new memory - area to the actually used amount. */ - } - else -#endif /* HAVE_GETHOSTBYNAME_R_3 */ - { - infof(data, "gethostbyname_r(2) failed for %s\n", hostname); - h = NULL; /* set return code to NULL */ - free(buf); - } -#else /* HAVE_GETHOSTBYNAME_R */ - else { - -#ifdef USE_THREADING_GETHOSTBYNAME - /* fire up a new resolver thread! */ - if (init_gethostbyname_thread(conn,hostname,port)) { - *waitp = TRUE; /* please wait for the response */ - return NULL; - } - infof(data, "init_gethostbyname_thread() failed for %s; code %lu\n", - hostname, GetLastError()); -#endif - h = gethostbyname(hostname); - if (!h) - infof(data, "gethostbyname(2) failed for %s\n", hostname); - else { - char *buf=(char *)malloc(CURL_NAMELOOKUP_SIZE); - /* we make a copy of the hostent right now, right here, as the static - one we got a pointer to might get removed when we don't want/expect - that */ - h = pack_hostent(&buf, h); - } -#endif /*HAVE_GETHOSTBYNAME_R */ - } - - return h; -} - -#endif /* end of IPv4-specific code */ - -#endif /* end of !USE_ARES */ - - -#if defined(USE_THREADING_GETHOSTBYNAME) -#ifdef DEBUG_THREADING_GETHOSTBYNAME -static void trace_it (const char *fmt, ...) -{ - static int do_trace = -1; - va_list args; - - if (do_trace == -1) { - const char *env = getenv("CURL_TRACE"); - do_trace = (env && atoi(env) > 0); - } - if (!do_trace) - return; - va_start (args, fmt); - vfprintf (stderr, fmt, args); -/*fflush (stderr); */ /* seems a bad idea in a multi-threaded app */ - va_end (args); -} -#endif - -/* - * gethostbyname_thread() resolves a name, calls the host_callback and then - * exits. - * - * For builds without ARES/USE_IPV6, create a resolver thread and wait on it. - */ -static unsigned __stdcall gethostbyname_thread (void *arg) -{ - struct connectdata *conn = (struct connectdata*) arg; - struct hostent *he; - int rc; - - WSASetLastError (conn->async.status = NO_DATA); /* pending status */ - he = gethostbyname (conn->async.hostname); - if (he) { - host_callback(conn, ARES_SUCCESS, he); - rc = 1; - } - else { - host_callback(conn, (int)WSAGetLastError(), NULL); - rc = 0; - } - TRACE(("Winsock-error %d, addr %s\n", conn->async.status, - he ? inet_ntoa(*(struct in_addr*)he->h_addr) : "unknown")); - return (rc); - /* An implicit _endthreadex() here */ -} - -/* - * destroy_thread_data() cleans up async resolver data. - * Complementary of ares_destroy. - */ -static void destroy_thread_data (struct Curl_async *async) -{ - if (async->hostname) - free(async->hostname); - - if (async->os_specific) { - curl_socket_t sock = ((const struct thread_data*)async->os_specific)->dummy_sock; - - if (sock != CURL_SOCKET_BAD) - sclose(sock); - free(async->os_specific); - } - async->hostname = NULL; - async->os_specific = NULL; -} - -/* - * init_gethostbyname_thread() starts a new thread that performs - * the actual resolve. This function returns before the resolve is done. - */ -static bool init_gethostbyname_thread (struct connectdata *conn, - const char *hostname, int port) -{ - struct thread_data *td = calloc(sizeof(*td), 1); - - if (!td) { - SetLastError(ENOMEM); - return (0); - } - - Curl_safefree(conn->async.hostname); - conn->async.hostname = strdup(hostname); - if (!conn->async.hostname) { - free(td); - SetLastError(ENOMEM); - return (0); - } - - conn->async.port = port; - conn->async.done = FALSE; - conn->async.status = 0; - conn->async.dns = NULL; - conn->async.os_specific = (void*) td; - - td->dummy_sock = CURL_SOCKET_BAD; - td->thread_hnd = (HANDLE) _beginthreadex(NULL, 0, gethostbyname_thread, - conn, 0, &td->thread_id); - if (!td->thread_hnd) { - SetLastError(errno); - TRACE(("_beginthreadex() failed; %s\n", Curl_strerror(conn,errno))); - destroy_thread_data(&conn->async); - return (0); - } - /* This socket is only to keep Curl_multi_ares_fdset() and select() happy; - * should never become signalled for read/write since it's unbound but - * Windows needs atleast 1 socket in select(). - */ - td->dummy_sock = socket(AF_INET, SOCK_DGRAM, 0); - return (1); -} - -/* - * Curl_wait_for_resolv() waits for a resolve to finish. This function should - * be avoided since using this risk getting the multi interface to "hang". - * - * If 'entry' is non-NULL, make it point to the resolved dns entry - * - * This is the version for resolves-in-a-thread. - */ -CURLcode Curl_wait_for_resolv(struct connectdata *conn, - struct Curl_dns_entry **entry) -{ - struct thread_data *td = (struct thread_data*) conn->async.os_specific; - struct SessionHandle *data = conn->data; - long timeout; - DWORD status, ticks; - CURLcode rc; - - curlassert (conn && td); - - /* now, see if there's a connect timeout or a regular timeout to - use instead of the default one */ - timeout = - conn->data->set.connecttimeout ? conn->data->set.connecttimeout : - conn->data->set.timeout ? conn->data->set.timeout : - CURL_TIMEOUT_RESOLVE; /* default name resolve timeout */ - ticks = GetTickCount(); - - status = WaitForSingleObject(td->thread_hnd, 1000UL*timeout); - if (status == WAIT_OBJECT_0 || status == WAIT_ABANDONED) { - /* Thread finished before timeout; propagate Winsock error to this thread. - * 'conn->async.done = TRUE' is set in host_callback(). - */ - WSASetLastError(conn->async.status); - GetExitCodeThread(td->thread_hnd, &td->thread_status); - TRACE(("gethostbyname_thread() status %lu, thread retval %lu, ", - status, td->thread_status)); - } - else { - conn->async.done = TRUE; - td->thread_status = (DWORD)-1; - TRACE(("gethostbyname_thread() timeout, ")); - } - - TRACE(("elapsed %lu ms\n", GetTickCount()-ticks)); - - CloseHandle(td->thread_hnd); - - if(entry) - *entry = conn->async.dns; - - rc = CURLE_OK; - - if (!conn->async.dns) { - /* a name was not resolved */ - if (td->thread_status == (DWORD)-1 || conn->async.status == NO_DATA) { - failf(data, "Resolving host timed out: %s", conn->hostname); - rc = CURLE_OPERATION_TIMEDOUT; - } - else if(conn->async.done) { - failf(data, "Could not resolve host: %s; %s", - conn->hostname, Curl_strerror(conn,conn->async.status)); - rc = CURLE_COULDNT_RESOLVE_HOST; - } - else - rc = CURLE_OPERATION_TIMEDOUT; - } - - destroy_thread_data(&conn->async); - - if (CURLE_OK != rc) - /* close the connection, since we can't return failure here without - cleaning up this connection properly */ - Curl_disconnect(conn); - - return (rc); -} - -/* - * Curl_is_resolved() is called repeatedly to check if a previous name resolve - * request has completed. It should also make sure to time-out if the - * operation seems to take too long. - */ -CURLcode Curl_is_resolved(struct connectdata *conn, - struct Curl_dns_entry **entry) -{ - *entry = NULL; - - if (conn->async.done) { - /* we're done */ - destroy_thread_data(&conn->async); - if (!conn->async.dns) { - TRACE(("Curl_is_resolved(): CURLE_COULDNT_RESOLVE_HOST\n")); - return CURLE_COULDNT_RESOLVE_HOST; - } - *entry = conn->async.dns; - TRACE(("resolved okay, dns %p\n", *entry)); - } - else - TRACE(("not yet\n")); - return CURLE_OK; -} - -#endif +#endif /* CURLRES_ADDRINFO_COPY */ diff --git a/lib/hostip.h b/lib/hostip.h index b7d212d81..19a62c666 100644 --- a/lib/hostip.h +++ b/lib/hostip.h @@ -51,20 +51,40 @@ struct Curl_dns_entry { * The returned data *MUST* be "unlocked" with Curl_resolv_unlock() after * use, or we'll leak memory! */ - int Curl_resolv(struct connectdata *conn, char *hostname, int port, struct Curl_dns_entry **dnsentry); +/* + * Curl_ipvalid() checks what CURL_IPRESOLVE_* requirements that might've + * been set and returns TRUE if they are OK. + */ +bool Curl_ipvalid(struct SessionHandle *data); + +/* + * Curl_getaddrinfo() is the generic low-level name resolve API within this + * source file. There are several versions of this function - for different + * name resolve layers (selected at build-time). They all take this same set + * of arguments + */ +Curl_addrinfo *Curl_getaddrinfo(struct connectdata *conn, + char *hostname, + int port, + int *waitp); + CURLcode Curl_is_resolved(struct connectdata *conn, struct Curl_dns_entry **dns); CURLcode Curl_wait_for_resolv(struct connectdata *conn, struct Curl_dns_entry **dnsentry); -CURLcode Curl_multi_ares_fdset(struct connectdata *conn, - fd_set *read_fd_set, - fd_set *write_fd_set, - int *max_fdp); + +/* Curl_fdset() is a generic function that exists in multiple versions + depending on what name resolve technology we've built to use. The function + is called from the curl_multi_fdset() function */ +CURLcode Curl_fdset(struct connectdata *conn, + fd_set *read_fd_set, + fd_set *write_fd_set, + int *max_fdp); /* unlock a previously resolved dns entry */ void Curl_resolv_unlock(struct SessionHandle *data, struct Curl_dns_entry *dns); @@ -81,19 +101,120 @@ curl_hash *Curl_mk_dnscache(void); void Curl_hostcache_prune(struct SessionHandle *data); #ifdef CURLDEBUG -void curl_freeaddrinfo(struct addrinfo *freethis, +void curl_dofreeaddrinfo(struct addrinfo *freethis, + int line, const char *source); +int curl_dogetaddrinfo(char *hostname, char *service, + struct addrinfo *hints, + struct addrinfo **result, + int line, const char *source); +int curl_dogetnameinfo(const struct sockaddr *sa, socklen_t salen, + char *host, size_t hostlen, + char *serv, size_t servlen, int flags, int line, const char *source); -int curl_getaddrinfo(char *hostname, char *service, - struct addrinfo *hints, - struct addrinfo **result, - int line, const char *source); #endif +/* This is the callback function that is used when we build with asynch + resolve */ +void Curl_addrinfo_callback(void *arg, + int status, + Curl_addrinfo *hostent); + +/* This is a utility-function for ipv4-builds to create a hostent struct + from a numerical-only IP address */ +Curl_addrinfo *Curl_ip2addr(unsigned long num, char *hostname); + +/* relocate a hostent struct */ +void Curl_hostent_relocate(struct hostent *h, long offset); + +/* copy a Curl_addrinfo struct, currently this only supports copying + a hostent (ipv4-style) struct */ +Curl_addrinfo *Curl_addrinfo_copy(Curl_addrinfo *orig); + +/* + * (IPv6) Curl_printable_address() returns a printable version of the + * ai->ai_addr address given in the 2nd argument. The first should be the + * ai->ai_family and the result will be stored in the buf that is bufsize + * bytes big. + */ +const char *Curl_printable_address(int af, void *addr, + char *buf, size_t bufsize); + +/* + * Curl_cache_addr() stores a 'Curl_addrinfo' struct in the DNS cache. + * + * 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); + #ifndef INADDR_NONE #define CURL_INADDR_NONE (in_addr_t) ~0 #else #define CURL_INADDR_NONE INADDR_NONE #endif +/* + * Setup comfortable CURLRES_* defines to use in the host*.c sources. + */ + +#ifdef USE_ARES +#define CURLRES_ASYNCH +#define CURLRES_ARES +#endif + +#ifdef USE_THREADING_GETHOSTBYNAME +#define CURLRES_ASYNCH +#define CURLRES_THREADED +#endif + +#ifdef USE_THREADING_GETADDRINFO +#define CURLRES_ASYNCH +#define CURLRES_THREADED +#endif + +#ifdef ENABLE_IPV6 +#define CURLRES_IPV6 +#else +#define CURLRES_IPV4 +#endif + +#ifdef CURLRES_IPV4 +#if !defined(HAVE_GETHOSTBYNAME_R) || defined(CURLRES_ASYNCH) +/* If built for ipv4 and missing gethostbyname_r(), or if using async name + resolve, we need the Curl_addrinfo_copy() function (which itself needs the + Curl_hostent_relocate() function)) */ +#define CURLRES_ADDRINFO_COPY +#define CURLRES_HOSTENT_RELOCATE +#endif +#endif /* IPv4-only */ + +#ifdef HAVE_GETHOSTBYNAME_R_6 +#define CURLRES_HOSTENT_RELOCATE +#endif + +#ifdef HAVE_GETHOSTBYNAME_R_5 +#define CURLRES_HOSTENT_RELOCATE +#endif + +#ifndef CURLRES_ASYNCH +#define CURLRES_SYNCH +#endif + +/* Allocate enough memory to hold the full name information structs and + * everything. OSF1 is known to require at least 8872 bytes. The buffer + * required for storing all possible aliases and IP numbers is according to + * Stevens' Unix Network Programming 2nd edition, p. 304: 8192 bytes! + */ +#define CURL_HOSTENT_SIZE 9000 + +#define CURL_TIMEOUT_RESOLVE 300 /* when using asynch methods, we allow this + many seconds for a name resolve */ + +#ifdef CURLRES_ARES +#define CURL_ASYNC_SUCCESS ARES_SUCCESS +#else +#define CURL_ASYNC_SUCCESS CURLE_OK +#endif #endif diff --git a/lib/hostip4.c b/lib/hostip4.c new file mode 100644 index 000000000..bd6e40e73 --- /dev/null +++ b/lib/hostip4.c @@ -0,0 +1,400 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 1998 - 2004, Daniel Stenberg, , 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 +#include + +#define _REENTRANT + +#if defined(WIN32) && !defined(__GNUC__) || defined(__MINGW32__) +#include +#else +#ifdef HAVE_SYS_TYPES_H +#include +#endif +#ifdef HAVE_SYS_SOCKET_H +#include +#endif +#ifdef HAVE_NETINET_IN_H +#include +#endif +#ifdef HAVE_NETDB_H +#include +#endif +#ifdef HAVE_ARPA_INET_H +#include +#endif +#ifdef HAVE_STDLIB_H +#include /* required for free() prototypes */ +#endif +#ifdef HAVE_UNISTD_H +#include /* for the close() proto */ +#endif +#ifdef VMS +#include +#include +#include +#endif +#endif + +#ifdef HAVE_SETJMP_H +#include +#endif + +#ifdef WIN32 +#include +#endif + +#if (defined(NETWARE) && defined(__NOVELL_LIBC__)) +#undef in_addr_t +#define in_addr_t unsigned long +#endif + +#include "urldata.h" +#include "sendf.h" +#include "hostip.h" +#include "hash.h" +#include "share.h" +#include "strerror.h" +#include "url.h" + +#define _MPRINTF_REPLACE /* use our functions only */ +#include + +#if defined(HAVE_INET_NTOA_R) && !defined(HAVE_INET_NTOA_R_DECL) +#include "inet_ntoa_r.h" +#endif + +/* The last #include file should be: */ +#ifdef CURLDEBUG +#include "memdebug.h" +#endif + +/*********************************************************************** + * Only for plain-ipv4 builds + **********************************************************************/ +#ifdef CURLRES_IPV4 /* plain ipv4 code coming up */ + +/* + * This is a wrapper function for freeing name information in a protocol + * independent way. This takes care of using the appropriate underlying + * function. + */ +void Curl_freeaddrinfo(Curl_addrinfo *p) +{ + free(p); /* works fine for the ARES case too */ +} + +/* + * Curl_ipvalid() checks what CURL_IPRESOLVE_* requirements that might've + * been set and returns TRUE if they are OK. + */ +bool Curl_ipvalid(struct SessionHandle *data) +{ + if(data->set.ip_version == CURL_IPRESOLVE_V6) + /* an ipv6 address was requested and we can't get/use one */ + return FALSE; + + return TRUE; /* OK, proceed */ +} + +/* + * Curl_ip2addr() takes a 32bit ipv4 internet address as input parameter + * together with a pointer to the string version of the address, and it + * retruns a malloc()ed version of a hostent struct filled in correctly with + * information for this address/host. + * + * The input parameters ARE NOT checked for validity but they are expected + * to have been checked already when this is called. + */ +Curl_addrinfo *Curl_ip2addr(unsigned long num, char *hostname) +{ + struct hostent *h; + struct in_addr *addrentry; + struct namebuf { + struct hostent hostentry; + char *h_addr_list[2]; + struct in_addr addrentry; + char h_name[16]; /* 123.123.123.123 = 15 letters is maximum */ + } *buf = (struct namebuf *)malloc(sizeof(struct namebuf)); + + if(!buf) + return NULL; /* major failure */ + + h = &buf->hostentry; + h->h_addr_list = &buf->h_addr_list[0]; + addrentry = &buf->addrentry; + addrentry->s_addr = num; + h->h_addr_list[0] = (char*)addrentry; + h->h_addr_list[1] = NULL; + h->h_addrtype = AF_INET; + h->h_length = sizeof(*addrentry); + h->h_name = &buf->h_name[0]; + h->h_aliases = NULL; + + /* Now store the dotted version of the address */ + snprintf(h->h_name, 16, "%s", hostname); + + return h; +} + +#ifdef CURLRES_SYNCH /* the functions below are for synchronous resolves */ + +/* + * Curl_getaddrinfo() - the ipv4 synchronous version. + * + * The original code to this function was once stolen from the Dancer source + * code, written by Bjorn Reese, it has since been patched and modified + * considerably. + * + * gethostbyname_r() is the thread-safe version of the gethostbyname() + * function. When we build for plain IPv4, we attempt to use this + * function. There are _three_ different gethostbyname_r() versions, and we + * detect which one this platform supports in the configure script and set up + * the HAVE_GETHOSTBYNAME_R_3, HAVE_GETHOSTBYNAME_R_5 or + * HAVE_GETHOSTBYNAME_R_6 defines accordingly. Note that HAVE_GETADDRBYNAME + * has the corresponding rules. This is primarily on *nix. Note that some unix + * flavours have thread-safe versions of the plain gethostbyname() etc. + * + */ +Curl_addrinfo *Curl_getaddrinfo(struct connectdata *conn, + char *hostname, + int port, + int *waitp) +{ + struct hostent *h = NULL; + in_addr_t in; + struct SessionHandle *data = conn->data; + (void)port; /* unused in IPv4 code */ + + *waitp = 0; /* don't wait, we act synchronously */ + + in=inet_addr(hostname); + if (in != CURL_INADDR_NONE) + /* This is a dotted IP address 123.123.123.123-style */ + return Curl_ip2addr(in, hostname); + +#if defined(HAVE_GETHOSTBYNAME_R) + /* + * gethostbyname_r() is the preferred resolve function for many platforms. + * Since there are three different versions of it, the following code is + * somewhat #ifdef-ridden. + */ + else { + int h_errnop; + int res=ERANGE; + int step_size=200; + int *buf = (int *)calloc(CURL_HOSTENT_SIZE, 1); + if(!buf) + return NULL; /* major failure */ + /* + * The clearing of the buffer is a workaround for a gethostbyname_r bug in + * qnx nto and it is also _required_ for some of these functions on some + * platforms. + */ + +#ifdef HAVE_GETHOSTBYNAME_R_5 + /* Solaris, IRIX and more */ + (void)res; /* prevent compiler warning */ + while(!h) { + h = gethostbyname_r(hostname, + (struct hostent *)buf, + (char *)buf + sizeof(struct hostent), + step_size - sizeof(struct hostent), + &h_errnop); + + /* If the buffer is too small, it returns NULL and sets errno to + * ERANGE. The errno is thread safe if this is compiled with + * -D_REENTRANT as then the 'errno' variable is a macro defined to get + * used properly for threads. + */ + + if(h || (errno != ERANGE)) + break; + + step_size+=200; + } + +#ifdef CURLDEBUG + infof(data, "gethostbyname_r() uses %d bytes\n", step_size); +#endif + + if(h) { + int offset; + h=(struct hostent *)realloc(buf, step_size); + offset=(long)h-(long)buf; + Curl_hostent_relocate(h, offset); + buf=(int *)h; + } + else +#endif /* HAVE_GETHOSTBYNAME_R_5 */ +#ifdef HAVE_GETHOSTBYNAME_R_6 + /* Linux */ + do { + res=gethostbyname_r(hostname, + (struct hostent *)buf, + (char *)buf + sizeof(struct hostent), + step_size - sizeof(struct hostent), + &h, /* DIFFERENCE */ + &h_errnop); + /* Redhat 8, using glibc 2.2.93 changed the behavior. Now all of a + * sudden this function returns EAGAIN if the given buffer size is too + * small. Previous versions are known to return ERANGE for the same + * problem. + * + * This wouldn't be such a big problem if older versions wouldn't + * sometimes return EAGAIN on a common failure case. Alas, we can't + * assume that EAGAIN *or* ERANGE means ERANGE for any given version of + * glibc. + * + * For now, we do that and thus we may call the function repeatedly and + * fail for older glibc versions that return EAGAIN, until we run out of + * buffer size (step_size grows beyond CURL_HOSTENT_SIZE). + * + * If anyone has a better fix, please tell us! + * + * ------------------------------------------------------------------- + * + * On October 23rd 2003, Dan C dug up more details on the mysteries of + * gethostbyname_r() in glibc: + * + * In glibc 2.2.5 the interface is different (this has also been + * discovered in glibc 2.1.1-6 as shipped by Redhat 6). What I can't + * explain, is that tests performed on glibc 2.2.4-34 and 2.2.4-32 + * (shipped/upgraded by Redhat 7.2) don't show this behavior! + * + * In this "buggy" version, the return code is -1 on error and 'errno' + * is set to the ERANGE or EAGAIN code. Note that 'errno' is not a + * thread-safe variable. + */ + + if(((ERANGE == res) || (EAGAIN == res)) || + ((res<0) && ((ERANGE == errno) || (EAGAIN == errno)))) + step_size+=200; + else + break; + } while(step_size <= CURL_HOSTENT_SIZE); + + if(!h) /* failure */ + res=1; + +#ifdef CURLDEBUG + infof(data, "gethostbyname_r() uses %d bytes\n", step_size); +#endif + if(!res) { + int offset; + h=(struct hostent *)realloc(buf, step_size); + offset=(long)h-(long)buf; + Curl_hostent_relocate(h, offset); + buf=(int *)h; + } + else +#endif/* HAVE_GETHOSTBYNAME_R_6 */ +#ifdef HAVE_GETHOSTBYNAME_R_3 + /* AIX, Digital Unix/Tru64, HPUX 10, more? */ + + /* For AIX 4.3 or later, we don't use gethostbyname_r() at all, because of + * the plain fact that it does not return unique full buffers on each + * call, but instead several of the pointers in the hostent structs will + * point to the same actual data! This have the unfortunate down-side that + * our caching system breaks down horribly. Luckily for us though, AIX 4.3 + * and more recent versions have a "completely thread-safe"[*] libc where + * all the data is stored in thread-specific memory areas making calls to + * the plain old gethostbyname() work fine even for multi-threaded + * programs. + * + * This AIX 4.3 or later detection is all made in the configure script. + * + * Troels Walsted Hansen helped us work this out on March 3rd, 2003. + * + * [*] = much later we've found out that it isn't at all "completely + * thread-safe", but at least the gethostbyname() function is. + */ + + if(CURL_HOSTENT_SIZE >= + (sizeof(struct hostent)+sizeof(struct hostent_data))) { + + /* August 22nd, 2000: Albert Chin-A-Young brought an updated version + * that should work! September 20: Richard Prescott worked on the buffer + * size dilemma. + */ + + res = gethostbyname_r(hostname, + (struct hostent *)buf, + (struct hostent_data *)((char *)buf + + sizeof(struct hostent))); + h_errnop= errno; /* we don't deal with this, but set it anyway */ + } + else + res = -1; /* failure, too smallish buffer size */ + + if(!res) { /* success */ + + h = (struct hostent*)buf; /* result expected in h */ + + /* This is the worst kind of the different gethostbyname_r() interfaces. + * Since we don't know how big buffer this particular lookup required, + * we can't realloc down the huge alloc without doing closer analysis of + * the returned data. Thus, we always use CURL_HOSTENT_SIZE for every + * name lookup. Fixing this would require an extra malloc() and then + * calling Curl_addrinfo_copy() that subsequent realloc()s down the new + * memory area to the actually used amount. + */ + } + else +#endif /* HAVE_GETHOSTBYNAME_R_3 */ + { + infof(data, "gethostbyname_r(2) failed for %s\n", hostname); + h = NULL; /* set return code to NULL */ + free(buf); + } +#else /* HAVE_GETHOSTBYNAME_R */ + /* + * Here is code for platforms that don't have gethostbyname_r() or for + * which the gethostbyname() is the preferred() function. + */ + else { + h = gethostbyname(hostname); + if (!h) + infof(data, "gethostbyname(2) failed for %s\n", hostname); + else { + /* + * Copy the hostent struct right here, as the static one we got a + * pointer to might get removed when we don't want/expect that. Windows + * (other platforms?) also doesn't allow passing of the returned data + * between threads, which thus the copying here them allows the app to + * do. + */ + h = Curl_addrinfo_copy(h); + } +#endif /*HAVE_GETHOSTBYNAME_R */ + } + + return h; +} + +#endif /* CURLRES_SYNCH */ + +#endif /* CURLRES_IPV4 */ diff --git a/lib/hostip6.c b/lib/hostip6.c new file mode 100644 index 000000000..61b140b14 --- /dev/null +++ b/lib/hostip6.c @@ -0,0 +1,284 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 1998 - 2004, Daniel Stenberg, , 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 +#include + +#define _REENTRANT + +#if defined(WIN32) && !defined(__GNUC__) || defined(__MINGW32__) +#include +#else +#ifdef HAVE_SYS_TYPES_H +#include +#endif +#ifdef HAVE_SYS_SOCKET_H +#include +#endif +#ifdef HAVE_NETINET_IN_H +#include +#endif +#ifdef HAVE_NETDB_H +#include +#endif +#ifdef HAVE_ARPA_INET_H +#include +#endif +#ifdef HAVE_STDLIB_H +#include /* required for free() prototypes */ +#endif +#ifdef HAVE_UNISTD_H +#include /* for the close() proto */ +#endif +#ifdef VMS +#include +#include +#include +#endif +#endif + +#ifdef HAVE_SETJMP_H +#include +#endif + +#ifdef WIN32 +#include +#endif + +#if (defined(NETWARE) && defined(__NOVELL_LIBC__)) +#undef in_addr_t +#define in_addr_t unsigned long +#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 + +#if defined(HAVE_INET_NTOA_R) && !defined(HAVE_INET_NTOA_R_DECL) +#include "inet_ntoa_r.h" +#endif + +/* The last #include file should be: */ +#ifdef CURLDEBUG +#include "memdebug.h" +#endif + +/*********************************************************************** + * Only for ipv6-enabled builds + **********************************************************************/ +#ifdef CURLRES_IPV6 +/* + * This is a wrapper function for freeing name information in a protocol + * independent way. This takes care of using the appropriate underlaying + * function. + */ +void Curl_freeaddrinfo(Curl_addrinfo *p) +{ + freeaddrinfo(p); +} + +/* + * Curl_printable_address() returns a printable version of the ai->ai_addr + * address given in the 2nd argument. The first should be the ai->ai_family + * and 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(int af, void *addr, + char *buf, size_t bufsize) +{ + const struct in_addr *addr4 = + &((const struct sockaddr_in*)addr)->sin_addr; + const struct in6_addr *addr6 = + &((const struct sockaddr_in6*)addr)->sin6_addr; + return Curl_inet_ntop(af, af == AF_INET6 ? + (const void *)addr6 : + (const void *)addr4, buf, bufsize); +} + + +#ifdef CURLRES_ASYNCH +/* + * Curl_addrinfo_copy() is used by the asynch callback to copy a given + * address. But this is an ipv6 build and then we don't copy the address, we + * just return the same pointer! + */ +Curl_addrinfo *Curl_addrinfo_copy(Curl_addrinfo *source) +{ + return source; +} +#endif + +#ifdef CURLDEBUG +/* These are strictly for memory tracing and are using the same style as the + * family otherwise present in memdebug.c. I put these ones here since they + * require a bunch of structs I didn't wanna include in memdebug.c + */ +int curl_dogetaddrinfo(char *hostname, char *service, + struct addrinfo *hints, + struct addrinfo **result, + int line, const char *source) +{ + int res=(getaddrinfo)(hostname, service, hints, result); + if(0 == res) { + /* success */ + if(logfile) + fprintf(logfile, "ADDR %s:%d getaddrinfo() = %p\n", + source, line, (void *)*result); + } + else { + if(logfile) + fprintf(logfile, "ADDR %s:%d getaddrinfo() failed\n", + source, line); + } + return res; +} + +int curl_dogetnameinfo(const struct sockaddr *sa, socklen_t salen, + char *host, size_t hostlen, + char *serv, size_t servlen, int flags, + int line, const char *source) +{ + int res=(getnameinfo)(sa, salen, host, hostlen, serv, servlen, flags); + if(0 == res) { + /* success */ + if(logfile) + fprintf(logfile, "GETNAME %s:%d getnameinfo()\n", + source, line); + } + else { + if(logfile) + fprintf(logfile, "GETNAME %s:%d getnameinfo() failed = %d\n", + source, line, res); + } + return res; +} + +void curl_dofreeaddrinfo(struct addrinfo *freethis, + int line, const char *source) +{ + (freeaddrinfo)(freethis); + if(logfile) + fprintf(logfile, "ADDR %s:%d freeaddrinfo(%p)\n", + source, line, (void *)freethis); +} + +#endif + +/* + * Curl_ipvalid() checks what CURL_IPRESOLVE_* requirements that might've + * been set and returns TRUE if they are OK. + */ +bool Curl_ipvalid(struct SessionHandle *data) +{ + if(data->set.ip_version == CURL_IPRESOLVE_V6) { + /* see if we have an IPv6 stack */ + curl_socket_t s = socket(PF_INET6, SOCK_DGRAM, 0); + if (s < 0) + /* an ipv6 address was requested and we can't get/use one */ + return FALSE; + sclose(s); + } + return TRUE; +} + +#ifndef USE_THREADING_GETADDRINFO +/* + * Curl_getaddrinfo() when built ipv6-enabled (non-threading version). + * + * Returns name information about the given hostname and port number. If + * successful, the 'addrinfo' is returned and the forth argument will point to + * memory we need to free after use. That memory *MUST* be freed with + * Curl_freeaddrinfo(), nothing else. + */ +Curl_addrinfo *Curl_getaddrinfo(struct connectdata *conn, + char *hostname, + int port, + int *waitp) +{ + struct addrinfo hints, *res; + int error; + char sbuf[NI_MAXSERV]; + curl_socket_t s; + int pf; + struct SessionHandle *data = conn->data; + + *waitp=0; /* don't wait, we have the response now */ + + /* see if we have an IPv6 stack */ + s = socket(PF_INET6, SOCK_DGRAM, 0); + if (s < 0) { + /* Some non-IPv6 stacks have been found to make very slow name resolves + * when PF_UNSPEC is used, so thus we switch to a mere PF_INET lookup if + * the stack seems to be a non-ipv6 one. */ + + pf = PF_INET; + } + else { + /* This seems to be an IPv6-capable stack, use PF_UNSPEC for the widest + * possible checks. And close the socket again. + */ + sclose(s); + + /* + * Check if a more limited name resolve has been requested. + */ + switch(data->set.ip_version) { + case CURL_IPRESOLVE_V4: + pf = PF_INET; + break; + case CURL_IPRESOLVE_V6: + pf = PF_INET6; + break; + default: + pf = PF_UNSPEC; + break; + } + } + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = pf; + hints.ai_socktype = SOCK_STREAM; + hints.ai_flags = AI_CANONNAME; + snprintf(sbuf, sizeof(sbuf), "%d", port); + error = getaddrinfo(hostname, sbuf, &hints, &res); + if (error) { + infof(data, "getaddrinfo(3) failed for %s:%d\n", hostname, port); + return NULL; + } + + return res; +} +#endif /* USE_THREADING_GETADDRINFO */ +#endif /* ipv6 */ + diff --git a/lib/hostsyn.c b/lib/hostsyn.c new file mode 100644 index 000000000..7ffe222f2 --- /dev/null +++ b/lib/hostsyn.c @@ -0,0 +1,150 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 1998 - 2004, Daniel Stenberg, , 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 +#include + +#define _REENTRANT + +#if defined(WIN32) && !defined(__GNUC__) || defined(__MINGW32__) +#include +#else +#ifdef HAVE_SYS_TYPES_H +#include +#endif +#ifdef HAVE_SYS_SOCKET_H +#include +#endif +#ifdef HAVE_NETINET_IN_H +#include +#endif +#ifdef HAVE_NETDB_H +#include +#endif +#ifdef HAVE_ARPA_INET_H +#include +#endif +#ifdef HAVE_STDLIB_H +#include /* required for free() prototypes */ +#endif +#ifdef HAVE_UNISTD_H +#include /* for the close() proto */ +#endif +#ifdef VMS +#include +#include +#include +#endif +#endif + +#ifdef HAVE_SETJMP_H +#include +#endif + +#ifdef WIN32 +#include +#endif + +#if (defined(NETWARE) && defined(__NOVELL_LIBC__)) +#undef in_addr_t +#define in_addr_t unsigned long +#endif + +#include "urldata.h" +#include "sendf.h" +#include "hostip.h" +#include "hash.h" +#include "share.h" +#include "strerror.h" +#include "url.h" + +#define _MPRINTF_REPLACE /* use our functions only */ +#include + +#if defined(HAVE_INET_NTOA_R) && !defined(HAVE_INET_NTOA_R_DECL) +#include "inet_ntoa_r.h" +#endif + +/* The last #include file should be: */ +#ifdef CURLDEBUG +#include "memdebug.h" +#endif + +/*********************************************************************** + * Only for builds using synchronous name resolves + **********************************************************************/ +#ifdef CURLRES_SYNCH + +/* + * Curl_wait_for_resolv() for synch-builds. Curl_resolv() can never return + * wait==TRUE, so this function will never be called. If it still gets called, + * we return failure at once. + * + * We provide this function only to allow multi.c to remain unaware if we are + * doing asynch resolves or not. + */ +CURLcode Curl_wait_for_resolv(struct connectdata *conn, + struct Curl_dns_entry **entry) +{ + (void)conn; + *entry=NULL; + return CURLE_COULDNT_RESOLVE_HOST; +} + +/* + * This function will never be called when synch-built. If it still gets + * called, we return failure at once. + * + * We provide this function only to allow multi.c to remain unaware if we are + * doing asynch resolves or not. + */ +CURLcode Curl_is_resolved(struct connectdata *conn, + struct Curl_dns_entry **dns) +{ + (void)conn; + *dns = NULL; + + return CURLE_COULDNT_RESOLVE_HOST; +} + +/* + * We just return OK, this function is never actually used for synch builds. + * It is present here to keep #ifdefs out from multi.c + */ + +CURLcode Curl_fdset(struct connectdata *conn, + fd_set *read_fd_set, + fd_set *write_fd_set, + int *max_fdp) +{ + (void)conn; + (void)read_fd_set; + (void)write_fd_set; + (void)max_fdp; + + return CURLE_OK; +} + +#endif /* truly sync */ diff --git a/lib/hostthre.c b/lib/hostthre.c new file mode 100644 index 000000000..48f4786f9 --- /dev/null +++ b/lib/hostthre.c @@ -0,0 +1,556 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 1998 - 2004, Daniel Stenberg, , 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 +#include + +#define _REENTRANT + +#if defined(WIN32) && !defined(__GNUC__) || defined(__MINGW32__) +#include +#else +#ifdef HAVE_SYS_TYPES_H +#include +#endif +#ifdef HAVE_SYS_SOCKET_H +#include +#endif +#ifdef HAVE_NETINET_IN_H +#include +#endif +#ifdef HAVE_NETDB_H +#include +#endif +#ifdef HAVE_ARPA_INET_H +#include +#endif +#ifdef HAVE_STDLIB_H +#include /* required for free() prototypes */ +#endif +#ifdef HAVE_UNISTD_H +#include /* for the close() proto */ +#endif +#ifdef VMS +#include +#include +#include +#endif +#endif + +#ifdef HAVE_SETJMP_H +#include +#endif + +#ifdef WIN32 +#include +#endif + +#if (defined(NETWARE) && defined(__NOVELL_LIBC__)) +#undef in_addr_t +#define in_addr_t unsigned long +#endif + +#include "urldata.h" +#include "sendf.h" +#include "hostip.h" +#include "hash.h" +#include "share.h" +#include "strerror.h" +#include "url.h" + +#define _MPRINTF_REPLACE /* use our functions only */ +#include + +#if defined(HAVE_INET_NTOA_R) && !defined(HAVE_INET_NTOA_R_DECL) +#include "inet_ntoa_r.h" +#endif + +/* The last #include file should be: */ +#ifdef CURLDEBUG +#include "memdebug.h" +#endif + +/*********************************************************************** + * Only for Windows threaded name resolves builds + **********************************************************************/ +#ifdef CURLRES_THREADED + +/* This function is used to init a threaded resolve */ +static bool init_resolve_thread(struct connectdata *conn, + const char *hostname, int port, + const Curl_addrinfo *hints); + +#ifdef CURLRES_IPV4 + #define THREAD_FUNC gethostbyname_thread + #define THREAD_NAME "gethostbyname_thread" +#else + #define THREAD_FUNC getaddrinfo_thread + #define THREAD_NAME "getaddrinfo_thread" +#endif + +#if defined(DEBUG_THREADING_GETHOSTBYNAME) || \ + defined(DEBUG_THREADING_GETADDRINFO) +/* If this is defined, provide tracing */ +#define TRACE(args) \ + do { trace_it("%u: ", __LINE__); trace_it args; } while (0) + +static void trace_it (const char *fmt, ...) +{ + static int do_trace = -1; + va_list args; + + if (do_trace == -1) { + const char *env = getenv("CURL_TRACE"); + do_trace = (env && atoi(env) > 0); + } + if (!do_trace) + return; + va_start (args, fmt); + vfprintf (stderr, fmt, args); + fflush (stderr); + va_end (args); +} +#else +#define TRACE(x) +#endif + +#ifdef DEBUG_THREADING_GETADDRINFO + +/* inet_ntop.c */ +extern const char *Curl_inet_ntop (int af, const void *addr, char *buf, size_t size); + +static void dump_addrinfo (struct connectdata *conn, const struct addrinfo *ai) +{ + TRACE(("dump_addrinfo:\n")); + for ( ; ai; ai = ai->ai_next) { + char buf [INET6_ADDRSTRLEN]; + trace_it(" fam %2d, CNAME %s, ", + af, ai->ai_canonname ? ai->ai_canonname : ""); + if (Curl_printable_address(ai->ai_family, ai->ai_addr, buf, sizeof(buf))) + trace_it("%s\n", buf); + else + trace_it("failed; %s\n", Curl_strerror(conn,WSAGetLastError())); + } +} +#endif + +struct thread_data { + HANDLE thread_hnd; + unsigned thread_id; + DWORD thread_status; + curl_socket_t dummy_sock; /* dummy for Curl_fdset() */ + FILE *stderr_file; +#ifdef CURLRES_IPV6 + struct addrinfo hints; +#endif +}; + +#if defined(CURLRES_IPV4) +/* + * gethostbyname_thread() resolves a name, calls the Curl_addrinfo_callback + * and then exits. + * + * For builds without ARES/ENABLE_IPV6, create a resolver thread and wait on + * it. + */ +static unsigned __stdcall gethostbyname_thread (void *arg) +{ + struct connectdata *conn = (struct connectdata*) arg; + struct thread_data *td = (struct thread_data*) conn->async.os_specific; + struct hostent *he; + int rc; + + /* Sharing the same _iob[] element with our parent thread should + * hopefully make printouts synchronised. I'm not sure it works + * with a static runtime lib (MSVC's libc.lib). + */ + *stderr = *td->stderr_file; + + WSASetLastError (conn->async.status = NO_DATA); /* pending status */ + he = gethostbyname (conn->async.hostname); + if (he) { + Curl_addrinfo_callback(conn, CURL_ASYNC_SUCCESS, he); + rc = 1; + } + else { + Curl_addrinfo_callback(conn, (int)WSAGetLastError(), NULL); + rc = 0; + } + TRACE(("Winsock-error %d, addr %s\n", conn->async.status, + he ? inet_ntoa(*(struct in_addr*)he->h_addr) : "unknown")); + return (rc); + /* An implicit _endthreadex() here */ +} + +#elif defined(CURLRES_IPV6) + +/* + * getaddrinfo_thread() resolves a name, calls Curl_addrinfo_callback and then + * exits. + * + * For builds without ARES, but with ENABLE_IPV6, create a resolver thread + * and wait on it. + */ +static unsigned __stdcall getaddrinfo_thread (void *arg) +{ + struct connectdata *conn = (struct connectdata*) arg; + struct thread_data *td = (struct thread_data*) conn->async.os_specific; + struct addrinfo *res; + char service [NI_MAXSERV]; + int rc; + + *stderr = *td->stderr_file; + + itoa(conn->async.port, service, 10); + + WSASetLastError(conn->async.status = NO_DATA); /* pending status */ + + rc = getaddrinfo(conn->async.hostname, service, &td->hints, &res); + + if (rc == 0) { +#ifdef DEBUG_THREADING_GETADDRINFO + dump_addrinfo (conn, res); +#endif + Curl_addrinfo_callback(conn, CURL_ASYNC_SUCCESS, res); + } + else { + Curl_addrinfo_callback(conn, (int)WSAGetLastError(), NULL); + TRACE(("Winsock-error %d, no address\n", conn->async.status)); + } + return (rc); + /* An implicit _endthreadex() here */ +} +#endif + +/* + * destroy_thread_data() cleans up async resolver data. + * Complementary of ares_destroy. + */ +static void destroy_thread_data (struct Curl_async *async) +{ + if (async->hostname) + free(async->hostname); + + if (async->os_specific) { + curl_socket_t sock = ((const struct thread_data*)async->os_specific)->dummy_sock; + + if (sock != CURL_SOCKET_BAD) + sclose(sock); + free(async->os_specific); + } + async->hostname = NULL; + async->os_specific = NULL; +} + +/* + * init_resolve_thread() starts a new thread that performs the actual + * resolve. This function returns before the resolve is done. + * + * Returns FALSE in case of failure, otherwise TRUE. + */ +static bool init_resolve_thread (struct connectdata *conn, + const char *hostname, int port, + const Curl_addrinfo *hints) +{ + struct thread_data *td = calloc(sizeof(*td), 1); + + if (!td) { + SetLastError(ENOMEM); + return FALSE; + } + + Curl_safefree(conn->async.hostname); + conn->async.hostname = strdup(hostname); + if (!conn->async.hostname) { + free(td); + SetLastError(ENOMEM); + return FALSE; + } + + conn->async.port = port; + conn->async.done = FALSE; + conn->async.status = 0; + conn->async.dns = NULL; + conn->async.os_specific = (void*) td; + + td->dummy_sock = CURL_SOCKET_BAD; + td->stderr_file = stderr; + td->thread_hnd = (HANDLE) _beginthreadex(NULL, 0, THREAD_FUNC, + conn, 0, &td->thread_id); +#ifdef CURLRES_IPV6 + curlassert(hints); + td->hints = *hints; +#else + (void) hints; +#endif + + if (!td->thread_hnd) { + SetLastError(errno); + TRACE(("_beginthreadex() failed; %s\n", Curl_strerror(conn,errno))); + destroy_thread_data(&conn->async); + return FALSE; + } + /* This socket is only to keep Curl_fdset() and select() happy; should never + * become signalled for read/write since it's unbound but Windows needs + * atleast 1 socket in select(). + */ + td->dummy_sock = socket(AF_INET, SOCK_DGRAM, 0); + return TRUE; +} + + +/* + * Curl_wait_for_resolv() waits for a resolve to finish. This function should + * be avoided since using this risk getting the multi interface to "hang". + * + * If 'entry' is non-NULL, make it point to the resolved dns entry + * + * This is the version for resolves-in-a-thread. + */ +CURLcode Curl_wait_for_resolv(struct connectdata *conn, + struct Curl_dns_entry **entry) +{ + struct thread_data *td = (struct thread_data*) conn->async.os_specific; + struct SessionHandle *data = conn->data; + long timeout; + DWORD status, ticks; + CURLcode rc; + + curlassert (conn && td); + + /* now, see if there's a connect timeout or a regular timeout to + use instead of the default one */ + timeout = + conn->data->set.connecttimeout ? conn->data->set.connecttimeout : + conn->data->set.timeout ? conn->data->set.timeout : + CURL_TIMEOUT_RESOLVE; /* default name resolve timeout */ + ticks = GetTickCount(); + + status = WaitForSingleObject(td->thread_hnd, 1000UL*timeout); + if (status == WAIT_OBJECT_0 || status == WAIT_ABANDONED) { + /* Thread finished before timeout; propagate Winsock error to this thread. + * 'conn->async.done = TRUE' is set in Curl_addrinfo_callback(). + */ + WSASetLastError(conn->async.status); + GetExitCodeThread(td->thread_hnd, &td->thread_status); + TRACE(("%s() status %lu, thread retval %lu, ", + THREAD_NAME, status, td->thread_status)); + } + else { + conn->async.done = TRUE; + td->thread_status = (DWORD)-1; + TRACE(("%s() timeout, ", THREAD_NAME)); + } + + TRACE(("elapsed %lu ms\n", GetTickCount()-ticks)); + + CloseHandle(td->thread_hnd); + + if(entry) + *entry = conn->async.dns; + + rc = CURLE_OK; + + if (!conn->async.dns) { + /* a name was not resolved */ + if (td->thread_status == (DWORD)-1 || conn->async.status == NO_DATA) { + failf(data, "Resolving host timed out: %s", conn->hostname); + rc = CURLE_OPERATION_TIMEDOUT; + } + else if(conn->async.done) { + failf(data, "Could not resolve host: %s; %s", + conn->hostname, Curl_strerror(conn,conn->async.status)); + rc = CURLE_COULDNT_RESOLVE_HOST; + } + else + rc = CURLE_OPERATION_TIMEDOUT; + } + + destroy_thread_data(&conn->async); + + if(CURLE_OK != rc) + /* close the connection, since we must not return failure from here + without cleaning up this connection properly */ + Curl_disconnect(conn); + + return (rc); +} + +/* + * Curl_is_resolved() is called repeatedly to check if a previous name resolve + * request has completed. It should also make sure to time-out if the + * operation seems to take too long. + */ +CURLcode Curl_is_resolved(struct connectdata *conn, + struct Curl_dns_entry **entry) +{ + *entry = NULL; + + if (conn->async.done) { + /* we're done */ + destroy_thread_data(&conn->async); + if (!conn->async.dns) { + TRACE(("Curl_is_resolved(): CURLE_COULDNT_RESOLVE_HOST\n")); + return CURLE_COULDNT_RESOLVE_HOST; + } + *entry = conn->async.dns; + TRACE(("resolved okay, dns %p\n", *entry)); + } + else + TRACE(("not yet\n")); + return CURLE_OK; +} + +CURLcode Curl_fdset(struct connectdata *conn, + fd_set *read_fd_set, + fd_set *write_fd_set, + int *max_fdp) +{ + const struct thread_data *td = + (const struct thread_data *) conn->async.os_specific; + + if (td && td->dummy_sock != CURL_SOCKET_BAD) { + FD_SET(td->dummy_sock,write_fd_set); + *max_fdp = td->dummy_sock; + } + (void) read_fd_set; + return CURLE_OK; +} + +#ifdef CURLRES_IPV4 +/* + * Curl_getaddrinfo() - for Windows threading without ENABLE_IPV6. + */ +Curl_addrinfo *Curl_getaddrinfo(struct connectdata *conn, + char *hostname, + int port, + int *waitp) +{ + struct hostent *h = NULL; + struct SessionHandle *data = conn->data; + in_addr_t in; + + *waitp = 0; /* don't wait, we act synchronously */ + + in = inet_addr(hostname); + if (in != CURL_INADDR_NONE) + /* This is a dotted IP address 123.123.123.123-style */ + return Curl_ip2addr(in, hostname); + + /* fire up a new resolver thread! */ + if (init_resolve_thread(conn, hostname, port, NULL)) { + *waitp = TRUE; /* please wait for the response */ + return NULL; + } + + /* fall-back to blocking version */ + infof(data, "init_resolve_thread() failed for %s; code %lu\n", + hostname, GetLastError()); + + h = gethostbyname(hostname); + if (!h) { + infof(data, "gethostbyname(2) failed for %s:%d; %s\n", + hostname, port, Curl_strerror(conn,WSAGetLastError())); + return NULL; + } + return h; +} +#endif /* CURLRES_IPV4 */ + +#ifdef CURLRES_IPV6 +/* + * Curl_getaddrinfo() - for Windows threading IPv6 enabled + */ +Curl_addrinfo *Curl_getaddrinfo(struct connectdata *conn, + char *hostname, + int port, + int *waitp) +{ + struct addrinfo hints, *res; + int error; + char sbuf[NI_MAXSERV]; + curl_socket_t s; + int pf; + struct SessionHandle *data = conn->data; + + *waitp = FALSE; /* default to synch response */ + + /* see if we have an IPv6 stack */ + s = socket(PF_INET6, SOCK_DGRAM, 0); + if (s < 0) { + /* Some non-IPv6 stacks have been found to make very slow name resolves + * when PF_UNSPEC is used, so thus we switch to a mere PF_INET lookup if + * the stack seems to be a non-ipv6 one. */ + + pf = PF_INET; + } + else { + /* This seems to be an IPv6-capable stack, use PF_UNSPEC for the widest + * possible checks. And close the socket again. + */ + sclose(s); + + /* + * Check if a more limited name resolve has been requested. + */ + switch(data->set.ip_version) { + case CURL_IPRESOLVE_V4: + pf = PF_INET; + break; + case CURL_IPRESOLVE_V6: + pf = PF_INET6; + break; + default: + pf = PF_UNSPEC; + break; + } + } + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = pf; + hints.ai_socktype = SOCK_STREAM; + hints.ai_flags = AI_CANONNAME; + itoa(port, sbuf, 10); + + /* fire up a new resolver thread! */ + if (init_resolve_thread(conn, hostname, port, &hints)) { + *waitp = TRUE; /* please wait for the response */ + return NULL; + } + + /* fall-back to blocking version */ + infof(data, "init_resolve_thread() failed for %s; code %lu\n", + hostname, GetLastError()); + + error = getaddrinfo(hostname, sbuf, &hints, &res); + if (error) { + infof(data, "getaddrinfo() failed for %s:%d; %s\n", + hostname, port, Curl_strerror(conn,WSAGetLastError())); + return NULL; + } + return res; +} +#endif /* CURLRES_IPV6 */ +#endif /* CURLRES_THREADED */ diff --git a/lib/inet_ntop.c b/lib/inet_ntop.c new file mode 100644 index 000000000..22c053e17 --- /dev/null +++ b/lib/inet_ntop.c @@ -0,0 +1,189 @@ +/* + * Original code by Paul Vixie. "curlified" by Gisle Vanem. + */ + +#include "setup.h" + +#ifndef HAVE_INET_NTOP + +#ifdef HAVE_SYS_PARAM_H +#include +#endif +#ifdef HAVE_SYS_TYPES_H +#include +#endif +#ifdef HAVE_SYS_SOCKET_H +#include +#endif +#ifdef HAVE_NETINET_IN_H +#include +#endif +#ifdef HAVE_ARPA_INET_H +#include +#endif +#include +#include + +#include "inet_ntop.h" + +#define IN6ADDRSZ 16 +#define INADDRSZ 4 +#define INT16SZ 2 + +#ifdef WIN32 +#define EAFNOSUPPORT WSAEAFNOSUPPORT +#define SET_ERRNO(e) WSASetLastError(errno = (e)) +#else +#define SET_ERRNO(e) errno = e +#endif + +/* + * Format an IPv4 address, more or less like inet_ntoa(). + * + * Returns `dst' (as a const) + * Note: + * - uses no statics + * - takes a u_char* not an in_addr as input + */ +static const char *inet_ntop4 (const u_char *src, char *dst, size_t size) +{ +#ifdef HAVE_INET_NTOA_R + return inet_ntoa_r(*(struct in_addr*)src, dst, size); +#else + const char *addr = inet_ntoa(*(struct in_addr*)src); + + if (strlen(addr) >= size) + { + SET_ERRNO(ENOSPC); + return (NULL); + } + return strcpy(dst, addr); +#endif +} + +#ifdef ENABLE_IPV6 +/* + * Convert IPv6 binary address into presentation (printable) format. + */ +static const char *inet_ntop6 (const u_char *src, char *dst, size_t size) +{ + /* + * Note that int32_t and int16_t need only be "at least" large enough + * to contain a value of the specified size. On some systems, like + * Crays, there is no such thing as an integer variable with 16 bits. + * Keep this in mind if you think this function should have been coded + * to use pointer overlays. All the world's not a VAX. + */ + char tmp [sizeof("ffff:ffff:ffff:ffff:ffff:ffff:255.255.255.255")]; + char *tp; + struct { + long base; + long len; + } best, cur; + u_long words [IN6ADDRSZ / INT16SZ]; + int i; + + /* Preprocess: + * Copy the input (bytewise) array into a wordwise array. + * Find the longest run of 0x00's in src[] for :: shorthanding. + */ + memset(words, 0, sizeof(words)); + for (i = 0; i < IN6ADDRSZ; i++) + words[i/2] |= (src[i] << ((1 - (i % 2)) << 3)); + + best.base = -1; + cur.base = -1; + for (i = 0; i < (IN6ADDRSZ / INT16SZ); i++) + { + if (words[i] == 0) + { + if (cur.base == -1) + cur.base = i, cur.len = 1; + else + cur.len++; + } + else if (cur.base != -1) + { + if (best.base == -1 || cur.len > best.len) + best = cur; + cur.base = -1; + } + } + if ((cur.base != -1) && (best.base == -1 || cur.len > best.len)) + best = cur; + if (best.base != -1 && best.len < 2) + best.base = -1; + + /* Format the result. + */ + tp = tmp; + for (i = 0; i < (IN6ADDRSZ / INT16SZ); i++) + { + /* Are we inside the best run of 0x00's? + */ + if (best.base != -1 && i >= best.base && i < (best.base + best.len)) + { + if (i == best.base) + *tp++ = ':'; + continue; + } + + /* Are we following an initial run of 0x00s or any real hex? + */ + if (i != 0) + *tp++ = ':'; + + /* Is this address an encapsulated IPv4? + */ + if (i == 6 && best.base == 0 && + (best.len == 6 || (best.len == 5 && words[5] == 0xffff))) + { + if (!inet_ntop4(src+12, tp, sizeof(tmp) - (tp - tmp))) + { + SET_ERRNO(ENOSPC); + return (NULL); + } + tp += strlen(tp); + break; + } + tp += sprintf (tp, "%lx", words[i]); + } + + /* Was it a trailing run of 0x00's? + */ + if (best.base != -1 && (best.base + best.len) == (IN6ADDRSZ / INT16SZ)) + *tp++ = ':'; + *tp++ = '\0'; + + /* Check for overflow, copy, and we're done. + */ + if ((size_t)(tp - tmp) > size) + { + SET_ERRNO(ENOSPC); + return (NULL); + } + return strcpy (dst, tmp); +} +#endif /* ENABLE_IPV6 */ + +/* + * Convert a network format address to presentation format. + * + * Returns pointer to presentation format address (`dst'), + * Returns NULL on error (see errno). + */ +const char *Curl_inet_ntop(int af, const void *src, char *buf, size_t size) +{ + switch (af) { + case AF_INET: + return inet_ntop4((const u_char*)src, buf, size); +#ifdef ENABLE_IPV6 + case AF_INET6: + return inet_ntop6((const u_char*)src, buf, size); +#endif + default: + SET_ERRNO(EAFNOSUPPORT); + return NULL; + } +} +#endif /* HAVE_INET_NTOP */ diff --git a/lib/inet_ntop.h b/lib/inet_ntop.h new file mode 100644 index 000000000..5948a120b --- /dev/null +++ b/lib/inet_ntop.h @@ -0,0 +1,37 @@ +#ifndef __INET_NTOP_H +#define __INET_NTOP_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 1998 - 2004, Daniel Stenberg, , 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" + +#ifdef HAVE_INET_NTOP +#define Curl_inet_ntop(af,addr,buf,size) inet_ntop(af,addr,buf,size) +#ifdef HAVE_ARPA_INET_H +#include +#endif +#else +const char *Curl_inet_ntop(int af, const void *addr, char *buf, size_t size); +#endif + +#endif /* __INET_NTOP_H */ diff --git a/lib/memdebug.h b/lib/memdebug.h index 40ba08033..42574cf43 100644 --- a/lib/memdebug.h +++ b/lib/memdebug.h @@ -84,9 +84,12 @@ int curl_fclose(FILE *file, int line, const char *source); curl_accept(sock,addr,len,__LINE__,__FILE__) #define getaddrinfo(host,serv,hint,res) \ - curl_getaddrinfo(host,serv,hint,res,__LINE__,__FILE__) + curl_dogetaddrinfo(host,serv,hint,res,__LINE__,__FILE__) +#define getnameinfo(sa,salen,host,hostlen,serv,servlen,flags) \ + curl_dogetnameinfo(sa,salen,host,hostlen,serv,servlen,flags, __LINE__, \ + __FILE__) #define freeaddrinfo(data) \ - curl_freeaddrinfo(data,__LINE__,__FILE__) + curl_dofreeaddrinfo(data,__LINE__,__FILE__) /* sclose is probably already defined, redefine it! */ #undef sclose diff --git a/lib/multi.c b/lib/multi.c index ddc4b16cb..7d86d202e 100644 --- a/lib/multi.c +++ b/lib/multi.c @@ -251,8 +251,7 @@ CURLMcode curl_multi_fdset(CURLM *multi_handle, break; case CURLM_STATE_WAITRESOLVE: /* waiting for a resolve to complete */ - Curl_multi_ares_fdset(easy->easy_conn, read_fd_set, write_fd_set, - &this_max_fd); + Curl_fdset(easy->easy_conn, read_fd_set, write_fd_set, &this_max_fd); if(this_max_fd > *max_fd) *max_fd = this_max_fd; break; @@ -413,7 +412,7 @@ CURLMcode curl_multi_perform(CURLM *multi_handle, int *running_handles) easy->easy_conn->sock[FIRSTSOCKET], &connected); if(connected) - easy->result = Curl_protocol_connect(easy->easy_conn, NULL); + easy->result = Curl_protocol_connect(easy->easy_conn); if(CURLE_OK != easy->result) { /* failure detected */ diff --git a/lib/setup.h b/lib/setup.h index 35370f673..0bbdbd5b3 100644 --- a/lib/setup.h +++ b/lib/setup.h @@ -261,9 +261,13 @@ typedef int curl_socket_t; #error "ares does not yet support IPv6. Disable IPv6 or ares and rebuild" #endif -#if defined(WIN32) && !defined(__CYGWIN32__) && !defined(USE_ARES) && !defined(ENABLE_IPV6) +#if defined(WIN32) && !defined(__CYGWIN__) && !defined(USE_ARES) +#ifdef ENABLE_IPV6 +#define USE_THREADING_GETADDRINFO +#else #define USE_THREADING_GETHOSTBYNAME /* Cygwin uses alarm() function */ #endif +#endif /* * Curl_addrinfo MUST be used for name resolving information. @@ -296,4 +300,10 @@ typedef struct in_addr Curl_ipconnect; #undef HAVE_ALARM #endif +#ifdef HAVE_LIBIDN +/* This could benefit from additional checks that some of the used/important + header files are present as well before we define the USE_* define. */ +#define USE_LIBIDN +#endif + #endif /* __CONFIG_H */ diff --git a/lib/url.c b/lib/url.c index 87d1685f2..7bdea96ef 100644 --- a/lib/url.c +++ b/lib/url.c @@ -84,6 +84,11 @@ #endif +#ifdef USE_LIBIDN +#include +#include +#endif + #ifdef HAVE_OPENSSL_ENGINE_H #include #endif @@ -116,6 +121,7 @@ #include "ldap.h" #include "url.h" #include "connect.h" +#include "inet_ntop.h" #include #include @@ -145,6 +151,11 @@ static unsigned int ConnectionStore(struct SessionHandle *data, struct connectdata *conn); static bool safe_strequal(char* str1, char* str2); +#ifdef USE_LIBIDN +static bool is_ASCII_name (const char *hostname); +static bool is_ACE_name (const char *hostname); +#endif + #ifndef USE_ARES /* not for Win32, unless it is cygwin not for ares builds */ @@ -1384,7 +1395,8 @@ CURLcode Curl_disconnect(struct connectdata *conn) Curl_safefree(conn->allocptr.host); Curl_safefree(conn->allocptr.cookiehost); Curl_safefree(conn->proxyhost); -#if defined(USE_ARES) || defined(USE_THREADING_GETHOSTBYNAME) +#if defined(USE_ARES) || defined(USE_THREADING_GETHOSTBYNAME) || \ + defined(USE_THREADING_GETADDRINFO) /* possible left-overs from the async name resolve */ Curl_safefree(conn->async.hostname); Curl_safefree(conn->async.os_specific); @@ -1875,64 +1887,26 @@ static CURLcode ConnectPlease(struct connectdata *conn, } /* - * ALERT! The 'dns' pointer being passed in here might be NULL at times. + * verboseconnect() displays verbose information after a connect */ -static void verboseconnect(struct connectdata *conn, - struct Curl_dns_entry *dns) +static void verboseconnect(struct connectdata *conn) { struct SessionHandle *data = conn->data; + const char *host=NULL; + char addrbuf[NI_MAXHOST]; - /* Figure out the ip-number and display the first host name it shows: */ + /* Get a printable version of the network address. */ #ifdef ENABLE_IPV6 - { - char hbuf[NI_MAXHOST]; -#ifdef HAVE_NI_WITHSCOPEID -#define NIFLAGS NI_NUMERICHOST | NI_WITHSCOPEID + struct addrinfo *ai = conn->serv_addr; + host = Curl_printable_address(ai->ai_family, ai->ai_addr, + addrbuf, sizeof(addrbuf)); #else -#define NIFLAGS NI_NUMERICHOST -#endif - if(dns) { - struct addrinfo *ai = dns->addr; - - /* Lookup the name of the given address. This should probably be remade - to use the DNS cache instead, as the host name is most likely cached - already. */ - if (getnameinfo(ai->ai_addr, ai->ai_addrlen, hbuf, sizeof(hbuf), NULL, 0, - NIFLAGS)) { - snprintf(hbuf, sizeof(hbuf), "unknown"); - } - else { - if (ai->ai_canonname) { - infof(data, "Connected to %s (%s) port %d\n", ai->ai_canonname, hbuf, - conn->port); - return; - } - } - } - else { - snprintf(hbuf, sizeof(hbuf), "same host"); - } - - infof(data, "Connected to %s port %d\n", hbuf, conn->port); - } -#else - { -#ifdef HAVE_INET_NTOA_R - char ntoa_buf[64]; -#endif - Curl_addrinfo *hostaddr=dns?dns->addr:NULL; - struct in_addr in; - (void) memcpy(&in.s_addr, &conn->serv_addr.sin_addr, sizeof (in.s_addr)); - infof(data, "Connected to %s (%s) port %d\n", - hostaddr?hostaddr->h_name:"", -#if defined(HAVE_INET_NTOA_R) - inet_ntoa_r(in, ntoa_buf, sizeof(ntoa_buf)), -#else - inet_ntoa(in), -#endif - conn->port); - } + struct in_addr in; + (void) memcpy(&in.s_addr, &conn->serv_addr.sin_addr, sizeof (in.s_addr)); + host = Curl_inet_ntop(AF_INET, &in, addrbuf, sizeof(addrbuf)); #endif + infof(data, "Connected to %s (%s) port %d\n", + conn->hostname, host?host:"", conn->port); } /* @@ -1942,11 +1916,9 @@ static void verboseconnect(struct connectdata *conn, * If we're using the multi interface, this host address pointer is most * likely NULL at this point as we can't keep the resolved info around. This * may call for some reworking, like a reference counter in the struct or - * something. The hostaddr is not used for very much though, we have the - * 'serv_addr' field in the connectdata struct for most of it. + * something. */ -CURLcode Curl_protocol_connect(struct connectdata *conn, - struct Curl_dns_entry *hostaddr) +CURLcode Curl_protocol_connect(struct connectdata *conn) { struct SessionHandle *data = conn->data; CURLcode result=CURLE_OK; @@ -1960,7 +1932,7 @@ CURLcode Curl_protocol_connect(struct connectdata *conn, Curl_pgrsTime(data, TIMER_CONNECT); /* connect done */ if(data->set.verbose) - verboseconnect(conn, hostaddr); + verboseconnect(conn); if(conn->curl_connect) { /* is there a protocol-specific connect() procedure? */ @@ -3173,6 +3145,26 @@ static CURLcode SetupConnection(struct connectdata *conn, a file:// transfer */ return result; +#ifdef USE_LIBIDN + /************************************************************* + * Check name for non-ASCII and convert hostname to ACE form. + *************************************************************/ + + if(!conn->bits.reuse && conn->remote_port) { + const char *host = conn->hostname; + char *ace_hostname; + + if (!is_ASCII_name(host) && !is_ACE_name(host)) { + int rc = idna_to_ascii_lz (host, &ace_hostname, 0); + + if (rc == IDNA_SUCCESS) + conn->ace_hostname = ace_hostname; + else + infof(data, "Failed to convert %s to ACE; IDNA error %d\n", host, rc); + } + } +#endif + /************************************************************* * Send user-agent to HTTP proxies even if the target protocol * isn't HTTP. @@ -3202,7 +3194,7 @@ static CURLcode SetupConnection(struct connectdata *conn, result = ConnectPlease(conn, hostaddr, &connected); if(connected) { - result = Curl_protocol_connect(conn, hostaddr); + result = Curl_protocol_connect(conn); if(CURLE_OK == result) conn->bits.tcpconnect = TRUE; } @@ -3217,7 +3209,7 @@ static CURLcode SetupConnection(struct connectdata *conn, Curl_pgrsTime(data, TIMER_CONNECT); /* we're connected already */ conn->bits.tcpconnect = TRUE; if(data->set.verbose) - verboseconnect(conn, hostaddr); + verboseconnect(conn); } conn->now = Curl_tvnow(); /* time this *after* the connect is done, we @@ -3277,7 +3269,8 @@ CURLcode Curl_connect(struct SessionHandle *data, then a successful name resolve has been received */ CURLcode Curl_async_resolved(struct connectdata *conn) { -#if defined(USE_ARES) || defined(USE_THREADING_GETHOSTBYNAME) +#if defined(USE_ARES) || defined(USE_THREADING_GETHOSTBYNAME) || \ + defined(USE_THREADING_GETADDRINFO) CURLcode code = SetupConnection(conn, conn->async.dns); if(code) @@ -3504,3 +3497,20 @@ void Curl_free_ssl_config(struct ssl_config_data* sslc) if(sslc->random_file) free(sslc->random_file); } + +/* + * Helpers for IDNA convertions. To do. + */ +#ifdef USE_LIBIDN +static bool is_ASCII_name (const char *hostname) +{ + (void) hostname; + return (TRUE); +} + +static bool is_ACE_name (const char *hostname) +{ + (void) hostname; + return (FALSE); +} +#endif diff --git a/lib/url.h b/lib/url.h index 1d3057ccd..170c107db 100644 --- a/lib/url.h +++ b/lib/url.h @@ -37,8 +37,7 @@ CURLcode Curl_do(struct connectdata **); CURLcode Curl_do_more(struct connectdata *); CURLcode Curl_done(struct connectdata *); CURLcode Curl_disconnect(struct connectdata *); -CURLcode Curl_protocol_connect(struct connectdata *conn, - struct Curl_dns_entry *dns); +CURLcode Curl_protocol_connect(struct connectdata *conn); bool Curl_ssl_config_matches(struct ssl_config_data* data, struct ssl_config_data* needle); bool Curl_clone_ssl_config(struct ssl_config_data* source, diff --git a/lib/urldata.h b/lib/urldata.h index 7642cb9a1..445788f8f 100644 --- a/lib/urldata.h +++ b/lib/urldata.h @@ -81,6 +81,10 @@ #include /* for content-encoding */ #endif +#ifdef USE_ARES +#include +#endif + #include #include "http_chunks.h" /* for the structs and enum stuff */ @@ -96,10 +100,6 @@ #endif #endif -#ifdef USE_ARES -#include -#endif - /* Download buffer size, keep it fairly big for speed reasons */ #define BUFSIZE CURL_MAX_WRITE_SIZE @@ -381,7 +381,8 @@ struct Curl_transfer_keeper { bool ignorebody; /* we read a response-body but we ignore it! */ }; -#if defined(USE_ARES) || defined(USE_THREADING_GETHOSTBYNAME) +#if defined(USE_ARES) || defined(USE_THREADING_GETHOSTBYNAME) || \ + defined(USE_THREADING_GETADDRINFO) struct Curl_async { char *hostname; int port; @@ -430,6 +431,9 @@ struct connectdata { char *namebuffer; /* allocated buffer to store the hostname in */ char *hostname; /* hostname to use, as parsed from url. points to somewhere within the namebuffer[] area */ +#ifdef USE_LIBIDN + char *ace_hostname; /* hostname possibly converted to ACE form */ +#endif char *pathbuffer;/* allocated buffer to store the URL's path part in */ char *path; /* path to use, points to somewhere within the pathbuffer area */ @@ -579,7 +583,8 @@ struct connectdata { char syserr_buf [256]; /* buffer for Curl_strerror() */ -#if defined(USE_ARES) || defined(USE_THREADING_GETHOSTBYNAME) +#if defined(USE_ARES) || defined(USE_THREADING_GETHOSTBYNAME) || \ + defined(USE_THREADING_GETADDRINFO) /* data used for the asynch name resolve callback */ struct Curl_async async; #endif