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

@ -181,7 +181,9 @@ struct cf_h2_proxy_ctx {
int32_t goaway_error;
int32_t last_stream_id;
BIT(conn_closed);
BIT(goaway);
BIT(rcvd_goaway);
BIT(sent_goaway);
BIT(shutdown);
BIT(nw_out_blocked);
};
@ -694,7 +696,7 @@ static int proxy_h2_on_frame_recv(nghttp2_session *session,
}
break;
case NGHTTP2_GOAWAY:
ctx->goaway = TRUE;
ctx->rcvd_goaway = TRUE;
break;
default:
break;
@ -1166,6 +1168,42 @@ static void cf_h2_proxy_destroy(struct Curl_cfilter *cf,
}
}
static CURLcode cf_h2_proxy_shutdown(struct Curl_cfilter *cf,
struct Curl_easy *data, bool *done)
{
struct cf_h2_proxy_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,
0, 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 = proxy_h2_progress_egress(cf, data);
if(!result && nghttp2_session_want_read(ctx->h2))
result = proxy_h2_progress_ingress(cf, data);
*done = !result && !nghttp2_session_want_write(ctx->h2) &&
!nghttp2_session_want_read(ctx->h2);
ctx->shutdown = (result || *done);
return result;
}
static bool cf_h2_proxy_data_pending(struct Curl_cfilter *cf,
const struct Curl_easy *data)
{
@ -1182,12 +1220,12 @@ static void cf_h2_proxy_adjust_pollset(struct Curl_cfilter *cf,
struct easy_pollset *ps)
{
struct cf_h2_proxy_ctx *ctx = cf->ctx;
struct cf_call_data save;
curl_socket_t sock = Curl_conn_cf_get_socket(cf, data);
bool want_recv, want_send;
Curl_pollset_check(data, ps, sock, &want_recv, &want_send);
if(ctx->h2 && (want_recv || want_send)) {
struct cf_call_data save;
bool c_exhaust, s_exhaust;
CF_DATA_SAVE(save, cf, data);
@ -1202,6 +1240,14 @@ static void cf_h2_proxy_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 ssize_t h2_handle_tunnel_close(struct Curl_cfilter *cf,
@ -1259,7 +1305,8 @@ static ssize_t tunnel_recv(struct Curl_cfilter *cf, struct Curl_easy *data,
}
else if(ctx->tunnel.reset ||
(ctx->conn_closed && Curl_bufq_is_empty(&ctx->inbufq)) ||
(ctx->goaway && ctx->last_stream_id < ctx->tunnel.stream_id)) {
(ctx->rcvd_goaway &&
ctx->last_stream_id < ctx->tunnel.stream_id)) {
*err = CURLE_RECV_ERROR;
nread = -1;
}
@ -1537,6 +1584,7 @@ struct Curl_cftype Curl_cft_h2_proxy = {
cf_h2_proxy_destroy,
cf_h2_proxy_connect,
cf_h2_proxy_close,
cf_h2_proxy_shutdown,
Curl_cf_http_proxy_get_host,
cf_h2_proxy_adjust_pollset,
cf_h2_proxy_data_pending,