Add "Happy Eyeballs" for IPv4/IPv6.

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.
This commit is contained in:
Björn Stenberg 2013-10-26 14:17:33 +02:00 committed by Daniel Stenberg
parent 7de4cc35f8
commit 7d7df83198
6 changed files with 161 additions and 161 deletions

View File

@ -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
if(CHKCONN_CONNECTED == chk) {
if(verifyconnect(sockfd, &error)) {
/* we are connected with TCP, awesome! */
/* check socket for connect */
result = Curl_socket_ready(CURL_SOCKET_BAD, conn->tempsock[i], 0);
/* see if we need to do any proxy magic first once we connected */
code = Curl_connected_proxy(conn, sockindex);
if(code)
return code;
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;
conn->bits.tcpconnect[sockindex] = TRUE;
case CURL_CSELECT_OUT:
if(verifyconnect(conn->tempsock[i], &error)) {
/* we are connected with TCP, awesome! */
int other = i ^ 1;
*connected = TRUE;
if(sockindex == FIRSTSOCKET)
Curl_pgrsTime(data, TIMER_CONNECT); /* connect done */
Curl_verboseconnect(conn);
Curl_updateconninfo(conn, sockfd);
/* 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]);
}
/* 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;
*connected = TRUE;
if(sockindex == FIRSTSOCKET)
Curl_pgrsTime(data, TIMER_CONNECT); /* connect done */
Curl_updateconninfo(conn, conn->sock[sockindex]);
Curl_verboseconnect(conn);
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));
/*
* 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);
}
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:
conn->timeoutms_per_addr = conn->tempaddr[i]->ai_next == NULL ?
allow : allow / 2;
code = trynextip(conn, sockindex, i, connected);
conn->timeoutms_per_addr = conn->ip_addr->ai_next == NULL ?
allow : allow / 2;
code = trynextip(conn, sockindex, 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;
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;
/* Max time for the next address */
conn->timeoutms_per_addr = curr_addr->ai_next == NULL ?
timeout_ms : timeout_ms / 2;
/* find first address for this address family, if any */
while(ai && ai->ai_family != family)
ai = ai->ai_next;
/* start connecting to the IP curr_addr points to */
res = singleipconnect(conn, curr_addr,
&sockfd, connected);
if(res)
return res;
/*
* Connecting with a Curl_addrinfo chain
*/
while(ai) {
CURLcode res;
if(sockfd != CURL_SOCKET_BAD)
break;
/* Max time for the next connection attempt */
conn->timeoutms_per_addr = ai->ai_next == NULL ?
timeout_ms : timeout_ms / 2;
/* 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 */
/* start connecting to the IP curr_addr points to */
res = singleipconnect(conn, ai, &sockfd, connected);
if(res)
return res;
*sockconn = sockfd; /* the socket descriptor we've connected */
if(sockfd != CURL_SOCKET_BAD)
break;
if(sockfd == CURL_SOCKET_BAD) {
/* 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;
/* next addresses */
do {
ai = ai->ai_next;
} while(ai && ai->ai_family != family);
} /* end of connect-to-each-address loop */
conn->tempsock[i] = sockfd;
conn->tempaddr[i] = ai;
}
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;

View File

@ -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

View File

@ -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 */
@ -2064,7 +2061,7 @@ static CURLcode ftp_state_pasv_resp(struct connectdata *conn,
if(data->set.verbose)
/* this just dumps information about this second connection */
ftp_pasv_verbose(conn, conninfo, ftpc->newhost, connectport);
ftp_pasv_verbose(conn, conn->ip_addr, ftpc->newhost, connectport);
if(connected) {
/* Only do the proxy connection magic if we're actually connected. We do

View File

@ -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,

View File

@ -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;

View File

@ -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];