aboutsummaryrefslogtreecommitdiff
path: root/lib/connect.c
diff options
context:
space:
mode:
authorBjörn Stenberg <bjorn@haxx.se>2013-10-26 14:17:33 +0200
committerDaniel Stenberg <daniel@haxx.se>2013-10-27 11:19:53 +0100
commit7d7df831981feebdbfeb21c12d6c068312a6e4b6 (patch)
tree0f9020e62bfbc3fa74b2d7a9200f03cf5fe4e1ae /lib/connect.c
parent7de4cc35f81aa6c3cdb8b377a27843bcabdaec48 (diff)
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.
Diffstat (limited to 'lib/connect.c')
-rw-r--r--lib/connect.c298
1 files changed, 149 insertions, 149 deletions
diff --git a/lib/connect.c b/lib/connect.c
index c442c48f8..4bf0232f9 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;