curl-library
[PATCH] Add "Happy Eyeballs" for IPv4/IPv6.
From: Björn Stenberg <bjorn_at_haxx.se>
Date: Sat, 26 Oct 2013 14:17:33 +0200
Date: Sat, 26 Oct 2013 14:17:33 +0200
This patch invokes two socket connect()s nearly simultaneously, and
the socket that is first connected "wins" and is subsequently used for
the connection. The other is terminated.
There is a very slight IPv4 preference, in that if both sockets connect
simultaneously IPv4 is checked first and thus will win.
--- lib/connect.c | 298 ++++++++++++++++++++++++++++----------------------------- lib/connect.h | 2 - lib/ftp.c | 3 - lib/multi.c | 13 ++- lib/url.c | 8 +- lib/urldata.h | 2 + 6 files changed, 163 insertions(+), 163 deletions(-) diff --git a/lib/connect.c b/lib/connect.c index c442c48..4bf0232 100644 --- a/lib/connect.c +++ b/lib/connect.c @@ -233,45 +233,6 @@ long Curl_timeleft(struct SessionHandle *data, return timeout_ms; } -/* - * checkconnect() checks for a TCP connect on the given socket. - * It returns: - */ - -enum chkconn_t { - CHKCONN_SELECT_ERROR = -1, - CHKCONN_CONNECTED = 0, - CHKCONN_IDLE = 1, - CHKCONN_FDSET_ERROR = 2 -}; - -static enum chkconn_t -checkconnect(curl_socket_t sockfd) -{ - int rc; -#ifdef mpeix - /* Call this function once now, and ignore the results. We do this to - "clear" the error state on the socket so that we can later read it - reliably. This is reported necessary on the MPE/iX operating system. */ - (void)verifyconnect(sockfd, NULL); -#endif - - rc = Curl_socket_ready(CURL_SOCKET_BAD, sockfd, 0); - - if(-1 == rc) - /* error, no connect here, try next */ - return CHKCONN_SELECT_ERROR; - - else if(rc & CURL_CSELECT_ERR) - /* error condition caught */ - return CHKCONN_FDSET_ERROR; - - else if(rc) - return CHKCONN_CONNECTED; - - return CHKCONN_IDLE; -} - static CURLcode bindlocal(struct connectdata *conn, curl_socket_t sockfd, int af) { @@ -573,17 +534,19 @@ static bool verifyconnect(curl_socket_t sockfd, int *error) more address exists or error */ static CURLcode trynextip(struct connectdata *conn, int sockindex, + int tempindex, bool *connected) { curl_socket_t sockfd; Curl_addrinfo *ai; + int family = tempindex ? AF_INET6 : AF_INET; /* First clean up after the failed socket. Don't close it yet to ensure that the next IP's socket gets a different file descriptor, which can prevent bugs when the curl_multi_socket_action interface is used with certain select() replacements such as kqueue. */ - curl_socket_t fd_to_close = conn->sock[sockindex]; - conn->sock[sockindex] = CURL_SOCKET_BAD; + curl_socket_t fd_to_close = conn->tempsock[tempindex]; + conn->tempsock[tempindex] = CURL_SOCKET_BAD; *connected = FALSE; if(sockindex != FIRSTSOCKET) { @@ -591,21 +554,26 @@ static CURLcode trynextip(struct connectdata *conn, return CURLE_COULDNT_CONNECT; /* no next */ } - /* try the next address */ - ai = conn->ip_addr->ai_next; + /* try the next address with same family */ + ai = conn->tempaddr[tempindex]->ai_next; + while(ai && ai->ai_family != family) + ai = ai->ai_next; - while(ai) { + while(ai && ai->ai_family == family) { CURLcode res = singleipconnect(conn, ai, &sockfd, connected); if(res) return res; if(sockfd != CURL_SOCKET_BAD) { /* store the new socket descriptor */ - conn->sock[sockindex] = sockfd; - conn->ip_addr = ai; + conn->tempsock[tempindex] = sockfd; + conn->tempaddr[tempindex] = ai; Curl_closesocket(conn, fd_to_close); return CURLE_OK; } - ai = ai->ai_next; + + do { + ai = ai->ai_next; + } while(ai && ai->ai_family == family); } Curl_closesocket(conn, fd_to_close); return CURLE_COULDNT_CONNECT; @@ -707,6 +675,7 @@ void Curl_updateconninfo(struct connectdata *conn, curl_socket_t sockfd) error, Curl_strerror(conn, error)); return; } + memcpy(conn->ip_addr_str, conn->primary_ip, MAX_IPADR_LEN); if(!getaddressinfo((struct sockaddr*)&ssloc, conn->local_ip, &conn->local_port)) { @@ -732,11 +701,11 @@ CURLcode Curl_is_connected(struct connectdata *conn, { struct SessionHandle *data = conn->data; CURLcode code = CURLE_OK; - curl_socket_t sockfd = conn->sock[sockindex]; long allow = DEFAULT_CONNECT_TIMEOUT; int error = 0; struct timeval now; - enum chkconn_t chk; + int result; + int i; DEBUGASSERT(sockindex >= FIRSTSOCKET && sockindex <= SECONDARYSOCKET); @@ -759,69 +728,94 @@ CURLcode Curl_is_connected(struct connectdata *conn, return CURLE_OPERATION_TIMEDOUT; } - /* check socket for connect */ - chk = checkconnect(sockfd); - if(CHKCONN_IDLE == chk) { - if(curlx_tvdiff(now, conn->connecttime) >= conn->timeoutms_per_addr) { - infof(data, "After %ldms connect time, move on!\n", - conn->timeoutms_per_addr); - goto next; - } + for(i=0; i<2; i++) { + if(conn->tempsock[i] == CURL_SOCKET_BAD) + continue; - /* not an error, but also no connection yet */ - return code; - } +#ifdef mpeix + /* Call this function once now, and ignore the results. We do this to + "clear" the error state on the socket so that we can later read it + reliably. This is reported necessary on the MPE/iX operating system. */ + (void)verifyconnect(conn->tempsock[i], NULL); +#endif + + /* check socket for connect */ + result = Curl_socket_ready(CURL_SOCKET_BAD, conn->tempsock[i], 0); + + switch(result) { + case 0: /* no connection yet */ + if(curlx_tvdiff(now, conn->connecttime) >= conn->timeoutms_per_addr) { + infof(data, "After %ldms connect time, move on!\n", + conn->timeoutms_per_addr); + break; + } + return CURLE_OK; + + case CURL_CSELECT_OUT: + if(verifyconnect(conn->tempsock[i], &error)) { + /* we are connected with TCP, awesome! */ + int other = i ^ 1; + + /* use this socket from now on */ + conn->sock[sockindex] = conn->tempsock[i]; + conn->ip_addr = conn->tempaddr[i]; + + /* close the other socket, if open */ + if(conn->tempsock[other] != CURL_SOCKET_BAD) { + if(conn->fclosesocket) + conn->fclosesocket(conn->closesocket_client, + conn->tempsock[other]); + else + sclose(conn->tempsock[other]); + } - if(CHKCONN_CONNECTED == chk) { - if(verifyconnect(sockfd, &error)) { - /* we are connected with TCP, awesome! */ + /* see if we need to do any proxy magic first once we connected */ + code = Curl_connected_proxy(conn, sockindex); + if(code) + return code; - /* see if we need to do any proxy magic first once we connected */ - code = Curl_connected_proxy(conn, sockindex); - if(code) - return code; + conn->bits.tcpconnect[sockindex] = TRUE; - conn->bits.tcpconnect[sockindex] = TRUE; + *connected = TRUE; + if(sockindex == FIRSTSOCKET) + Curl_pgrsTime(data, TIMER_CONNECT); /* connect done */ + Curl_updateconninfo(conn, conn->sock[sockindex]); + Curl_verboseconnect(conn); - *connected = TRUE; - if(sockindex == FIRSTSOCKET) - Curl_pgrsTime(data, TIMER_CONNECT); /* connect done */ - Curl_verboseconnect(conn); - Curl_updateconninfo(conn, sockfd); + return CURLE_OK; + } + else + infof(data, "Connection failed\n"); + break; + case CURL_CSELECT_ERR|CURL_CSELECT_OUT: + (void)verifyconnect(conn->tempsock[i], &error); + break; + + default: + infof(data, "Whut?\n"); return CURLE_OK; } - /* nope, not connected for real */ - } - else { - /* nope, not connected */ - if(CHKCONN_FDSET_ERROR == chk) { - (void)verifyconnect(sockfd, &error); - infof(data, "%s\n",Curl_strerror(conn, error)); - } - else - infof(data, "Connection failed\n"); - } - /* - * The connection failed here, we should attempt to connect to the "next - * address" for the given host. But first remember the latest error. - */ - if(error) { - data->state.os_errno = error; - SET_SOCKERRNO(error); - } - next: + /* + * The connection failed here, we should attempt to connect to the "next + * address" for the given host. But first remember the latest error. + */ + if(error) { + data->state.os_errno = error; + SET_SOCKERRNO(error); + } - conn->timeoutms_per_addr = conn->ip_addr->ai_next == NULL ? - allow : allow / 2; - code = trynextip(conn, sockindex, connected); + conn->timeoutms_per_addr = conn->tempaddr[i]->ai_next == NULL ? + allow : allow / 2; + code = trynextip(conn, sockindex, i, connected); - if(code) { - error = SOCKERRNO; - data->state.os_errno = error; - failf(data, "Failed connect to %s:%ld; %s", - conn->host.name, conn->port, Curl_strerror(conn, error)); + if(code) { + error = SOCKERRNO; + data->state.os_errno = error; + failf(data, "Failed connect to %s:%ld; %s", + conn->host.name, conn->port, Curl_strerror(conn, error)); + } } return code; @@ -948,6 +942,8 @@ singleipconnect(struct connectdata *conn, struct SessionHandle *data = conn->data; curl_socket_t sockfd; CURLcode res = CURLE_OK; + char ipaddress[MAX_IPADR_LEN]; + long port; *sockp = CURL_SOCKET_BAD; *connected = FALSE; /* default is not connected */ @@ -961,7 +957,7 @@ singleipconnect(struct connectdata *conn, /* store remote address and port used in this connection attempt */ if(!getaddressinfo((struct sockaddr*)&addr.sa_addr, - conn->primary_ip, &conn->primary_port)) { + ipaddress, &port)) { /* malformed address or bug in inet_ntop, try next address */ error = ERRNO; failf(data, "sa_addr inet_ntop() failed with errno %d: %s", @@ -969,10 +965,7 @@ singleipconnect(struct connectdata *conn, Curl_closesocket(conn, sockfd); return CURLE_OK; } - memcpy(conn->ip_addr_str, conn->primary_ip, MAX_IPADR_LEN); - infof(data, " Trying %s...\n", conn->ip_addr_str); - - Curl_persistconninfo(conn); + infof(data, " Trying %s...\n", ipaddress); if(data->set.tcp_nodelay) tcpnodelay(conn, sockfd); @@ -1074,24 +1067,18 @@ singleipconnect(struct connectdata *conn, CURLcode Curl_connecthost(struct connectdata *conn, /* context */ const struct Curl_dns_entry *remotehost, - curl_socket_t *sockconn, /* the connected socket */ - Curl_addrinfo **addr, /* the one we used */ bool *connected) /* really connected? */ { struct SessionHandle *data = conn->data; - curl_socket_t sockfd = CURL_SOCKET_BAD; - Curl_addrinfo *ai; - Curl_addrinfo *curr_addr; - struct timeval after; struct timeval before = Curl_tvnow(); + int i; /************************************************************* * Figure out what maximum time we have left *************************************************************/ long timeout_ms; - DEBUGASSERT(sockconn); *connected = FALSE; /* default to not connected */ /* get the timeout left */ @@ -1104,45 +1091,62 @@ CURLcode Curl_connecthost(struct connectdata *conn, /* context */ } conn->num_addr = Curl_num_addresses(remotehost->addr); - - ai = remotehost->addr; + conn->tempaddr[0] = remotehost->addr; + conn->tempaddr[1] = remotehost->addr; /* Below is the loop that attempts to connect to all IP-addresses we - * know for the given host. One by one until one IP succeeds. + * know for the given host. + * One by one, for each protocol, until one IP succeeds. */ - /* - * Connecting with a Curl_addrinfo chain - */ - for(curr_addr = ai; curr_addr; curr_addr = curr_addr->ai_next) { - CURLcode res; - - /* Max time for the next address */ - conn->timeoutms_per_addr = curr_addr->ai_next == NULL ? - timeout_ms : timeout_ms / 2; - - /* start connecting to the IP curr_addr points to */ - res = singleipconnect(conn, curr_addr, - &sockfd, connected); - if(res) - return res; - - if(sockfd != CURL_SOCKET_BAD) - break; + for(i=0; i<2; i++) { + curl_socket_t sockfd = CURL_SOCKET_BAD; + Curl_addrinfo *ai = conn->tempaddr[i]; + int family = i ? AF_INET6 : AF_INET; + + /* find first address for this address family, if any */ + while(ai && ai->ai_family != family) + ai = ai->ai_next; + + /* + * Connecting with a Curl_addrinfo chain + */ + while(ai) { + CURLcode res; + + /* Max time for the next connection attempt */ + conn->timeoutms_per_addr = ai->ai_next == NULL ? + timeout_ms : timeout_ms / 2; + + /* start connecting to the IP curr_addr points to */ + res = singleipconnect(conn, ai, &sockfd, connected); + if(res) + return res; + + if(sockfd != CURL_SOCKET_BAD) + break; + + /* get a new timeout for next attempt */ + after = Curl_tvnow(); + timeout_ms -= Curl_tvdiff(after, before); + if(timeout_ms < 0) { + failf(data, "connect() timed out!"); + return CURLE_OPERATION_TIMEDOUT; + } + before = after; - /* get a new timeout for next attempt */ - after = Curl_tvnow(); - timeout_ms -= Curl_tvdiff(after, before); - if(timeout_ms < 0) { - failf(data, "connect() timed out!"); - return CURLE_OPERATION_TIMEDOUT; - } - before = after; - } /* end of connect-to-each-address loop */ + /* next addresses */ + do { + ai = ai->ai_next; + } while(ai && ai->ai_family != family); + } /* end of connect-to-each-address loop */ - *sockconn = sockfd; /* the socket descriptor we've connected */ + conn->tempsock[i] = sockfd; + conn->tempaddr[i] = ai; + } - if(sockfd == CURL_SOCKET_BAD) { + if((conn->tempsock[0] == CURL_SOCKET_BAD) && + (conn->tempsock[1] == CURL_SOCKET_BAD)) { /* no good connect was made */ failf(data, "couldn't connect to %s at %s:%ld", conn->bits.proxy?"proxy":"host", @@ -1152,10 +1156,6 @@ CURLcode Curl_connecthost(struct connectdata *conn, /* context */ /* leave the socket in non-blocking mode */ - /* store the address we use */ - if(addr) - *addr = curr_addr; - data->info.numconnects++; /* to track the number of connections made */ return CURLE_OK; diff --git a/lib/connect.h b/lib/connect.h index ab98002..526ce37 100644 --- a/lib/connect.h +++ b/lib/connect.h @@ -33,8 +33,6 @@ CURLcode Curl_is_connected(struct connectdata *conn, CURLcode Curl_connecthost(struct connectdata *conn, const struct Curl_dns_entry *host, /* connect to this */ - curl_socket_t *sockconn, /* not set if error */ - Curl_addrinfo **addr, /* the one we used */ bool *connected); /* truly connected? */ /* generic function that returns how much time there's left to run, according diff --git a/lib/ftp.c b/lib/ftp.c index e66a8a8..f793826 100644 --- a/lib/ftp.c +++ b/lib/ftp.c @@ -1881,7 +1881,6 @@ static CURLcode ftp_state_pasv_resp(struct connectdata *conn, struct ftp_conn *ftpc = &conn->proto.ftpc; CURLcode result; struct SessionHandle *data=conn->data; - Curl_addrinfo *conninfo; struct Curl_dns_entry *addr=NULL; int rc; unsigned short connectport; /* the local port connect() should use! */ @@ -2041,8 +2040,6 @@ static CURLcode ftp_state_pasv_resp(struct connectdata *conn, result = Curl_connecthost(conn, addr, - &conn->sock[SECONDARYSOCKET], - &conninfo, &connected); Curl_resolv_unlock(data, addr); /* we're done using this address */ diff --git a/lib/multi.c b/lib/multi.c index e723a3e..f11ba06 100644 --- a/lib/multi.c +++ b/lib/multi.c @@ -621,17 +621,26 @@ static int waitconnect_getsock(struct connectdata *conn, curl_socket_t *sock, int numsocks) { + int i; + int s=0; + int rc=0; + if(!numsocks) return GETSOCK_BLANK; - sock[0] = conn->sock[FIRSTSOCKET]; + for(i=0; i<2; i++) { + if(conn->tempsock[i] != CURL_SOCKET_BAD) { + sock[s] = conn->tempsock[i]; + rc |= GETSOCK_WRITESOCK(s++); + } + } /* when we've sent a CONNECT to a proxy, we should rather wait for the socket to become readable to be able to get the response headers */ if(conn->tunnel_state[FIRSTSOCKET] == TUNNEL_CONNECT) return GETSOCK_READSOCK(0); - return GETSOCK_WRITESOCK(0); + return rc; } static int domore_getsock(struct connectdata *conn, diff --git a/lib/url.c b/lib/url.c index 9973bd2..e86fbc2 100644 --- a/lib/url.c +++ b/lib/url.c @@ -3260,7 +3260,6 @@ static CURLcode ConnectPlease(struct SessionHandle *data, bool *connected) { CURLcode result; - Curl_addrinfo *addr; #ifndef CURL_DISABLE_VERBOSE_STRINGS char *hostname = conn->bits.proxy?conn->proxy.name:conn->host.name; @@ -3276,13 +3275,8 @@ static CURLcode ConnectPlease(struct SessionHandle *data, *************************************************************/ result= Curl_connecthost(conn, conn->dns_entry, - &conn->sock[FIRSTSOCKET], - &addr, connected); if(CURLE_OK == result) { - /* All is cool, we store the current information */ - conn->ip_addr = addr; - if(*connected) { result = Curl_connected_proxy(conn, FIRSTSOCKET); if(!result) { @@ -5643,8 +5637,8 @@ CURLcode Curl_setup_conn(struct connectdata *conn, Curl_pgrsTime(data, TIMER_APPCONNECT); /* we're connected already */ conn->bits.tcpconnect[FIRSTSOCKET] = TRUE; *protocol_done = TRUE; - Curl_verboseconnect(conn); Curl_updateconninfo(conn, conn->sock[FIRSTSOCKET]); + Curl_verboseconnect(conn); } /* Stop the loop now */ break; diff --git a/lib/urldata.h b/lib/urldata.h index ebaaf6f..98686bb 100644 --- a/lib/urldata.h +++ b/lib/urldata.h @@ -838,6 +838,7 @@ struct connectdata { within the DNS cache, so this pointer is only valid as long as the DNS cache entry remains locked. It gets unlocked in Curl_done() */ Curl_addrinfo *ip_addr; + Curl_addrinfo *tempaddr[2]; /* for happy eyeballs */ /* 'ip_addr_str' is the ip_addr data as a human readable string. It remains available as long as the connection does, which is longer than @@ -889,6 +890,7 @@ struct connectdata { struct timeval created; /* creation time */ curl_socket_t sock[2]; /* two sockets, the second is used for the data transfer when doing FTP */ + curl_socket_t tempsock[2]; /* temporary sockets for happy eyeballs */ bool sock_accepted[2]; /* TRUE if the socket on this index was created with accept() */ Curl_recv *recv[2]; -- 1.7.10.4 --0F1p//8PRICkK4MW Content-Type: text/plain; charset="us-ascii" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit Content-Disposition: inline ------------------------------------------------------------------- List admin: http://cool.haxx.se/list/listinfo/curl-library Etiquette: http://curl.haxx.se/mail/etiquette.html --0F1p//8PRICkK4MW--Received on 2001-09-17