connection: shutdown TLS (for FTP) better

This adds connection shutdown infrastructure and first use for FTP. FTP
data connections, when not encountering an error, are now shut down in a
blocking way with a 2sec timeout.

    - add cfilter `Curl_cft_shutdown` callback
    - keep a shutdown start timestamp and timeout at connectdata
    - provide shutdown timeout default and member in
      `data->set.shutdowntimeout`.
    - provide methods for starting, interrogating and clearing
      shutdown timers
    - provide `Curl_conn_shutdown_blocking()` to shutdown the
      `sockindex` filter chain in a blocking way. Use that in FTP.
    - add `Curl_conn_cf_poll()` to wait for socket events during
      shutdown of a connection filter chain.
      This gets the monitoring sockets and events via the filters
      "adjust_pollset()" methods. This gives correct behaviour when
      shutting down a TLS connection through a HTTP/2 proxy.
    - Implement shutdown for all socket filters
      - for HTTP/2 and h2 proxying to send GOAWAY
      - for TLS backends to the best of their capabilities
      - for tcp socket filter to make a final, nonblocking
        receive to avoid unwanted RST states
    - add shutdown forwarding to happy eyeballers and
      https connect ballers when applicable.

Closes #13904
This commit is contained in:
Stefan Eissing 2024-06-07 10:12:39 +02:00 committed by Daniel Stenberg
parent 7d934267ab
commit c31041b17e
No known key found for this signature in database
GPG key ID: 5CC908FDB71E12C2
33 changed files with 1161 additions and 465 deletions

View file

@ -142,6 +142,43 @@ timediff_t Curl_timeleft(struct Curl_easy *data,
return (ctimeleft_ms < timeleft_ms)? ctimeleft_ms : timeleft_ms;
}
void Curl_shutdown_start(struct Curl_easy *data, int sockindex,
struct curltime *nowp)
{
struct curltime now;
DEBUGASSERT(data->conn);
if(!nowp) {
now = Curl_now();
nowp = &now;
}
data->conn->shutdown.start[sockindex] = *nowp;
data->conn->shutdown.timeout_ms = (data->set.shutdowntimeout > 0) ?
data->set.shutdowntimeout : DEFAULT_SHUTDOWN_TIMEOUT_MS;
}
timediff_t Curl_shutdown_timeleft(struct connectdata *conn, int sockindex,
struct curltime *nowp)
{
struct curltime now;
if(!conn->shutdown.start[sockindex].tv_sec || !conn->shutdown.timeout_ms)
return 0; /* not started or no limits */
if(!nowp) {
now = Curl_now();
nowp = &now;
}
return conn->shutdown.timeout_ms -
Curl_timediff(*nowp, conn->shutdown.start[sockindex]);
}
void Curl_shutdown_clear(struct Curl_easy *data, int sockindex)
{
struct curltime *pt = &data->conn->shutdown.start[sockindex];
memset(pt, 0, sizeof(*pt));
}
/* Copies connection info into the transfer handle to make it available when
the transfer handle is no longer associated with the connection. */
void Curl_persistconninfo(struct Curl_easy *data, struct connectdata *conn,
@ -358,6 +395,7 @@ struct eyeballer {
BIT(has_started); /* attempts have started */
BIT(is_done); /* out of addresses/time */
BIT(connected); /* cf has connected */
BIT(shutdown); /* cf has shutdown */
BIT(inconclusive); /* connect was not a hard failure, we
* might talk to a restarting server */
};
@ -857,6 +895,46 @@ static void cf_he_ctx_clear(struct Curl_cfilter *cf, struct Curl_easy *data)
ctx->winner = NULL;
}
static CURLcode cf_he_shutdown(struct Curl_cfilter *cf,
struct Curl_easy *data, bool *done)
{
struct cf_he_ctx *ctx = cf->ctx;
size_t i;
CURLcode result = CURLE_OK;
DEBUGASSERT(data);
if(cf->connected) {
*done = TRUE;
return CURLE_OK;
}
/* shutdown all ballers that have not done so already. If one fails,
* continue shutting down others until all are shutdown. */
for(i = 0; i < ARRAYSIZE(ctx->baller); i++) {
struct eyeballer *baller = ctx->baller[i];
bool bdone = FALSE;
if(!baller || !baller->cf || baller->shutdown)
continue;
baller->result = baller->cf->cft->do_shutdown(baller->cf, data, &bdone);
if(baller->result || bdone)
baller->shutdown = TRUE; /* treat a failed shutdown as done */
}
*done = TRUE;
for(i = 0; i < ARRAYSIZE(ctx->baller); i++) {
if(ctx->baller[i] && !ctx->baller[i]->shutdown)
*done = FALSE;
}
if(*done) {
for(i = 0; i < ARRAYSIZE(ctx->baller); i++) {
if(ctx->baller[i] && ctx->baller[i]->result)
result = ctx->baller[i]->result;
}
}
CURL_TRC_CF(data, cf, "shutdown -> %d, done=%d", result, *done);
return result;
}
static void cf_he_adjust_pollset(struct Curl_cfilter *cf,
struct Curl_easy *data,
struct easy_pollset *ps)
@ -1052,6 +1130,7 @@ struct Curl_cftype Curl_cft_happy_eyeballs = {
cf_he_destroy,
cf_he_connect,
cf_he_close,
cf_he_shutdown,
Curl_cf_def_get_host,
cf_he_adjust_pollset,
cf_he_data_pending,
@ -1316,6 +1395,7 @@ struct Curl_cftype Curl_cft_setup = {
cf_setup_destroy,
cf_setup_connect,
cf_setup_close,
Curl_cf_def_shutdown,
Curl_cf_def_get_host,
Curl_cf_def_adjust_pollset,
Curl_cf_def_data_pending,