connect: Add connection delay to Happy Eyeballs.

This patch adds a 200ms delay between the first and second address
family socket connection attempts.

It also iterates over IP addresses in the order returned by the
system, meaning most dual-stack systems will try IPv6 first.

Additionally, it refactors the connect code, removing most code that
handled synchronous connects. Since all sockets are now non-blocking,
the logic can be made simpler.
This commit is contained in:
Björn Stenberg 2013-10-29 11:51:25 +01:00 committed by Daniel Stenberg
parent 0074c9f5d1
commit 02fbc26d59
5 changed files with 79 additions and 206 deletions

View File

@ -164,8 +164,7 @@ tcpkeepalive(struct SessionHandle *data,
static CURLcode
singleipconnect(struct connectdata *conn,
const Curl_addrinfo *ai, /* start connecting to this */
curl_socket_t *sock,
bool *connected);
curl_socket_t *sock);
/*
* Curl_timeleft() returns the amount of milliseconds left allowed for the
@ -534,12 +533,9 @@ 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)
int tempindex)
{
curl_socket_t sockfd;
Curl_addrinfo *ai;
int family = tempindex ? AF_INET6 : AF_INET;
CURLcode rc = CURLE_COULDNT_CONNECT;
/* First clean up after the failed socket.
Don't close it yet to ensure that the next IP's socket gets a different
@ -547,36 +543,35 @@ static CURLcode trynextip(struct connectdata *conn,
interface is used with certain select() replacements such as kqueue. */
curl_socket_t fd_to_close = conn->tempsock[tempindex];
conn->tempsock[tempindex] = CURL_SOCKET_BAD;
*connected = FALSE;
if(sockindex != FIRSTSOCKET) {
Curl_closesocket(conn, fd_to_close);
return CURLE_COULDNT_CONNECT; /* no next */
}
if(sockindex == FIRSTSOCKET) {
Curl_addrinfo *ai;
int family;
/* 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 && 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->tempsock[tempindex] = sockfd;
conn->tempaddr[tempindex] = ai;
Curl_closesocket(conn, fd_to_close);
return CURLE_OK;
if(conn->tempaddr[tempindex]) {
/* find next address in the same protocol family */
family = conn->tempaddr[tempindex]->ai_family;
ai = conn->tempaddr[tempindex]->ai_next;
}
else {
/* happy eyeballs - try the other protocol family */
int firstfamily = conn->tempaddr[0]->ai_family;
family = (firstfamily == AF_INET) ? AF_INET6 : AF_INET;
ai = conn->tempaddr[0]->ai_next;
}
do {
while(ai && ai->ai_family != family)
ai = ai->ai_next;
} while(ai && ai->ai_family != family);
if(ai) {
rc = singleipconnect(conn, ai, &conn->tempsock[tempindex]);
conn->tempaddr[tempindex] = ai;
}
}
Curl_closesocket(conn, fd_to_close);
return CURLE_COULDNT_CONNECT;
if(fd_to_close != CURL_SOCKET_BAD)
Curl_closesocket(conn, fd_to_close);
return rc;
}
/* Copies connection info into the session handle to make it available
@ -701,7 +696,7 @@ CURLcode Curl_is_connected(struct connectdata *conn,
{
struct SessionHandle *data = conn->data;
CURLcode code = CURLE_OK;
long allow = DEFAULT_CONNECT_TIMEOUT;
long allow;
int error = 0;
struct timeval now;
int result;
@ -748,6 +743,12 @@ CURLcode Curl_is_connected(struct connectdata *conn,
conn->timeoutms_per_addr);
error = ETIMEDOUT;
}
/* should we try another protocol family? */
if(i == 0 && conn->tempaddr[1] == NULL &&
curlx_tvdiff(now, conn->connecttime) >= HAPPY_EYEBALLS_TIMEOUT) {
trynextip(conn, sockindex, 1);
}
}
else if(result == CURL_CSELECT_OUT) {
if(verifyconnect(conn->tempsock[i], &error)) {
@ -759,13 +760,8 @@ CURLcode Curl_is_connected(struct connectdata *conn,
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(conn->tempsock[other] != CURL_SOCKET_BAD)
Curl_closesocket(conn, conn->tempsock[other]);
/* see if we need to do any proxy magic first once we connected */
code = Curl_connected_proxy(conn, sockindex);
@ -797,18 +793,28 @@ CURLcode Curl_is_connected(struct connectdata *conn,
data->state.os_errno = error;
SET_SOCKERRNO(error);
Curl_printable_address(conn->tempaddr[i], ipaddress, MAX_IPADR_LEN);
infof(data, "connect to %s port %ld: %s\n",
infof(data, "connect to %s port %ld failed: %s\n",
ipaddress, conn->port, Curl_strerror(conn, error));
conn->timeoutms_per_addr = conn->tempaddr[i]->ai_next == NULL ?
allow : allow / 2;
code = trynextip(conn, sockindex, i, connected);
code = trynextip(conn, sockindex, i);
}
}
if(code) {
/* no more addresses to try */
/* if the first address family runs out of addresses to try before
the happy eyeball timeout, go ahead and try the next family now */
if(conn->tempaddr[1] == NULL) {
int rc;
rc = trynextip(conn, sockindex, 1);
if(rc == CURLE_OK)
return CURLE_OK;
}
failf(data, "Failed to connect to %s port %ld: %s",
conn->host.name, conn->port, Curl_strerror(conn, error));
}
@ -927,8 +933,7 @@ void Curl_sndbufset(curl_socket_t sockfd)
static CURLcode
singleipconnect(struct connectdata *conn,
const Curl_addrinfo *ai,
curl_socket_t *sockp,
bool *connected)
curl_socket_t *sockp)
{
struct Curl_sockaddr_ex addr;
int rc;
@ -941,7 +946,6 @@ singleipconnect(struct connectdata *conn,
long port;
*sockp = CURL_SOCKET_BAD;
*connected = FALSE; /* default is not connected */
res = Curl_socket(conn, ai, &addr, &sockfd);
if(res)
@ -1038,14 +1042,12 @@ singleipconnect(struct connectdata *conn,
default:
/* unknown error, fallthrough and try another address! */
failf(data, "Failed to connect to %s: %s",
conn->ip_addr_str, Curl_strerror(conn,error));
infof(data, "Immediate connect fail for %s: %s\n",
ipaddress, Curl_strerror(conn,error));
data->state.os_errno = error;
/* connect failed */
Curl_closesocket(conn, sockfd);
break;
return CURLE_COULDNT_CONNECT;
}
}
else
@ -1061,23 +1063,13 @@ singleipconnect(struct connectdata *conn,
*/
CURLcode Curl_connecthost(struct connectdata *conn, /* context */
const struct Curl_dns_entry *remotehost,
bool *connected) /* really connected? */
const struct Curl_dns_entry *remotehost)
{
struct SessionHandle *data = conn->data;
struct timeval after;
struct timeval before = Curl_tvnow();
int i;
CURLcode res;
/*************************************************************
* Figure out what maximum time we have left
*************************************************************/
long timeout_ms;
*connected = FALSE; /* default to not connected */
/* get the timeout left */
timeout_ms = Curl_timeleft(data, &before, TRUE);
long timeout_ms = Curl_timeleft(data, &before, TRUE);
if(timeout_ms < 0) {
/* a precaution, no need to continue if time already is up */
@ -1087,69 +1079,26 @@ CURLcode Curl_connecthost(struct connectdata *conn, /* context */
conn->num_addr = Curl_num_addresses(remotehost->addr);
conn->tempaddr[0] = remotehost->addr;
conn->tempaddr[1] = remotehost->addr;
conn->tempaddr[1] = NULL;
conn->tempsock[0] = CURL_SOCKET_BAD;
conn->tempsock[1] = CURL_SOCKET_BAD;
Curl_expire(conn->data,
HAPPY_EYEBALLS_TIMEOUT + (MULTI_TIMEOUT_INACCURACY/1000));
/* Below is the loop that attempts to connect to all IP-addresses we
* know for the given host.
* One by one, for each protocol, until one IP succeeds.
*/
/* Max time for the next connection attempt */
conn->timeoutms_per_addr =
conn->tempaddr[0]->ai_next == NULL ? timeout_ms : timeout_ms / 2;
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;
/* start connecting to first IP */
res = singleipconnect(conn, conn->tempaddr[0], &(conn->tempsock[0]));
while(res != CURLE_OK &&
conn->tempaddr[0] &&
conn->tempaddr[0]->ai_next &&
conn->tempsock[0] == CURL_SOCKET_BAD)
res = trynextip(conn, FIRSTSOCKET, 0);
/* 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;
/* 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",
conn->bits.proxy?conn->proxy.name:conn->host.name, conn->port);
return CURLE_COULDNT_CONNECT;
}
/* leave the socket in non-blocking mode */
if(conn->tempsock[0] == CURL_SOCKET_BAD)
return res;
data->info.numconnects++; /* to track the number of connections made */

View File

@ -31,9 +31,7 @@ CURLcode Curl_is_connected(struct connectdata *conn,
bool *connected);
CURLcode Curl_connecthost(struct connectdata *conn,
const struct Curl_dns_entry *host, /* connect to
this */
bool *connected); /* truly connected? */
const struct Curl_dns_entry *host);
/* generic function that returns how much time there's left to run, according
to the timeouts set */
@ -42,6 +40,8 @@ long Curl_timeleft(struct SessionHandle *data,
bool duringconnect);
#define DEFAULT_CONNECT_TIMEOUT 300000 /* milliseconds == five minutes */
#define HAPPY_EYEBALLS_TIMEOUT 200 /* milliseconds to wait between
ipv4/ipv6 connection attempts */
/*
* Used to extract socket and connectdata struct for the most recent

View File

@ -1884,7 +1884,6 @@ static CURLcode ftp_state_pasv_resp(struct connectdata *conn,
struct Curl_dns_entry *addr=NULL;
int rc;
unsigned short connectport; /* the local port connect() should use! */
bool connected;
char *str=&data->state.buffer[4]; /* start on the first letter */
if((ftpc->count1 == 0) &&
@ -2038,9 +2037,8 @@ static CURLcode ftp_state_pasv_resp(struct connectdata *conn,
}
}
result = Curl_connecthost(conn,
addr,
&connected);
conn->bits.tcpconnect[SECONDARYSOCKET] = FALSE;
result = Curl_connecthost(conn, addr);
Curl_resolv_unlock(data, addr); /* we're done using this address */
@ -2051,7 +2049,6 @@ static CURLcode ftp_state_pasv_resp(struct connectdata *conn,
return result;
}
conn->bits.tcpconnect[SECONDARYSOCKET] = connected;
/*
* When this is used from the multi interface, this might've returned with
@ -2063,15 +2060,6 @@ static CURLcode ftp_state_pasv_resp(struct connectdata *conn,
/* this just dumps information about this second connection */
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
this little trick and send in the same 'connected' variable here again
and it will be set FALSE by proxy_magic() for when for example the
CONNECT procedure doesn't complete */
infof(data, "Connection to proxy confirmed almost instantly\n");
result = proxy_magic(conn, ftpc->newhost, ftpc->newport, &connected);
}
conn->bits.tcpconnect[SECONDARYSOCKET] = connected;
conn->bits.do_more = TRUE;
state(conn, FTP_STOP); /* this phase is completed */

View File

@ -874,6 +874,7 @@ CURLMcode curl_multi_wait(CURLM *multi_handle,
if(nfds) {
/* wait... */
infof(data, "Curl_poll(%d ds, %d ms)\n", nfds, timeout_ms);
i = Curl_poll(ufds, nfds, timeout_ms);
if(i) {

View File

@ -3256,43 +3256,6 @@ CURLcode Curl_connected_proxy(struct connectdata *conn,
return CURLE_OK;
}
static CURLcode ConnectPlease(struct SessionHandle *data,
struct connectdata *conn,
bool *connected)
{
CURLcode result;
#ifndef CURL_DISABLE_VERBOSE_STRINGS
char *hostname = conn->bits.proxy?conn->proxy.name:conn->host.name;
infof(data, "About to connect() to %s%s port %ld (#%ld)\n",
conn->bits.proxy?"proxy ":"",
hostname, conn->port, conn->connection_id);
#else
(void)data;
#endif
/*************************************************************
* Connect to server/proxy
*************************************************************/
result= Curl_connecthost(conn,
conn->dns_entry,
connected);
if(CURLE_OK == result) {
if(*connected) {
result = Curl_connected_proxy(conn, FIRSTSOCKET);
if(!result) {
conn->bits.tcpconnect[FIRSTSOCKET] = TRUE;
Curl_pgrsTime(data, TIMER_CONNECT); /* connect done */
}
}
}
if(result)
*connected = FALSE; /* mark it as not connected */
return result;
}
/*
* verboseconnect() displays verbose information after a connect
*/
@ -5600,36 +5563,8 @@ CURLcode Curl_setup_conn(struct connectdata *conn,
/* loop for CURL_SERVER_CLOSED_CONNECTION */
if(CURL_SOCKET_BAD == conn->sock[FIRSTSOCKET]) {
/* Try to connect only if not already connected */
bool connected = FALSE;
result = ConnectPlease(data, conn, &connected);
if(result && !conn->ip_addr) {
/* transport connection failure not related with authentication */
conn->bits.tcpconnect[FIRSTSOCKET] = FALSE;
return result;
}
if(connected) {
result = Curl_protocol_connect(conn, protocol_done);
if(CURLE_OK == result)
conn->bits.tcpconnect[FIRSTSOCKET] = TRUE;
}
else
conn->bits.tcpconnect[FIRSTSOCKET] = FALSE;
/* if the connection was closed by the server while exchanging
authentication informations, retry with the new set
authentication information */
if(conn->bits.proxy_connect_closed) {
/* reset the error buffer */
if(data->set.errorbuffer)
data->set.errorbuffer[0] = '\0';
data->state.errorbuf = FALSE;
continue;
}
conn->bits.tcpconnect[FIRSTSOCKET] = FALSE;
result = Curl_connecthost(conn, conn->dns_entry);
if(CURLE_OK != result)
return result;
}