Skip to content

Commit

Permalink
QUIC eyeballers connect retries
Browse files Browse the repository at this point in the history
- 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 curl#11832 where connects were retried indefinitely until
  the overall timeout fired
  • Loading branch information
icing committed Nov 24, 2023
1 parent 03cb1ff commit 58e1e3b
Show file tree
Hide file tree
Showing 3 changed files with 24 additions and 43 deletions.
19 changes: 18 additions & 1 deletion lib/connect.c
Expand Up @@ -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 */
Expand All @@ -362,9 +363,12 @@ struct eyeballer {
expire_id timeout_id; /* ID for Curl_expire() */
CURLcode result;
int error;
int rewinds; /* how often 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 */
};


Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -441,6 +445,13 @@ static void baller_free(struct eyeballer *baller,
}
}

static void baller_rewind(struct eyeballer *baller)
{
++baller->rewinds;
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);
Expand Down Expand Up @@ -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->rewinds == 0)
baller_rewind(baller);
baller_start(cf, data, baller, timeoutms);
}
else {
Expand Down Expand Up @@ -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;
}
Expand Down
24 changes: 3 additions & 21 deletions lib/vquic/curl_ngtcp2.c
Expand Up @@ -2617,27 +2617,9 @@ static CURLcode cf_ngtcp2_connect(struct Curl_cfilter *cf,
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
Expand Down
24 changes: 3 additions & 21 deletions lib/vquic/curl_quiche.c
Expand Up @@ -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:
Expand Down

0 comments on commit 58e1e3b

Please sign in to comment.