diff options
Diffstat (limited to 'lib/hostip.c')
-rw-r--r-- | lib/hostip.c | 1137 |
1 files changed, 100 insertions, 1037 deletions
diff --git a/lib/hostip.c b/lib/hostip.c index 32bb03de3..4b57051d7 100644 --- a/lib/hostip.c +++ b/lib/hostip.c @@ -92,59 +92,51 @@ #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 * a future version. Use the share interface instead! @@ -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,37 +477,18 @@ 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. */ static void freednsentry(void *freethis) @@ -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 - -/* - * 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! - */ - -CURLcode Curl_multi_ares_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; -} - +#ifdef CURLRES_HOSTENT_RELOCATE /* - * 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. + * 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_wait_for_resolv(struct connectdata *conn, - struct Curl_dns_entry **entry) +void Curl_hostent_relocate(struct hostent *h, long offset) { - 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; -} -#endif - -#if defined(USE_ARES) || defined(USE_THREADING_GETHOSTBYNAME) - -/* - * 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. - */ -static void host_callback(void *arg, /* "struct connectdata *" */ - int status, - struct hostent *hostent) -{ - 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); + int i=0; - if(data->share) - Curl_share_unlock(data, CURL_LOCK_DATA_DNS); + 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++; } } - 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); + 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 res; } +#endif /* CURLRES_HOSTENT_RELOCATE */ -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 +#ifdef CURLRES_ADDRINFO_COPY -/* - * 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); +/* align on even 64bit boundaries */ +#define MEMALIGN(x) ((x)+(8-(((unsigned long)(x))&0x7))) /* - * 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! + * 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 struct hostent* pack_hostent(char** buf, struct hostent* orig) +Curl_addrinfo *Curl_addrinfo_copy(Curl_addrinfo *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; + + if(!bufptr) + return NULL; /* major bad */ - bufptr = *buf; - copy = (struct hostent*)bufptr; + copy = (Curl_addrinfo *)bufptr; - bufptr += sizeof(struct hostent); + 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 */ |