diff --git a/lib/Makefile.am b/lib/Makefile.am index 421b4fecb..ffa6be2b1 100644 --- a/lib/Makefile.am +++ b/lib/Makefile.am @@ -12,10 +12,16 @@ EXTRA_DIST = getdate.y Makefile.b32 Makefile.b32.resp Makefile.m32 \ lib_LTLIBRARIES = libcurl.la +if ARES +ARESINC = -I$(top_srcdir)/ares +endif + # we use srcdir/include for the static global include files # we use builddir/lib for the generated lib/config.h file to get found # we use srcdir/lib for the lib-private header files -INCLUDES = -I$(top_srcdir)/include -I$(top_builddir)/lib -I$(top_srcdir)/lib +INCLUDES = -I$(top_srcdir)/include -I$(top_builddir)/lib -I$(top_srcdir)/lib $(ARESINC) + +LDFLAGS = -L$(top_srcdir)/lib VERSION=-version-info 2:2:0 @@ -48,15 +54,18 @@ VERSION=-version-info 2:2:0 # if NO_UNDEFINED -# The -no-undefined flag is CRUCIAL for this to build fine on Cygwin. If we -# find a case in which we need to remove this flag, we should most likely -# write a configure check that detects when this flag is needed and when its -# not. -libcurl_la_LDFLAGS = -no-undefined $(VERSION) +# The -no-undefined flag is CRUCIAL for this to build fine on Cygwin. +UNDEF = -no-undefined else -libcurl_la_LDFLAGS = $(VERSION) +UNDEF = endif +if ARES +ARESLIB = -lares -L$(top_builddir)/ares +endif + +libcurl_la_LDFLAGS = $(UNDEF) $(VERSION) $(ARESLIB) + libcurl_la_SOURCES = arpa_telnet.h file.c getpass.h netrc.h timeval.c \ base64.c file.h hostip.c progress.c timeval.h base64.h formdata.c \ hostip.h progress.h cookie.c formdata.h http.c sendf.c cookie.h ftp.c \ diff --git a/lib/connect.c b/lib/connect.c index 681fc0039..9bc7d5076 100644 --- a/lib/connect.c +++ b/lib/connect.c @@ -208,6 +208,7 @@ static CURLcode bindlocal(struct connectdata *conn, size_t size; char myhost[256] = ""; in_addr_t in; + int rc; /* First check if the given name is an IP address */ in=inet_addr(data->set.device); @@ -217,7 +218,10 @@ static CURLcode bindlocal(struct connectdata *conn, /* * We now have the numerical IPv4-style x.y.z.w in the 'myhost' buffer */ - h = Curl_resolv(data, myhost, 0); + rc = Curl_resolv(conn, myhost, 0, &h); + if(rc == 1) + rc = Curl_wait_for_resolv(conn, &h); + } else { if(strlen(data->set.device)>1) { @@ -225,11 +229,14 @@ static CURLcode bindlocal(struct connectdata *conn, * This was not an interface, resolve the name as a host name * or IP number */ - h = Curl_resolv(data, data->set.device, 0); - if(h) { + rc = Curl_resolv(conn, data->set.device, 0, &h); + if(rc == 1) + rc = Curl_wait_for_resolv(conn, &h); + + if(h) /* we know data->set.device is shorter than the myhost array */ strcpy(myhost, data->set.device); - } + } } diff --git a/lib/ftp.c b/lib/ftp.c index 6acab245b..bb3b9feb3 100644 --- a/lib/ftp.c +++ b/lib/ftp.c @@ -1231,18 +1231,24 @@ CURLcode ftp_use_port(struct connectdata *conn) if(data->set.ftpport) { in_addr_t in; + int rc; /* First check if the given name is an IP address */ in=inet_addr(data->set.ftpport); if((in == CURL_INADDR_NONE) && Curl_if2ip(data->set.ftpport, myhost, sizeof(myhost))) { - h = Curl_resolv(data, myhost, 0); + rc = Curl_resolv(conn, myhost, 0, &h); + if(rc == 1) + rc = Curl_wait_for_resolv(conn, &h); } else { int len = strlen(data->set.ftpport); - if(len>1) - h = Curl_resolv(data, data->set.ftpport, 0); + if(len>1) { + rc = Curl_resolv(conn, data->set.ftpport, 0, &h); + if(rc == 1) + rc = Curl_wait_for_resolv(conn, &h); + } if(h) strcpy(myhost, data->set.ftpport); /* buffer overflow risk */ } @@ -1381,6 +1387,7 @@ CURLcode ftp_use_pasv(struct connectdata *conn, CURLcode result; struct Curl_dns_entry *addr=NULL; Curl_ipconnect *conninfo; + int rc; /* Here's the excecutive summary on what to do: @@ -1505,14 +1512,20 @@ CURLcode ftp_use_pasv(struct connectdata *conn, * We don't want to rely on a former host lookup that might've expired * now, instead we remake the lookup here and now! */ - addr = Curl_resolv(data, conn->proxyhost, conn->port); + rc = Curl_resolv(conn, conn->proxyhost, conn->port, &addr); + if(rc == 1) + rc = Curl_wait_for_resolv(conn, &addr); + connectport = (unsigned short)conn->port; /* we connect to the proxy's port */ } else { /* normal, direct, ftp connection */ - addr = Curl_resolv(data, newhostp, newport); + rc = Curl_resolv(conn, newhostp, newport, &addr); + if(rc == 1) + rc = Curl_wait_for_resolv(conn, &addr); + if(!addr) { failf(data, "Can't resolve new host %s:%d", newhostp, newport); return CURLE_FTP_CANT_GET_HOST; diff --git a/lib/hostip.c b/lib/hostip.c index b1b175d9e..3e69d4ea9 100644 --- a/lib/hostip.c +++ b/lib/hostip.c @@ -65,6 +65,7 @@ #include "hostip.h" #include "hash.h" #include "share.h" +#include "url.h" #define _MPRINTF_REPLACE /* use our functions only */ #include @@ -81,10 +82,13 @@ static curl_hash hostname_cache; static int host_cache_initialized; -static Curl_addrinfo *my_getaddrinfo(struct SessionHandle *data, - char *hostname, - int port, - char **bufp); +static Curl_addrinfo *my_getaddrinfo(struct connectdata *conn, + char *hostname, + int port, + int *waitp); +#if !defined(HAVE_GETHOSTBYNAME_R) || defined(USE_ARES) +static struct hostent* pack_hostent(char** buf, struct hostent* orig); +#endif void Curl_global_host_cache_init(void) { @@ -135,15 +139,14 @@ create_hostcache_id(char *server, int port, ssize_t *entry_len) char *id = NULL; /* Get the length of the new entry id */ - *entry_len = *entry_len + /* Hostname length */ - 1 + /* The ':' seperator */ - _num_chars(port); /* The number of characters the port will take up */ + *entry_len = *entry_len + /* Hostname length */ + 1 + /* ':' seperator */ + _num_chars(port); /* number of characters the port will take up */ /* Allocate the new entry id */ id = malloc(*entry_len + 1); - if (!id) { + if (!id) return NULL; - } /* Create the new entry */ /* If sprintf() doesn't return the entry length, that signals failure */ @@ -192,57 +195,26 @@ hostcache_prune(curl_hash *hostcache, int cache_timeout, int now) hostcache_timestamp_remove); } -#if defined(CURLDEBUG) && defined(AGGRESIVE_TEST) -/* Called from Curl_done() to check that there's no DNS cache entry with - a non-zero counter left. */ -void Curl_scan_cache_used(void *user, void *ptr) -{ - struct Curl_dns_entry *e = ptr; - (void)user; /* prevent compiler warning */ - if(e->inuse) { - fprintf(stderr, "*** WARNING: locked DNS cache entry detected: %s\n", - e->entry_id); - /* perform a segmentation fault to draw attention */ - *(void **)0 = 0; - } -} -#endif - -/* Macro to save redundant free'ing of entry_id */ -#define HOSTCACHE_RETURN(dns) \ -{ \ - free(entry_id); \ - if(data->share) \ - { \ - Curl_share_unlock(data, CURL_LOCK_DATA_DNS); \ - } \ - return dns; \ -} - #ifdef HAVE_SIGSETJMP /* Beware this is a global and unique instance */ sigjmp_buf curl_jmpenv; #endif -struct Curl_dns_entry *Curl_resolv(struct SessionHandle *data, - char *hostname, - int port) -{ - char *entry_id = NULL; - struct Curl_dns_entry *dns = NULL; - ssize_t entry_len; - time_t now; - char *bufp; -#ifdef HAVE_SIGSETJMP - /* this allows us to time-out from the name resolver, as the timeout - will generate a signal and we will siglongjmp() from that here */ - if(!data->set.no_signal && sigsetjmp(curl_jmpenv, 1)) { - /* this is coming from a siglongjmp() */ - failf(data, "name lookup timed out"); - return NULL; - } -#endif +/* When calling Curl_resolv() has resulted in a response with a returned + address, we call this function to store the information in the dns + cache etc */ + +static struct Curl_dns_entry * +cache_resolv_response(struct SessionHandle *data, + Curl_addrinfo *addr, + char *hostname, + int port) +{ + char *entry_id; + int entry_len; + struct Curl_dns_entry *dns; + time_t now; /* Create an entry id, based upon the hostname and port */ entry_len = strlen(hostname); @@ -251,45 +223,112 @@ struct Curl_dns_entry *Curl_resolv(struct SessionHandle *data, if (!entry_id) return NULL; + /* Create a new cache entry */ + dns = (struct Curl_dns_entry *) malloc(sizeof(struct Curl_dns_entry)); + if (!dns) { + Curl_freeaddrinfo(addr); + free(entry_id); + return NULL; + } + + dns->inuse = 0; + dns->addr = addr; + + /* Store it in our dns cache */ + Curl_hash_add(data->hostcache, entry_id, entry_len+1, + (const void *) dns); + time(&now); + + dns->timestamp = now; + dns->inuse++; /* mark entry as in-use */ + + + /* Remove outdated and unused entries from the hostcache */ + hostcache_prune(data->hostcache, + data->set.dns_cache_timeout, + now); + + /* free the allocated entry_id again */ + free(entry_id); + + return dns; +} + +/* Resolve a name and return a pointer in the 'entry' argument if one + is available. + + Return codes: + + -1 = error, no pointer + 0 = OK, pointer provided + 1 = waiting for response, no pointer +*/ +int Curl_resolv(struct connectdata *conn, + char *hostname, + int port, + struct Curl_dns_entry **entry) +{ + char *entry_id = NULL; + struct Curl_dns_entry *dns = NULL; + ssize_t entry_len; + int wait; + struct SessionHandle *data = conn->data; + + /* default to failure */ + int rc = -1; + *entry = NULL; + +#ifdef HAVE_SIGSETJMP + /* this allows us to time-out from the name resolver, as the timeout + will generate a signal and we will siglongjmp() from that here */ + if(!data->set.no_signal && sigsetjmp(curl_jmpenv, 1)) { + /* this is coming from a siglongjmp() */ + failf(data, "name lookup timed out"); + return -1; + } +#endif + + /* Create an entry id, based upon the hostname and port */ + entry_len = strlen(hostname); + entry_id = create_hostcache_id(hostname, port, &entry_len); + /* If we can't create the entry id, fail */ + if (!entry_id) + return -1; + if(data->share) Curl_share_lock(data, CURL_LOCK_DATA_DNS, CURL_LOCK_ACCESS_SINGLE); /* See if its already in our dns cache */ dns = Curl_hash_pick(data->hostcache, entry_id, entry_len+1); + /* free the allocated entry_id again */ + free(entry_id); + if (!dns) { - Curl_addrinfo *addr = my_getaddrinfo(data, hostname, port, &bufp); + /* 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 + value indicating that we need to wait for the response to the resolve + call */ + Curl_addrinfo *addr = my_getaddrinfo(conn, hostname, port, &wait); if (!addr) { - HOSTCACHE_RETURN(NULL); + if(wait) + /* the response to our resolve call will come asynchronously at + a later time, good or bad */ + rc = 1; } - - /* Create a new cache entry */ - dns = (struct Curl_dns_entry *) malloc(sizeof(struct Curl_dns_entry)); - if (!dns) { - Curl_freeaddrinfo(addr); - HOSTCACHE_RETURN(NULL); - } - - dns->inuse = 0; - dns->addr = addr; - /* Save it in our host cache */ - Curl_hash_add(data->hostcache, entry_id, entry_len+1, (const void *) dns); + else + /* we got a response, store it in the cache */ + dns = cache_resolv_response(data, addr, hostname, port); } - time(&now); - dns->timestamp = now; - dns->inuse++; /* mark entry as in-use */ -#ifdef CURLDEBUG - dns->entry_id = entry_id; -#endif + if(data->share) + Curl_share_unlock(data, CURL_LOCK_DATA_DNS); - /* Remove outdated and unused entries from the hostcache */ - hostcache_prune(data->hostcache, - data->set.dns_cache_timeout, - now); + *entry = dns; - HOSTCACHE_RETURN(dns); + return rc; } void Curl_resolv_unlock(struct SessionHandle *data, struct Curl_dns_entry *dns) @@ -314,7 +353,7 @@ void Curl_freeaddrinfo(Curl_addrinfo *p) #ifdef ENABLE_IPV6 freeaddrinfo(p); #else - free(p); + free(p); /* works fine for the ARES case too */ #endif } @@ -332,7 +371,203 @@ void Curl_freednsinfo(void *freethis) /* --- resolve name or IP-number --- */ -#ifdef ENABLE_IPV6 +/* 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 + +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; +} + +/* called to check if the name is resolved now */ +CURLcode Curl_is_resolved(struct connectdata *conn, bool *done) +{ + fd_set read_fds, write_fds; + static const struct timeval tv={0,0}; + int count; + struct SessionHandle *data = conn->data; + int nfds = ares_fds(data->state.areschannel, &read_fds, &write_fds); + + count = select(nfds, &read_fds, &write_fds, NULL, + (struct timeval *)&tv); + + if(count) + ares_process(data->state.areschannel, &read_fds, &write_fds); + + if(conn->async.done) { + *done = TRUE; + + if(!conn->async.dns) + return CURLE_COULDNT_RESOLVE_HOST; + } + else + *done = FALSE; + + return CURLE_OK; +} + +/* This is a function that locks and waits until the name resolve operation + has completed. + + If 'entry' is non-NULL, make it point to the resolved dns entry + + Return 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; + + /* Wait for the name resolve query to complete. */ + while (1) { + int nfds=0; + fd_set read_fds, write_fds; + struct timeval *tvp, tv; + int count; + + FD_ZERO(&read_fds); + FD_ZERO(&write_fds); + nfds = ares_fds(data->state.areschannel, &read_fds, &write_fds); + if (nfds == 0) + break; + tvp = ares_timeout(data->state.areschannel, + NULL, /* pass in our maximum time here */ + &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); + } + + /* Operation complete, if the lookup was successful we now have the entry + in the cache. */ + + /* this destroys the channel and we cannot use it anymore after this */ + ares_destroy(data->state.areschannel); + + if(entry) + *entry = conn->async.dns; + + if(!conn->async.dns) { + /* a name was not resolved */ + if(conn->async.done) + 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; +} + +/* this function gets called by ares when we got the name resolved */ +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); + + dns = cache_resolv_response(conn->data, he, + conn->async.hostname, conn->async.port); + } + } + + conn->async.dns = dns; + + /* The input hostent struct will be freed by ares when we return from this + function */ +} + +/* + * Return 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 meory *MUST* be freed with + * Curl_freeaddrinfo(), nothing else. + */ +static Curl_addrinfo *my_getaddrinfo(struct connectdata *conn, + char *hostname, + int port, + int *waitp) +{ + int rc; + char *bufp; + struct SessionHandle *data = conn->data; + + rc = ares_init(&data->state.areschannel); + + *waitp = FALSE; + + if(!rc) { + /* only if success */ + + 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 */ + + ares_gethostbyname(data->state.areschannel, hostname, PF_INET, + host_callback, conn); + + *waitp = TRUE; /* please wait for the response */ + } + else + ares_destroy(data->state.areschannel); + } + + return NULL; /* no struct yet */ + +} +#else +/* For builds without ARES, Curl_resolv() can never return wait==TRUE, + so this function will never be called. If it still gets called, we + return failure at once. */ +CURLcode Curl_wait_for_resolv(struct connectdata *conn, + struct Curl_dns_entry **entry) +{ + (void)conn; + *entry=NULL; + return CURLE_COULDNT_RESOLVE_HOST; +} +#endif + +#if defined(ENABLE_IPV6) && !defined(USE_ARES) #ifdef CURLDEBUG /* These two are strictly for memory tracing and are using the same @@ -377,15 +612,16 @@ void curl_freeaddrinfo(struct addrinfo *freethis, * memory we need to free after use. That meory *MUST* be freed with * Curl_freeaddrinfo(), nothing else. */ -static Curl_addrinfo *my_getaddrinfo(struct SessionHandle *data, +static Curl_addrinfo *my_getaddrinfo(struct connectdata *conn, char *hostname, int port, - char **bufp) + int *waitp) { struct addrinfo hints, *res; int error; char sbuf[NI_MAXSERV]; int s, pf = PF_UNSPEC; + struct SessionHandle *data = conn->data; /* see if we have an IPv6 stack */ s = socket(PF_INET6, SOCK_DGRAM, 0); @@ -410,20 +646,18 @@ static Curl_addrinfo *my_getaddrinfo(struct SessionHandle *data, infof(data, "getaddrinfo(3) failed for %s:%d\n", hostname, port); return NULL; } - *bufp=(char *)res; /* make it point to the result struct */ + *waitp=0; /* don't wait, we have the response now */ return res; } #else /* following code is IPv4-only */ -#ifndef HAVE_GETHOSTBYNAME_R +#if !defined(HAVE_GETHOSTBYNAME_R) || defined(USE_ARES) static void hostcache_fixoffset(struct hostent *h, int offset); -/** +/* * 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! - * - * Keith McGuigan - * 10/3/2001 */ + */ static struct hostent* pack_hostent(char** buf, struct hostent* orig) { char *bufptr; @@ -512,6 +746,25 @@ static struct hostent* pack_hostent(char** buf, struct hostent* orig) } #endif +static void hostcache_fixoffset(struct hostent *h, int offset) +{ + int i=0; + h->h_name=(char *)((long)h->h_name+offset); + 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 + static char *MakeIP(unsigned long num, char *addr, int addr_len) { #if defined(HAVE_INET_NTOA) || defined(HAVE_INET_NTOA_R) @@ -533,43 +786,24 @@ static char *MakeIP(unsigned long num, char *addr, int addr_len) return (addr); } -static void hostcache_fixoffset(struct hostent *h, int offset) -{ - int i=0; - h->h_name=(char *)((long)h->h_name+offset); - 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++; - } -} - /* 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 SessionHandle *data, +static Curl_addrinfo *my_getaddrinfo(struct connectdata *conn, char *hostname, int port, - char **bufp) + int *waitp) { struct hostent *h = NULL; in_addr_t in; int ret; /* this variable is unused on several platforms but used on some */ + struct SessionHandle *data = conn->data; -#define CURL_NAMELOOKUP_SIZE 9000 - /* 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 editor, p. 304: 8192 bytes! */ - port=0; /* unused in IPv4 code */ + (void)port; /* unused in IPv4 code */ ret = 0; /* to prevent the compiler warning */ + *waitp = 0; /* don't wait, we act synchronously */ + in=inet_addr(hostname); if (in != CURL_INADDR_NONE) { struct in_addr *addrentry; @@ -581,7 +815,6 @@ static Curl_addrinfo *my_getaddrinfo(struct SessionHandle *data, } *buf = (struct namebuf *)malloc(sizeof(struct namebuf)); if(!buf) return NULL; /* major failure */ - *bufp = (char *)buf; h = &buf->hostentry; h->h_addr_list = &buf->h_addr_list[0]; @@ -602,7 +835,6 @@ static Curl_addrinfo *my_getaddrinfo(struct SessionHandle *data, int *buf = (int *)malloc(CURL_NAMELOOKUP_SIZE); if(!buf) return NULL; /* major failure */ - *bufp=(char *)buf; /* Workaround for gethostbyname_r bug in qnx nto. It is also _required_ for some of these functions. */ @@ -638,7 +870,6 @@ static Curl_addrinfo *my_getaddrinfo(struct SessionHandle *data, offset=(long)h-(long)buf; hostcache_fixoffset(h, offset); buf=(int *)h; - *bufp=(char *)buf; } else #endif @@ -687,7 +918,6 @@ static Curl_addrinfo *my_getaddrinfo(struct SessionHandle *data, offset=(long)h-(long)buf; hostcache_fixoffset(h, offset); buf=(int *)h; - *bufp=(char *)buf; } else #endif @@ -730,13 +960,11 @@ static Curl_addrinfo *my_getaddrinfo(struct SessionHandle *data, infof(data, "gethostbyname_r(2) failed for %s\n", hostname); h = NULL; /* set return code to NULL */ free(buf); - *bufp=NULL; } #else else { if ((h = gethostbyname(hostname)) == NULL ) { infof(data, "gethostbyname(2) failed for %s\n", hostname); - *bufp=NULL; } else { @@ -745,7 +973,6 @@ static Curl_addrinfo *my_getaddrinfo(struct SessionHandle *data, static one we got a pointer to might get removed when we don't want/expect that */ h = pack_hostent(&buf, h); - *bufp=(char *)buf; } #endif } @@ -753,3 +980,5 @@ static Curl_addrinfo *my_getaddrinfo(struct SessionHandle *data, } #endif /* end of IPv4-specific code */ + +#endif /* end of !USE_ARES */ diff --git a/lib/hostip.h b/lib/hostip.h index 36828ee1b..2f53f4f16 100644 --- a/lib/hostip.h +++ b/lib/hostip.h @@ -29,6 +29,7 @@ struct addrinfo; struct hostent; struct SessionHandle; +struct connectdata; void Curl_global_host_cache_init(void); void Curl_global_host_cache_dtor(void); @@ -41,9 +42,6 @@ struct Curl_dns_entry { time_t timestamp; long inuse; /* use-counter, make very sure you decrease this when you're done using the address you received */ -#ifdef CURLDEBUG - char *entry_id; -#endif }; /* @@ -54,10 +52,18 @@ struct Curl_dns_entry { * use, or we'll leak memory! */ -struct Curl_dns_entry *Curl_resolv(struct SessionHandle *data, - char *hostname, - int port); +int Curl_resolv(struct connectdata *conn, + char *hostname, + int port, + struct Curl_dns_entry **dnsentry); +CURLcode Curl_is_resolved(struct connectdata *conn, bool *done); +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); /* unlock a previously resolved dns entry */ void Curl_resolv_unlock(struct SessionHandle *data, struct Curl_dns_entry *dns); diff --git a/lib/multi.c b/lib/multi.c index f6749f18f..0d2fcf7ff 100644 --- a/lib/multi.c +++ b/lib/multi.c @@ -56,7 +56,8 @@ struct Curl_message { typedef enum { CURLM_STATE_INIT, - CURLM_STATE_CONNECT, /* connect has been sent off */ + CURLM_STATE_CONNECT, /* resolve/connect has been sent off */ + CURLM_STATE_WAITRESOLVE, /* we're awaiting the resolve to finalize */ CURLM_STATE_WAITCONNECT, /* we're awaiting the connect to finalize */ CURLM_STATE_DO, /* send off the request (part 1) */ CURLM_STATE_DO_MORE, /* send off the request (part 2) */ @@ -239,6 +240,14 @@ CURLMcode curl_multi_fdset(CURLM *multi_handle, switch(easy->state) { default: 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); + if(this_max_fd > *max_fd) + *max_fd = this_max_fd; + break; + case CURLM_STATE_WAITCONNECT: case CURLM_STATE_DO_MORE: { @@ -293,6 +302,7 @@ CURLMcode curl_multi_perform(CURLM *multi_handle, int *running_handles) CURLMcode result=CURLM_OK; struct Curl_message *msg = NULL; bool connected; + bool async; *running_handles = 0; /* bump this once for every living handle */ @@ -320,6 +330,7 @@ CURLMcode curl_multi_perform(CURLM *multi_handle, int *running_handles) easy->easy_handle->state.used_interface = Curl_if_multi; } break; + case CURLM_STATE_CONNECT: if (Curl_global_host_cache_use(easy->easy_handle)) { easy->easy_handle->hostcache = Curl_global_host_cache_get(); @@ -333,16 +344,46 @@ CURLMcode curl_multi_perform(CURLM *multi_handle, int *running_handles) /* Connect. We get a connection identifier filled in. */ Curl_pgrsTime(easy->easy_handle, TIMER_STARTSINGLE); - easy->result = Curl_connect(easy->easy_handle, &easy->easy_conn); + easy->result = Curl_connect(easy->easy_handle, &easy->easy_conn, &async); - /* after the connect has been sent off, go WAITCONNECT */ if(CURLE_OK == easy->result) { - easy->state = CURLM_STATE_WAITCONNECT; - result = CURLM_CALL_MULTI_PERFORM; + if(async) + /* We're now waiting for an asynchronous name lookup */ + easy->state = CURLM_STATE_WAITRESOLVE; + else { + /* after the connect has been sent off, go WAITCONNECT */ + easy->state = CURLM_STATE_WAITCONNECT; + result = CURLM_CALL_MULTI_PERFORM; + } + } + break; + + case CURLM_STATE_WAITRESOLVE: + /* awaiting an asynch name resolve to complete */ + { + bool done; + + /* check if we have the name resolved by now */ + easy->result = Curl_is_resolved(easy->easy_conn, &done); + + if(done) { + /* Perform the next step in the connection phase, and then move on + to the WAITCONNECT state */ + easy->result = Curl_async_resolved(easy->easy_conn); + + easy->state = CURLM_STATE_WAITCONNECT; + } + + if(CURLE_OK != easy->result) { + /* failure detected */ + easy->easy_conn = NULL; /* no more connection */ + break; + } } break; case CURLM_STATE_WAITCONNECT: + /* awaiting a completion of an asynch connect */ { bool connected; easy->result = Curl_is_connected(easy->easy_conn, diff --git a/lib/setup.h b/lib/setup.h index 4af61f97e..ef1514505 100644 --- a/lib/setup.h +++ b/lib/setup.h @@ -185,7 +185,7 @@ int fileno( FILE *stream); * Information regarding a single IP witin a Curl_addrinfo MUST be stored in * a Curl_ipconnect struct. */ -#ifdef ENABLE_IPV6 +#if defined(ENABLE_IPV6) && !defined(USE_ARES) typedef struct addrinfo Curl_addrinfo; typedef struct addrinfo Curl_ipconnect; #else diff --git a/lib/transfer.c b/lib/transfer.c index 445975b0a..c4b1c90fa 100644 --- a/lib/transfer.c +++ b/lib/transfer.c @@ -605,8 +605,7 @@ CURLcode Curl_readwrite(struct connectdata *conn, len = end-start+1; /* allocate memory of a cloned copy */ - if(data->info.contenttype) - free(data->info.contenttype); + Curl_safefree(data->info.contenttype); data->info.contenttype = malloc(len + 1); if (NULL == data->info.contenttype) @@ -1903,10 +1902,22 @@ CURLcode Curl_perform(struct SessionHandle *data) do { int urlchanged = FALSE; do { + bool async; Curl_pgrsTime(data, TIMER_STARTSINGLE); data->change.url_changed = FALSE; - res = Curl_connect(data, &conn); + res = Curl_connect(data, &conn, &async); + if((CURLE_OK == res) && async) { + /* Now, if async is TRUE here, we need to wait for the name + to resolve */ + res = Curl_wait_for_resolv(conn, NULL); + if(CURLE_OK == res) + /* Resolved, continue with the connection */ + res = Curl_async_resolved(conn); + } + if(res) + break; + /* If a callback (or something) has altered the URL we should use within the Curl_connect(), we detect it here and act as if we are redirected to the new URL */ diff --git a/lib/url.c b/lib/url.c index f1095bcee..84f3bbb0f 100644 --- a/lib/url.c +++ b/lib/url.c @@ -147,7 +147,11 @@ static unsigned int ConnectionStore(struct SessionHandle *data, struct connectdata *conn); static bool safe_strequal(char* str1, char* str2); -#if !defined(WIN32)||defined(__CYGWIN32__) +#ifndef USE_ARES +/* not for Win32, unless it is cygwin + not for ares builds */ +#if !defined(WIN32) || defined(__CYGWIN32__) + #ifndef RETSIGTYPE #define RETSIGTYPE void #endif @@ -165,6 +169,7 @@ RETSIGTYPE alarmfunc(int signal) return; } #endif +#endif /* USE_ARES */ void Curl_safefree(void *ptr) { @@ -1286,7 +1291,11 @@ CURLcode Curl_disconnect(struct connectdata *conn) Curl_safefree(conn->allocptr.host); Curl_safefree(conn->allocptr.cookiehost); Curl_safefree(conn->proxyhost); - +#ifdef USE_ARES + /* possible left-overs from the async name resolve */ + Curl_safefree(conn->async.hostname); +#endif + Curl_free_ssl_config(&conn->ssl_config); free(conn); /* free all the connection oriented data */ @@ -1632,7 +1641,15 @@ static int handleSock5Proxy( #ifndef ENABLE_IPV6 struct Curl_dns_entry *dns; Curl_addrinfo *hp=NULL; - dns = Curl_resolv(conn->data, conn->hostname, conn->remote_port); + int rc = Curl_resolv(conn, conn->hostname, conn->remote_port, &dns); + + if(rc == -1) + return CURLE_COULDNT_RESOLVE_HOST; + + if(rc == 1) + /* this requires that we're in "wait for resolve" state */ + rc = Curl_wait_for_resolv(conn, &dns); + /* * We cannot use 'hostent' as a struct that Curl_resolv() returns. It * returns a Curl_addrinfo pointer that may not always look the same. @@ -1841,8 +1858,19 @@ CURLcode Curl_protocol_connect(struct connectdata *conn, return result; /* pass back status */ } +/* + * CreateConnection() sets up a new connectdata struct, or re-uses an already + * existing one, and resolves host name. + * + * if this function returns CURLE_OK and *async is set to TRUE, the resolve + * response will be coming asynchronously. If *async is FALSE, the name is + * already resolved. + */ + static CURLcode CreateConnection(struct SessionHandle *data, - struct connectdata **in_connect) + struct connectdata **in_connect, + struct Curl_dns_entry **addr, + bool *async) { char *tmp; CURLcode result=CURLE_OK; @@ -1859,7 +1887,7 @@ static CURLcode CreateConnection(struct SessionHandle *data, char passwd[MAX_CURL_PASSWORD_LENGTH]; bool passwdgiven=FALSE; /* set TRUE if an application-provided password has been set */ - + int rc; #ifdef HAVE_SIGACTION struct sigaction keep_sigact; /* store the old struct here */ @@ -1870,6 +1898,9 @@ static CURLcode CreateConnection(struct SessionHandle *data, #endif #endif + *addr = NULL; /* nothing yet */ + *async = FALSE; + /************************************************************* * Check input data *************************************************************/ @@ -2875,8 +2906,10 @@ static CURLcode CreateConnection(struct SessionHandle *data, /* else, no chunky upload */ FALSE; +#ifndef USE_ARES /************************************************************* - * Set timeout if that is being used + * Set timeout if that is being used, and we're not using an asynchronous + * name resolve. *************************************************************/ if((data->set.timeout || data->set.connecttimeout) && !data->set.no_signal) { /************************************************************* @@ -2919,7 +2952,8 @@ static CURLcode CreateConnection(struct SessionHandle *data, has been done since then until now. */ #endif } - +#endif + /************************************************************* * Resolve the name of the server or proxy *************************************************************/ @@ -2935,9 +2969,11 @@ static CURLcode CreateConnection(struct SessionHandle *data, conn->port = conn->remote_port; /* it is the same port */ /* Resolve target host right on */ - hostaddr = Curl_resolv(data, conn->name, conn->port); + rc = Curl_resolv(conn, conn->name, conn->port, &hostaddr); + if(rc == 1) + *async = TRUE; - if(!hostaddr) { + else if(!hostaddr) { failf(data, "Couldn't resolve host '%s'", conn->name); result = CURLE_COULDNT_RESOLVE_HOST; /* don't return yet, we need to clean up the timeout first */ @@ -2947,15 +2983,19 @@ static CURLcode CreateConnection(struct SessionHandle *data, /* This is a proxy that hasn't been resolved yet. */ /* resolve proxy */ - hostaddr = Curl_resolv(data, conn->proxyhost, conn->port); + rc = Curl_resolv(conn, conn->proxyhost, conn->port, &hostaddr); - if(!hostaddr) { + if(rc == 1) + *async = TRUE; + + else if(!hostaddr) { failf(data, "Couldn't resolve proxy '%s'", conn->proxyhost); result = CURLE_COULDNT_RESOLVE_PROXY; /* don't return yet, we need to clean up the timeout first */ } } - Curl_pgrsTime(data, TIMER_NAMELOOKUP); + *addr = hostaddr; + #ifdef HAVE_ALARM if((data->set.timeout || data->set.connecttimeout) && !data->set.no_signal) { #ifdef HAVE_SIGACTION @@ -2995,7 +3035,25 @@ static CURLcode CreateConnection(struct SessionHandle *data, alarm(0); /* just shut it off */ } #endif - if(result) + + return result; +} + +/* SetupConnection() should be called after the name resolve initiated in + * CreateConnection() is all done. + */ + +static CURLcode SetupConnection(struct connectdata *conn, + struct Curl_dns_entry *hostaddr) +{ + struct SessionHandle *data = conn->data; + CURLcode result=CURLE_OK; + + Curl_pgrsTime(data, TIMER_NAMELOOKUP); + + if(conn->protocol & PROT_FILE) + /* There's nothing in this function to setup if we're only doing + a file:// transfer */ return result; /************************************************************* @@ -3007,8 +3065,7 @@ static CURLcode CreateConnection(struct SessionHandle *data, conn->proxyuser, conn->proxypasswd); if(Curl_base64_encode(data->state.buffer, strlen(data->state.buffer), &authorization) >= 0) { - if(conn->allocptr.proxyuserpwd) - free(conn->allocptr.proxyuserpwd); + Curl_safefree(conn->allocptr.proxyuserpwd); conn->allocptr.proxyuserpwd = aprintf("Proxy-authorization: Basic %s\015\012", authorization); free(authorization); @@ -3022,16 +3079,14 @@ static CURLcode CreateConnection(struct SessionHandle *data, if((conn->protocol&PROT_HTTP) || (data->change.proxy && *data->change.proxy)) { if(data->set.useragent) { - if(conn->allocptr.uagent) - free(conn->allocptr.uagent); + Curl_safefree(conn->allocptr.uagent); conn->allocptr.uagent = aprintf("User-Agent: %s\015\012", data->set.useragent); } } if(data->set.encoding) { - if(conn->allocptr.accept_encoding) - free(conn->allocptr.accept_encoding); + Curl_safefree(conn->allocptr.accept_encoding); conn->allocptr.accept_encoding = aprintf("Accept-Encoding: %s\015\012", data->set.encoding); } @@ -3083,26 +3138,60 @@ static CURLcode CreateConnection(struct SessionHandle *data, } CURLcode Curl_connect(struct SessionHandle *data, - struct connectdata **in_connect) + struct connectdata **in_connect, + bool *asyncp) { CURLcode code; - struct connectdata *conn; + struct Curl_dns_entry *dns; + *asyncp = FALSE; /* assume synchronous resolves by default */ + /* call the stuff that needs to be called */ - code = CreateConnection(data, in_connect); + code = CreateConnection(data, in_connect, &dns, asyncp); + if(CURLE_OK == code) { + /* no error */ + if(dns || !*asyncp) + /* If an address is available it means that we already have the name + resolved, OR it isn't async. + If so => continue connecting from here */ + code = SetupConnection(*in_connect, dns); + /* else + response will be received and treated async wise */ + } + if(CURLE_OK != code) { /* We're not allowed to return failure with memory left allocated in the connectdata struct, free those here */ - conn = (struct connectdata *)*in_connect; - if(conn) { - Curl_disconnect(conn); /* close the connection */ - *in_connect = NULL; /* return a NULL */ + if(*in_connect) { + Curl_disconnect(*in_connect); /* close the connection */ + *in_connect = NULL; /* return a NULL */ } } + return code; } +/* Call this function after Curl_connect() has returned async=TRUE and + then a successful name resolve has been received */ +CURLcode Curl_async_resolved(struct connectdata *conn) +{ +#ifdef USE_ARES + CURLcode code = SetupConnection(conn, conn->async.dns); + + if(code) + /* We're not allowed to return failure with memory left allocated + in the connectdata struct, free those here */ + Curl_disconnect(conn); /* close the connection */ + + return code; +#else + (void)conn; + return CURLE_OK; +#endif +} + + CURLcode Curl_done(struct connectdata *conn) { struct SessionHandle *data=conn->data; @@ -3179,11 +3268,28 @@ CURLcode Curl_do(struct connectdata **connp) conn->bits.close = TRUE; /* enforce close of this connetion */ result = Curl_done(conn); /* we are so done with this */ if(CURLE_OK == result) { + bool async; /* Now, redo the connect and get a new connection */ - result = Curl_connect(data, connp); - if(CURLE_OK == result) + result = Curl_connect(data, connp, &async); + if(CURLE_OK == result) { + /* We have connected or sent away a name resolve query fine */ + + if(async) { + /* Now, if async is TRUE here, we need to wait for the name + to resolve */ + result = Curl_wait_for_resolv(conn, NULL); + if(result) + return result; + + /* Resolved, continue with the connection */ + result = Curl_async_resolved(conn); + if(result) + return result; + } + /* ... finally back to actually retry the DO phase */ - result = conn->curl_do(*connp); + result = conn->curl_do(conn); + } } } } diff --git a/lib/url.h b/lib/url.h index 59ac39058..002b11249 100644 --- a/lib/url.h +++ b/lib/url.h @@ -30,7 +30,9 @@ CURLcode Curl_open(struct SessionHandle **curl); CURLcode Curl_setopt(struct SessionHandle *data, CURLoption option, ...); CURLcode Curl_close(struct SessionHandle *data); /* opposite of curl_open() */ -CURLcode Curl_connect(struct SessionHandle *, struct connectdata **); +CURLcode Curl_connect(struct SessionHandle *, struct connectdata **, + bool *async); +CURLcode Curl_async_resolved(struct connectdata *conn); CURLcode Curl_do(struct connectdata **); CURLcode Curl_do_more(struct connectdata *); CURLcode Curl_done(struct connectdata *); diff --git a/lib/urldata.h b/lib/urldata.h index 165b90fe1..0218935ab 100644 --- a/lib/urldata.h +++ b/lib/urldata.h @@ -90,6 +90,10 @@ #include #endif +#ifdef USE_ARES +#include +#endif + /* Download buffer size, keep it fairly big for speed reasons */ #define BUFSIZE CURL_MAX_WRITE_SIZE @@ -364,6 +368,16 @@ struct Curl_transfer_keeper { bool ignorebody; /* we read a response-body but we ignore it! */ }; +#ifdef USE_ARES +struct Curl_async { + char *hostname; + int port; + struct Curl_dns_entry *dns; + bool done; /* set TRUE when the lookup is complete */ + int status; /* if done is TRUE, this is the status from the callback */ +}; +#endif + /* * The connectdata struct contains all fields and variables that should be * unique for an entire connection. @@ -538,6 +552,11 @@ struct connectdata { because it authenticates connections, not single requests! */ struct ntlmdata proxyntlm; /* NTLM data for proxy */ + +#ifdef USE_ARES + /* data used for the asynch name resolve callback */ + struct Curl_async async; +#endif }; /* The end of connectdata. */ @@ -669,6 +688,10 @@ struct UrlState { long authwant; /* inherited from what the user set with CURLOPT_HTTPAUTH */ long authavail; /* what the server reports */ + +#ifdef USE_ARES + ares_channel areschannel; /* for name resolves */ +#endif };