mirror of
https://github.com/moparisthebest/curl
synced 2024-12-21 23:58:49 -05:00
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:
parent
7de4cc35f8
commit
7d7df83198
190
lib/connect.c
190
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;
|
||||
}
|
||||
|
||||
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,22 +728,46 @@ CURLcode Curl_is_connected(struct connectdata *conn,
|
||||
return CURLE_OPERATION_TIMEDOUT;
|
||||
}
|
||||
|
||||
for(i=0; i<2; i++) {
|
||||
if(conn->tempsock[i] == CURL_SOCKET_BAD)
|
||||
continue;
|
||||
|
||||
#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 */
|
||||
chk = checkconnect(sockfd);
|
||||
if(CHKCONN_IDLE == chk) {
|
||||
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);
|
||||
goto next;
|
||||
break;
|
||||
}
|
||||
return CURLE_OK;
|
||||
|
||||
/* not an error, but also no connection yet */
|
||||
return code;
|
||||
}
|
||||
|
||||
if(CHKCONN_CONNECTED == chk) {
|
||||
if(verifyconnect(sockfd, &error)) {
|
||||
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]);
|
||||
}
|
||||
|
||||
/* see if we need to do any proxy magic first once we connected */
|
||||
code = Curl_connected_proxy(conn, sockindex);
|
||||
@ -786,21 +779,22 @@ CURLcode Curl_is_connected(struct connectdata *conn,
|
||||
*connected = TRUE;
|
||||
if(sockindex == FIRSTSOCKET)
|
||||
Curl_pgrsTime(data, TIMER_CONNECT); /* connect done */
|
||||
Curl_updateconninfo(conn, conn->sock[sockindex]);
|
||||
Curl_verboseconnect(conn);
|
||||
Curl_updateconninfo(conn, sockfd);
|
||||
|
||||
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");
|
||||
break;
|
||||
|
||||
case CURL_CSELECT_ERR|CURL_CSELECT_OUT:
|
||||
(void)verifyconnect(conn->tempsock[i], &error);
|
||||
break;
|
||||
|
||||
default:
|
||||
infof(data, "Whut?\n");
|
||||
return CURLE_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
@ -811,11 +805,10 @@ CURLcode Curl_is_connected(struct connectdata *conn,
|
||||
data->state.os_errno = error;
|
||||
SET_SOCKERRNO(error);
|
||||
}
|
||||
next:
|
||||
|
||||
conn->timeoutms_per_addr = conn->ip_addr->ai_next == NULL ?
|
||||
conn->timeoutms_per_addr = conn->tempaddr[i]->ai_next == NULL ?
|
||||
allow : allow / 2;
|
||||
code = trynextip(conn, sockindex, connected);
|
||||
code = trynextip(conn, sockindex, i, connected);
|
||||
|
||||
if(code) {
|
||||
error = SOCKERRNO;
|
||||
@ -823,6 +816,7 @@ CURLcode Curl_is_connected(struct connectdata *conn,
|
||||
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,26 +1091,35 @@ 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.
|
||||
*/
|
||||
|
||||
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
|
||||
*/
|
||||
for(curr_addr = ai; curr_addr; curr_addr = curr_addr->ai_next) {
|
||||
while(ai) {
|
||||
CURLcode res;
|
||||
|
||||
/* Max time for the next address */
|
||||
conn->timeoutms_per_addr = curr_addr->ai_next == NULL ?
|
||||
/* 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, curr_addr,
|
||||
&sockfd, connected);
|
||||
res = singleipconnect(conn, ai, &sockfd, connected);
|
||||
if(res)
|
||||
return res;
|
||||
|
||||
@ -1138,11 +1134,19 @@ CURLcode Curl_connecthost(struct connectdata *conn, /* context */
|
||||
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 */
|
||||
|
||||
*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;
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
13
lib/multi.c
13
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,
|
||||
|
@ -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;
|
||||
|
@ -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];
|
||||
|
Loading…
Reference in New Issue
Block a user