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

@ -133,11 +133,14 @@ struct cf_h2_ctx {
struct Curl_hash streams; /* hash of `data->id` to `h2_stream_ctx` */
size_t drain_total; /* sum of all stream's UrlState drain */
uint32_t max_concurrent_streams;
uint32_t goaway_error;
int32_t last_stream_id;
uint32_t goaway_error; /* goaway error code from server */
int32_t remote_max_sid; /* max id processed by server */
int32_t local_max_sid; /* max id processed by us */
BIT(conn_closed);
BIT(goaway);
BIT(rcvd_goaway);
BIT(sent_goaway);
BIT(enable_push);
BIT(shutdown);
BIT(nw_out_blocked);
};
@ -437,7 +440,7 @@ static CURLcode cf_h2_ctx_init(struct Curl_cfilter *cf,
Curl_bufq_initp(&ctx->outbufq, &ctx->stream_bufcp, H2_NW_SEND_CHUNKS, 0);
Curl_dyn_init(&ctx->scratch, CURL_MAX_HTTP_HEADER);
Curl_hash_offt_init(&ctx->streams, 63, h2_stream_hash_free);
ctx->last_stream_id = 2147483647;
ctx->remote_max_sid = 2147483647;
rc = nghttp2_session_callbacks_new(&cbs);
if(rc) {
@ -967,6 +970,10 @@ static int push_promise(struct Curl_cfilter *cf,
rv = CURL_PUSH_DENY;
goto fail;
}
/* success, remember max stream id processed */
if(newstream->id > ctx->local_max_sid)
ctx->local_max_sid = newstream->id;
}
else {
CURL_TRC_CF(data, cf, "Got PUSH_PROMISE, ignore it");
@ -1252,12 +1259,12 @@ static int on_frame_recv(nghttp2_session *session, const nghttp2_frame *frame,
break;
}
case NGHTTP2_GOAWAY:
ctx->goaway = TRUE;
ctx->rcvd_goaway = TRUE;
ctx->goaway_error = frame->goaway.error_code;
ctx->last_stream_id = frame->goaway.last_stream_id;
ctx->remote_max_sid = frame->goaway.last_stream_id;
if(data) {
infof(data, "received GOAWAY, error=%u, last_stream=%u",
ctx->goaway_error, ctx->last_stream_id);
ctx->goaway_error, ctx->remote_max_sid);
Curl_multi_connchanged(data->multi);
}
break;
@ -1878,7 +1885,7 @@ static ssize_t stream_recv(struct Curl_cfilter *cf, struct Curl_easy *data,
}
else if(stream->reset ||
(ctx->conn_closed && Curl_bufq_is_empty(&ctx->inbufq)) ||
(ctx->goaway && ctx->last_stream_id < stream->id)) {
(ctx->rcvd_goaway && ctx->remote_max_sid < stream->id)) {
CURL_TRC_CF(data, cf, "[%d] returning ERR", stream->id);
*err = data->req.bytecount? CURLE_PARTIAL_FILE : CURLE_HTTP2;
nread = -1;
@ -2358,6 +2365,7 @@ static void cf_h2_adjust_pollset(struct Curl_cfilter *cf,
struct easy_pollset *ps)
{
struct cf_h2_ctx *ctx = cf->ctx;
struct cf_call_data save;
curl_socket_t sock;
bool want_recv, want_send;
@ -2368,7 +2376,6 @@ static void cf_h2_adjust_pollset(struct Curl_cfilter *cf,
Curl_pollset_check(data, ps, sock, &want_recv, &want_send);
if(want_recv || want_send) {
struct h2_stream_ctx *stream = H2_STREAM_CTX(ctx, data);
struct cf_call_data save;
bool c_exhaust, s_exhaust;
CF_DATA_SAVE(save, cf, data);
@ -2383,6 +2390,14 @@ static void cf_h2_adjust_pollset(struct Curl_cfilter *cf,
Curl_pollset_set(data, ps, sock, want_recv, want_send);
CF_DATA_RESTORE(cf, save);
}
else if(ctx->sent_goaway && !ctx->shutdown) {
/* shutdown in progress */
CF_DATA_SAVE(save, cf, data);
want_send = nghttp2_session_want_write(ctx->h2);
want_recv = nghttp2_session_want_read(ctx->h2);
Curl_pollset_set(data, ps, sock, want_recv, want_send);
CF_DATA_RESTORE(cf, save);
}
}
static CURLcode cf_h2_connect(struct Curl_cfilter *cf,
@ -2446,6 +2461,7 @@ static void cf_h2_close(struct Curl_cfilter *cf, struct Curl_easy *data)
CF_DATA_SAVE(save, cf, data);
cf_h2_ctx_clear(ctx);
CF_DATA_RESTORE(cf, save);
cf->connected = FALSE;
}
if(cf->next)
cf->next->cft->do_close(cf->next, data);
@ -2462,6 +2478,42 @@ static void cf_h2_destroy(struct Curl_cfilter *cf, struct Curl_easy *data)
}
}
static CURLcode cf_h2_shutdown(struct Curl_cfilter *cf,
struct Curl_easy *data, bool *done)
{
struct cf_h2_ctx *ctx = cf->ctx;
CURLcode result;
int rv;
if(!cf->connected || !ctx->h2 || ctx->shutdown) {
*done = TRUE;
return CURLE_OK;
}
if(!ctx->sent_goaway) {
rv = nghttp2_submit_goaway(ctx->h2, NGHTTP2_FLAG_NONE,
ctx->local_max_sid, 0,
(const uint8_t *)"shutown", sizeof("shutown"));
if(rv) {
failf(data, "nghttp2_submit_goaway() failed: %s(%d)",
nghttp2_strerror(rv), rv);
return CURLE_SEND_ERROR;
}
ctx->sent_goaway = TRUE;
}
/* GOAWAY submitted, process egress and ingress until nghttp2 is done. */
result = CURLE_OK;
if(nghttp2_session_want_write(ctx->h2))
result = h2_progress_egress(cf, data);
if(!result && nghttp2_session_want_read(ctx->h2))
result = h2_progress_ingress(cf, data, 0);
*done = !result && !nghttp2_session_want_write(ctx->h2) &&
!nghttp2_session_want_read(ctx->h2);
ctx->shutdown = (result || *done);
return result;
}
static CURLcode http2_data_pause(struct Curl_cfilter *cf,
struct Curl_easy *data,
bool pause)
@ -2632,6 +2684,7 @@ struct Curl_cftype Curl_cft_nghttp2 = {
cf_h2_destroy,
cf_h2_connect,
cf_h2_close,
cf_h2_shutdown,
Curl_cf_def_get_host,
cf_h2_adjust_pollset,
cf_h2_data_pending,