quic: make eyeballers connect retries stop at weird replies

- when a connect immediately goes into DRAINING state, do
  not attempt retries in the QUIC connection filter. Instead,
  return CURLE_WEIRD_SERVER_REPLY
- When eyeballing, interpret CURLE_WEIRD_SERVER_REPLY as an
  inconclusive answer. When all addresses have been attempted,
  rewind the address list once on an inconclusive answer.
- refs #11832 where connects were retried indefinitely until
  the overall timeout fired

Closes #12400
This commit is contained in:
Stefan Eissing 2023-11-24 13:45:55 +01:00 committed by Daniel Stenberg
parent e7112a726b
commit 247defa753
No known key found for this signature in database
GPG Key ID: 5CC908FDB71E12C2
3 changed files with 24 additions and 43 deletions

View File

@ -351,6 +351,7 @@ void Curl_conncontrol(struct connectdata *conn,
*/
struct eyeballer {
const char *name;
const struct Curl_addrinfo *first; /* complete address list, not owned */
const struct Curl_addrinfo *addr; /* List of addresses to try, not owned */
int ai_family; /* matching address family only */
cf_ip_connect_create *cf_create; /* for creating cf */
@ -362,9 +363,12 @@ struct eyeballer {
expire_id timeout_id; /* ID for Curl_expire() */
CURLcode result;
int error;
BIT(rewinded); /* if we rewinded the addr list */
BIT(has_started); /* attempts have started */
BIT(is_done); /* out of addresses/time */
BIT(connected); /* cf has connected */
BIT(inconclusive); /* connect was not a hard failure, we
* might talk to a restarting server */
};
@ -411,7 +415,7 @@ static CURLcode eyeballer_new(struct eyeballer **pballer,
#endif
"ip"));
baller->cf_create = cf_create;
baller->addr = addr;
baller->first = baller->addr = addr;
baller->ai_family = ai_family;
baller->primary = primary;
baller->delay_ms = delay_ms;
@ -441,6 +445,13 @@ static void baller_free(struct eyeballer *baller,
}
}
static void baller_rewind(struct eyeballer *baller)
{
baller->rewinded = TRUE;
baller->addr = baller->first;
baller->inconclusive = FALSE;
}
static void baller_next_addr(struct eyeballer *baller)
{
baller->addr = addr_next_match(baller->addr, baller->ai_family);
@ -531,6 +542,10 @@ static CURLcode baller_start_next(struct Curl_cfilter *cf,
{
if(cf->sockindex == FIRSTSOCKET) {
baller_next_addr(baller);
/* If we get inconclusive answers from the server(s), we make
* a second iteration over the address list */
if(!baller->addr && baller->inconclusive && !baller->rewinded)
baller_rewind(baller);
baller_start(cf, data, baller, timeoutms);
}
else {
@ -569,6 +584,8 @@ static CURLcode baller_connect(struct Curl_cfilter *cf,
baller->result = CURLE_OPERATION_TIMEDOUT;
}
}
else if(baller->result == CURLE_WEIRD_SERVER_REPLY)
baller->inconclusive = TRUE;
}
return baller->result;
}

View File

@ -2617,27 +2617,9 @@ out:
ngtcp2_conn_in_draining_period(ctx->qconn)) {
/* When a QUIC server instance is shutting down, it may send us a
* CONNECTION_CLOSE right away. Our connection then enters the DRAINING
* state.
* This may be a stopping of the service or it may be that the server
* is reloading and a new instance will start serving soon.
* In any case, we tear down our socket and start over with a new one.
* We re-open the underlying UDP cf right now, but do not start
* connecting until called again.
*/
int reconn_delay_ms = 200;
CURL_TRC_CF(data, cf, "connect, remote closed, reconnect after %dms",
reconn_delay_ms);
Curl_conn_cf_close(cf->next, data);
cf_ngtcp2_ctx_clear(ctx);
result = Curl_conn_cf_connect(cf->next, data, FALSE, done);
if(!result && *done) {
*done = FALSE;
ctx->reconnect_at = now;
ctx->reconnect_at.tv_usec += reconn_delay_ms * 1000;
Curl_expire(data, reconn_delay_ms, EXPIRE_QUIC);
result = CURLE_OK;
}
* state. The CONNECT may work in the near future again. Indicate
* that as a "weird" reply. */
result = CURLE_WEIRD_SERVER_REPLY;
}
#ifndef CURL_DISABLE_VERBOSE_STRINGS

View File

@ -1501,27 +1501,9 @@ static CURLcode cf_quiche_connect(struct Curl_cfilter *cf,
else if(quiche_conn_is_draining(ctx->qconn)) {
/* When a QUIC server instance is shutting down, it may send us a
* CONNECTION_CLOSE right away. Our connection then enters the DRAINING
* state.
* This may be a stopping of the service or it may be that the server
* is reloading and a new instance will start serving soon.
* In any case, we tear down our socket and start over with a new one.
* We re-open the underlying UDP cf right now, but do not start
* connecting until called again.
*/
int reconn_delay_ms = 200;
CURL_TRC_CF(data, cf, "connect, remote closed, reconnect after %dms",
reconn_delay_ms);
Curl_conn_cf_close(cf->next, data);
cf_quiche_ctx_clear(ctx);
result = Curl_conn_cf_connect(cf->next, data, FALSE, done);
if(!result && *done) {
*done = FALSE;
ctx->reconnect_at = Curl_now();
ctx->reconnect_at.tv_usec += reconn_delay_ms * 1000;
Curl_expire(data, reconn_delay_ms, EXPIRE_QUIC);
result = CURLE_OK;
}
* state. The CONNECT may work in the near future again. Indicate
* that as a "weird" reply. */
result = CURLE_WEIRD_SERVER_REPLY;
}
out: