connect: improve happy eyeballs handling

For QUIC but also for regular TCP when the second family runs out of IPs
with a failure while the first family is still trying to connect.

Separated the timeout handling for IPv4 and IPv6 connections when they
both have a number of addresses to iterate over.
This commit is contained in:
Daniel Stenberg 2020-06-15 16:17:55 +02:00
parent 325866fd6f
commit dc90f51065
No known key found for this signature in database
GPG Key ID: 5CC908FDB71E12C2
5 changed files with 84 additions and 58 deletions

View File

@ -560,7 +560,7 @@ static bool verifyconnect(curl_socket_t sockfd, int *error)
to the correct family */ to the correct family */
static struct Curl_addrinfo *ainext(struct connectdata *conn, static struct Curl_addrinfo *ainext(struct connectdata *conn,
int tempindex, int tempindex,
bool next) /* use current or next entry */ bool next) /* use next entry? */
{ {
struct Curl_addrinfo *ai = conn->tempaddr[tempindex]; struct Curl_addrinfo *ai = conn->tempaddr[tempindex];
if(ai && next) if(ai && next)
@ -571,7 +571,7 @@ static struct Curl_addrinfo *ainext(struct connectdata *conn,
return ai; return ai;
} }
/* Used within the multi interface. Try next IP address, return TRUE if no /* Used within the multi interface. Try next IP address, returns error if no
more address exists or error */ more address exists or error */
static CURLcode trynextip(struct connectdata *conn, static CURLcode trynextip(struct connectdata *conn,
int sockindex, int sockindex,
@ -823,8 +823,8 @@ CURLcode Curl_is_connected(struct connectdata *conn,
timediff_t allow; timediff_t allow;
int error = 0; int error = 0;
struct curltime now; struct curltime now;
int rc; int rc = 0;
int i; unsigned int i;
DEBUGASSERT(sockindex >= FIRSTSOCKET && sockindex <= SECONDARYSOCKET); DEBUGASSERT(sockindex >= FIRSTSOCKET && sockindex <= SECONDARYSOCKET);
@ -859,49 +859,42 @@ CURLcode Curl_is_connected(struct connectdata *conn,
const int other = i ^ 1; const int other = i ^ 1;
if(conn->tempsock[i] == CURL_SOCKET_BAD) if(conn->tempsock[i] == CURL_SOCKET_BAD)
continue; continue;
error = 0;
#ifdef ENABLE_QUIC #ifdef ENABLE_QUIC
if(conn->transport == TRNSPRT_QUIC) { if(conn->transport == TRNSPRT_QUIC) {
result = Curl_quic_is_connected(conn, i, connected); result = Curl_quic_is_connected(conn, i, connected);
if(result) { if(!result && *connected) {
error = SOCKERRNO;
goto error;
}
if(*connected) {
/* use this socket from now on */ /* use this socket from now on */
conn->sock[sockindex] = conn->tempsock[i]; conn->sock[sockindex] = conn->tempsock[i];
conn->ip_addr = conn->tempaddr[i]; conn->ip_addr = conn->tempaddr[i];
conn->tempsock[i] = CURL_SOCKET_BAD; conn->tempsock[i] = CURL_SOCKET_BAD;
post_SOCKS(conn, sockindex, connected); post_SOCKS(conn, sockindex, connected);
connkeep(conn, "HTTP/3 default"); connkeep(conn, "HTTP/3 default");
return result; return CURLE_OK;
} }
/* should we try another protocol family? */ if(result)
if(i == 0 && !conn->bits.parallel_connect && error = SOCKERRNO;
(Curl_timediff(now, conn->connecttime) >=
data->set.happy_eyeballs_timeout)) {
conn->bits.parallel_connect = TRUE; /* starting now */
trynextip(conn, sockindex, 1);
}
continue;
} }
else
#endif #endif
{
#ifdef mpeix #ifdef mpeix
/* Call this function once now, and ignore the results. We do this to /* 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 "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. */ reliably. This is reported necessary on the MPE/iX operating
(void)verifyconnect(conn->tempsock[i], NULL); system. */
(void)verifyconnect(conn->tempsock[i], NULL);
#endif #endif
/* check socket for connect */ /* check socket for connect */
rc = SOCKET_WRITABLE(conn->tempsock[i], 0); rc = SOCKET_WRITABLE(conn->tempsock[i], 0);
}
if(rc == 0) { /* no connection yet */ if(rc == 0) { /* no connection yet */
error = 0; if(Curl_timediff(now, conn->connecttime) >=
if(Curl_timediff(now, conn->connecttime) >= conn->timeoutms_per_addr) { conn->timeoutms_per_addr[i]) {
infof(data, "After %" CURL_FORMAT_TIMEDIFF_T infof(data, "After %" CURL_FORMAT_TIMEDIFF_T
"ms connect time, move on!\n", conn->timeoutms_per_addr); "ms connect time, move on!\n", conn->timeoutms_per_addr[i]);
error = ETIMEDOUT; error = ETIMEDOUT;
} }
@ -946,9 +939,6 @@ CURLcode Curl_is_connected(struct connectdata *conn,
else if(rc & CURL_CSELECT_ERR) else if(rc & CURL_CSELECT_ERR)
(void)verifyconnect(conn->tempsock[i], &error); (void)verifyconnect(conn->tempsock[i], &error);
#ifdef ENABLE_QUIC
error:
#endif
/* /*
* The connection failed here, we should attempt to connect to the "next * The connection failed here, we should attempt to connect to the "next
* address" for the given host. But first remember the latest error. * address" for the given host. But first remember the latest error.
@ -968,7 +958,7 @@ CURLcode Curl_is_connected(struct connectdata *conn,
Curl_strerror(error, buffer, sizeof(buffer))); Curl_strerror(error, buffer, sizeof(buffer)));
#endif #endif
conn->timeoutms_per_addr = conn->tempaddr[i]->ai_next == NULL ? conn->timeoutms_per_addr[i] = conn->tempaddr[i]->ai_next == NULL ?
allow : allow / 2; allow : allow / 2;
ainext(conn, i, TRUE); ainext(conn, i, TRUE);
status = trynextip(conn, sockindex, i); status = trynextip(conn, sockindex, i);
@ -980,13 +970,15 @@ CURLcode Curl_is_connected(struct connectdata *conn,
} }
} }
if(result) { if(result &&
(conn->tempsock[0] == CURL_SOCKET_BAD) &&
(conn->tempsock[1] == CURL_SOCKET_BAD)) {
/* no more addresses to try */ /* no more addresses to try */
const char *hostname; const char *hostname;
char buffer[STRERROR_LEN]; char buffer[STRERROR_LEN];
/* if the first address family runs out of addresses to try before /* if the first address family runs out of addresses to try before the
the happy eyeball timeout, go ahead and try the next family now */ happy eyeball timeout, go ahead and try the next family now */
result = trynextip(conn, sockindex, 1); result = trynextip(conn, sockindex, 1);
if(!result) if(!result)
return result; return result;
@ -1007,6 +999,9 @@ CURLcode Curl_is_connected(struct connectdata *conn,
hostname, conn->port, hostname, conn->port,
Curl_strerror(error, buffer, sizeof(buffer))); Curl_strerror(error, buffer, sizeof(buffer)));
Curl_quic_disconnect(conn, 0);
Curl_quic_disconnect(conn, 1);
#ifdef WSAETIMEDOUT #ifdef WSAETIMEDOUT
if(WSAETIMEDOUT == data->state.os_errno) if(WSAETIMEDOUT == data->state.os_errno)
result = CURLE_OPERATION_TIMEDOUT; result = CURLE_OPERATION_TIMEDOUT;
@ -1015,6 +1010,8 @@ CURLcode Curl_is_connected(struct connectdata *conn,
result = CURLE_OPERATION_TIMEDOUT; result = CURLE_OPERATION_TIMEDOUT;
#endif #endif
} }
else
result = CURLE_OK; /* still trying */
return result; return result;
} }
@ -1206,8 +1203,10 @@ static CURLcode singleipconnect(struct connectdata *conn,
(void)curlx_nonblock(sockfd, TRUE); (void)curlx_nonblock(sockfd, TRUE);
conn->connecttime = Curl_now(); conn->connecttime = Curl_now();
if(conn->num_addr > 1) if(conn->num_addr > 1) {
Curl_expire(data, conn->timeoutms_per_addr, EXPIRE_DNS_PER_NAME); Curl_expire(data, conn->timeoutms_per_addr[0], EXPIRE_DNS_PER_NAME);
Curl_expire(data, conn->timeoutms_per_addr[1], EXPIRE_DNS_PER_NAME2);
}
/* Connect TCP and QUIC sockets */ /* Connect TCP and QUIC sockets */
if(!isconnected && (conn->transport != TRNSPRT_UDP)) { if(!isconnected && (conn->transport != TRNSPRT_UDP)) {
@ -1330,8 +1329,10 @@ CURLcode Curl_connecthost(struct connectdata *conn, /* context */
conn->tempsock[0] = conn->tempsock[1] = CURL_SOCKET_BAD; conn->tempsock[0] = conn->tempsock[1] = CURL_SOCKET_BAD;
/* Max time for the next connection attempt */ /* Max time for the next connection attempt */
conn->timeoutms_per_addr = conn->timeoutms_per_addr[0] =
conn->tempaddr[0]->ai_next == NULL ? timeout_ms : timeout_ms / 2; conn->tempaddr[0]->ai_next == NULL ? timeout_ms : timeout_ms / 2;
conn->timeoutms_per_addr[1] =
conn->tempaddr[1]->ai_next == NULL ? timeout_ms : timeout_ms / 2;
conn->tempfamily[0] = conn->tempaddr[0]? conn->tempfamily[0] = conn->tempaddr[0]?
conn->tempaddr[0]->ai_family:0; conn->tempaddr[0]->ai_family:0;

View File

@ -7,7 +7,7 @@
* | (__| |_| | _ <| |___ * | (__| |_| | _ <| |___
* \___|\___/|_| \_\_____| * \___|\___/|_| \_\_____|
* *
* Copyright (C) 1998 - 2019, Daniel Stenberg, <daniel@haxx.se>, et al. * Copyright (C) 1998 - 2020, Daniel Stenberg, <daniel@haxx.se>, et al.
* *
* This software is licensed as described in the file COPYING, which * This software is licensed as described in the file COPYING, which
* you should have received as part of this distribution. The terms * you should have received as part of this distribution. The terms
@ -47,11 +47,13 @@ int Curl_quic_ver(char *p, size_t len);
CURLcode Curl_quic_done_sending(struct connectdata *conn); CURLcode Curl_quic_done_sending(struct connectdata *conn);
void Curl_quic_done(struct Curl_easy *data, bool premature); void Curl_quic_done(struct Curl_easy *data, bool premature);
bool Curl_quic_data_pending(const struct Curl_easy *data); bool Curl_quic_data_pending(const struct Curl_easy *data);
void Curl_quic_disconnect(struct connectdata *conn, int tempindex);
#else /* ENABLE_QUIC */ #else /* ENABLE_QUIC */
#define Curl_quic_done_sending(x) #define Curl_quic_done_sending(x)
#define Curl_quic_done(x,y) #define Curl_quic_done(x,y)
#define Curl_quic_data_pending(x) #define Curl_quic_data_pending(x)
#define Curl_quic_disconnect(x,y)
#endif /* !ENABLE_QUIC */ #endif /* !ENABLE_QUIC */
#endif /* HEADER_CURL_QUIC_H */ #endif /* HEADER_CURL_QUIC_H */

View File

@ -981,8 +981,10 @@ struct connectdata {
struct curltime connecttime; struct curltime connecttime;
/* The two fields below get set in Curl_connecthost */ /* The two fields below get set in Curl_connecthost */
int num_addr; /* number of addresses to try to connect to */ int num_addr; /* number of addresses to try to connect to */
timediff_t timeoutms_per_addr; /* how long time in milliseconds to spend on
trying to connect to each IP address */ /* how long time in milliseconds to spend on trying to connect to each IP
address, per family */
timediff_t timeoutms_per_addr[2];
const struct Curl_handler *handler; /* Connection's protocol handler */ const struct Curl_handler *handler; /* Connection's protocol handler */
const struct Curl_handler *given; /* The protocol first given */ const struct Curl_handler *given; /* The protocol first given */
@ -1245,7 +1247,8 @@ typedef enum {
EXPIRE_100_TIMEOUT, EXPIRE_100_TIMEOUT,
EXPIRE_ASYNC_NAME, EXPIRE_ASYNC_NAME,
EXPIRE_CONNECTTIMEOUT, EXPIRE_CONNECTTIMEOUT,
EXPIRE_DNS_PER_NAME, EXPIRE_DNS_PER_NAME, /* family1 */
EXPIRE_DNS_PER_NAME2, /* family2 */
EXPIRE_HAPPY_EYEBALLS_DNS, /* See asyn-ares.c */ EXPIRE_HAPPY_EYEBALLS_DNS, /* See asyn-ares.c */
EXPIRE_HAPPY_EYEBALLS, EXPIRE_HAPPY_EYEBALLS,
EXPIRE_MULTI_PENDING, EXPIRE_MULTI_PENDING,

View File

@ -256,11 +256,11 @@ static int quic_set_encryption_secrets(SSL *ssl,
int level = quic_from_ossl_level(ossl_level); int level = quic_from_ossl_level(ossl_level);
if(ngtcp2_crypto_derive_and_install_rx_key( if(ngtcp2_crypto_derive_and_install_rx_key(
qs->qconn, NULL, NULL, NULL, level, rx_secret, secretlen) != 0) qs->qconn, NULL, NULL, NULL, level, rx_secret, secretlen) != 0)
return 0; return 0;
if(ngtcp2_crypto_derive_and_install_tx_key( if(ngtcp2_crypto_derive_and_install_tx_key(
qs->qconn, NULL, NULL, NULL, level, tx_secret, secretlen) != 0) qs->qconn, NULL, NULL, NULL, level, tx_secret, secretlen) != 0)
return 0; return 0;
if(level == NGTCP2_CRYPTO_LEVEL_APP) { if(level == NGTCP2_CRYPTO_LEVEL_APP) {
@ -341,9 +341,7 @@ static int quic_init_ssl(struct quicsocket *qs)
/* this will need some attention when HTTPS proxy over QUIC get fixed */ /* this will need some attention when HTTPS proxy over QUIC get fixed */
const char * const hostname = qs->conn->host.name; const char * const hostname = qs->conn->host.name;
if(qs->ssl) DEBUGASSERT(!qs->ssl);
SSL_free(qs->ssl);
qs->ssl = SSL_new(qs->sslctx); qs->ssl = SSL_new(qs->sslctx);
SSL_set_app_data(qs->ssl, qs); SSL_set_app_data(qs->ssl, qs);
@ -375,11 +373,11 @@ static int secret_func(gnutls_session_t ssl,
if(level != NGTCP2_CRYPTO_LEVEL_EARLY && if(level != NGTCP2_CRYPTO_LEVEL_EARLY &&
ngtcp2_crypto_derive_and_install_rx_key( ngtcp2_crypto_derive_and_install_rx_key(
qs->qconn, NULL, NULL, NULL, level, rx_secret, secretlen) != 0) qs->qconn, NULL, NULL, NULL, level, rx_secret, secretlen) != 0)
return 0; return 0;
if(ngtcp2_crypto_derive_and_install_tx_key( if(ngtcp2_crypto_derive_and_install_tx_key(
qs->qconn, NULL, NULL, NULL, level, tx_secret, secretlen) != 0) qs->qconn, NULL, NULL, NULL, level, tx_secret, secretlen) != 0)
return 0; return 0;
if(level == NGTCP2_CRYPTO_LEVEL_APP) { if(level == NGTCP2_CRYPTO_LEVEL_APP) {
@ -429,8 +427,8 @@ static int tp_recv_func(gnutls_session_t ssl, const uint8_t *data,
ngtcp2_transport_params params; ngtcp2_transport_params params;
if(ngtcp2_decode_transport_params( if(ngtcp2_decode_transport_params(
&params, NGTCP2_TRANSPORT_PARAMS_TYPE_ENCRYPTED_EXTENSIONS, &params, NGTCP2_TRANSPORT_PARAMS_TYPE_ENCRYPTED_EXTENSIONS,
data, data_size) != 0) data, data_size) != 0)
return -1; return -1;
if(ngtcp2_conn_set_remote_transport_params(qs->qconn, &params) != 0) if(ngtcp2_conn_set_remote_transport_params(qs->qconn, &params) != 0)
@ -471,8 +469,7 @@ static int quic_init_ssl(struct quicsocket *qs)
const char * const hostname = qs->conn->host.name; const char * const hostname = qs->conn->host.name;
int rc; int rc;
if(qs->ssl) DEBUGASSERT(!qs->ssl);
gnutls_deinit(qs->ssl);
gnutls_init(&qs->ssl, GNUTLS_CLIENT); gnutls_init(&qs->ssl, GNUTLS_CLIENT);
gnutls_session_set_ptr(qs->ssl, qs); gnutls_session_set_ptr(qs->ssl, qs);
@ -782,6 +779,8 @@ CURLcode Curl_quic_connect(struct connectdata *conn,
long port; long port;
int qfd; int qfd;
if(qs->conn)
Curl_quic_disconnect(conn, sockindex);
qs->conn = conn; qs->conn = conn;
/* extract the used address as a string */ /* extract the used address as a string */
@ -880,11 +879,16 @@ static int ng_perform_getsock(const struct connectdata *conn,
return ng_getsock((struct connectdata *)conn, socks); return ng_getsock((struct connectdata *)conn, socks);
} }
static CURLcode qs_disconnect(struct quicsocket *qs) static void qs_disconnect(struct quicsocket *qs)
{ {
int i; int i;
if(qs->qlogfd != -1) if(!qs->conn) /* already closed */
return;
qs->conn = NULL;
if(qs->qlogfd != -1) {
close(qs->qlogfd); close(qs->qlogfd);
qs->qlogfd = -1;
}
if(qs->ssl) if(qs->ssl)
#ifdef USE_OPENSSL #ifdef USE_OPENSSL
SSL_free(qs->ssl); SSL_free(qs->ssl);
@ -903,14 +907,22 @@ static CURLcode qs_disconnect(struct quicsocket *qs)
#ifdef USE_OPENSSL #ifdef USE_OPENSSL
SSL_CTX_free(qs->sslctx); SSL_CTX_free(qs->sslctx);
#endif #endif
return CURLE_OK; }
void Curl_quic_disconnect(struct connectdata *conn,
int tempindex)
{
if(conn->transport == TRNSPRT_QUIC)
qs_disconnect(&conn->hequic[tempindex]);
} }
static CURLcode ng_disconnect(struct connectdata *conn, static CURLcode ng_disconnect(struct connectdata *conn,
bool dead_connection) bool dead_connection)
{ {
(void)dead_connection; (void)dead_connection;
return qs_disconnect(&conn->hequic[0]); Curl_quic_disconnect(conn, 0);
Curl_quic_disconnect(conn, 1);
return CURLE_OK;
} }
static unsigned int ng_conncheck(struct connectdata *conn, static unsigned int ng_conncheck(struct connectdata *conn,

View File

@ -107,6 +107,14 @@ static CURLcode quiche_disconnect(struct connectdata *conn,
(void)dead_connection; (void)dead_connection;
return qs_disconnect(qs); return qs_disconnect(qs);
} }
void Curl_quic_disconnect(struct connectdata *conn,
int tempindex)
{
if(conn->transport == TRNSPRT_QUIC)
qs_disconnect(&conn->hequic[tempindex]);
}
static unsigned int quiche_conncheck(struct connectdata *conn, static unsigned int quiche_conncheck(struct connectdata *conn,
unsigned int checks_to_perform) unsigned int checks_to_perform)
{ {