From ca0486238adf009b26f885c9098dced9812aebd1 Mon Sep 17 00:00:00 2001 From: Matus Fabian Date: Mon, 8 Sep 2025 13:08:51 -0400 Subject: [PATCH] http: add half-close support for h2 tcp tunnels Proxy app can now handle TCP connection close better. Type: improvement Change-Id: I8d0cbf0c8dbc97aac4b8d860e67c4a325f316213 Signed-off-by: Matus Fabian --- src/plugins/http/http.c | 25 +++++++++++++++++++------ src/plugins/http/http1.c | 2 +- src/plugins/http/http2/http2.c | 14 ++++++++++---- src/plugins/http/http_private.h | 3 ++- 4 files changed, 32 insertions(+), 12 deletions(-) diff --git a/src/plugins/http/http.c b/src/plugins/http/http.c index f7071bf0e1a..71baccf101b 100644 --- a/src/plugins/http/http.c +++ b/src/plugins/http/http.c @@ -1171,9 +1171,8 @@ http_stop_listen (u32 listener_index) return 0; } - -static void -http_transport_close (u32 rh, clib_thread_index_t thread_index) +static_always_inline void +http_app_close (u32 rh, clib_thread_index_t thread_index, u8 is_shutdown) { http_conn_t *hc; u32 hc_index; @@ -1183,7 +1182,8 @@ http_transport_close (u32 rh, clib_thread_index_t thread_index) hc_index = http_vfts[hr_handle.version].hc_index_get_by_req_index ( hr_handle.req_index, thread_index); - HTTP_DBG (1, "App disconnecting [%u]%x", thread_index, hc_index); + HTTP_DBG (1, "App disconnecting [%u]%x is_shutdown=%u", thread_index, + hc_index, is_shutdown); hc = http_conn_get_w_thread (hc_index, thread_index); if (hc->state == HTTP_CONN_STATE_CONNECTING) @@ -1200,7 +1200,19 @@ http_transport_close (u32 rh, clib_thread_index_t thread_index) } http_vfts[hc->version].app_close_callback (hc, hr_handle.req_index, - thread_index); + thread_index, is_shutdown); +} + +static void +http_transport_shutdown (u32 rh, clib_thread_index_t thread_index) +{ + http_app_close (rh, thread_index, 1); +} + +static void +http_transport_close (u32 rh, clib_thread_index_t thread_index) +{ + http_app_close (rh, thread_index, 0); } static void @@ -1272,7 +1284,7 @@ http_app_tx_callback (void *session, transport_send_params_t *sp) if (hc->state == HTTP_CONN_STATE_APP_CLOSED) http_vfts[hc->version].app_close_callback (hc, hr_handle.req_index, - as->thread_index); + as->thread_index, 0); sent = max_burst_sz - sp->max_burst_size; @@ -1420,6 +1432,7 @@ static const transport_proto_vft_t http_proto = { .connect = http_transport_connect, .start_listen = http_start_listen, .stop_listen = http_stop_listen, + .half_close = http_transport_shutdown, .close = http_transport_close, .reset = http_transport_reset, .cleanup_ho = http_transport_cleanup_ho, diff --git a/src/plugins/http/http1.c b/src/plugins/http/http1.c index 5547d309bf6..19cc1879b8e 100644 --- a/src/plugins/http/http1.c +++ b/src/plugins/http/http1.c @@ -1922,7 +1922,7 @@ http1_app_rx_evt_callback (http_conn_t *hc, u32 req_index, static void http1_app_close_callback (http_conn_t *hc, u32 req_index, - clib_thread_index_t thread_index) + clib_thread_index_t thread_index, u8 is_shutdown) { http_req_t *req; diff --git a/src/plugins/http/http2/http2.c b/src/plugins/http/http2/http2.c index adc99b1a4eb..a98969b0c81 100644 --- a/src/plugins/http/http2/http2.c +++ b/src/plugins/http/http2/http2.c @@ -31,6 +31,7 @@ typedef enum http2_stream_state_ #define foreach_http2_req_flags \ _ (APP_CLOSED, "app-closed") \ + _ (SHUTDOWN_TUNNEL, "shutdown-tunnel") \ _ (NEED_WINDOW_UPDATE, "need-window-update") \ _ (IS_PARENT, "is-parent") @@ -268,6 +269,8 @@ http2_conn_alloc_req (http_conn_t *hc, u8 is_parent) h2c->req_num++; if (is_parent) { + HTTP_DBG (1, "is parent"); + ASSERT (h2c->parent_req_index == SESSION_INVALID_INDEX); req->flags |= HTTP2_REQ_F_IS_PARENT; h2c->parent_req_index = req_index; } @@ -1717,7 +1720,8 @@ http2_req_state_tunnel_rx (http_conn_t *hc, http2_req_t *req, u32 max_enq; HTTP_DBG (1, "tunnel received data from peer %lu", req->payload_len); - if (req->flags & HTTP2_REQ_F_APP_CLOSED) + if (req->flags & HTTP2_REQ_F_APP_CLOSED && + !(req->flags & HTTP2_REQ_F_SHUTDOWN_TUNNEL)) { HTTP_DBG (1, "proxy app closed, going to reset stream"); http2_stream_error (hc, req, HTTP2_ERROR_CONNECT_ERROR, sp); @@ -2820,7 +2824,7 @@ http2_app_rx_evt_callback (http_conn_t *hc, u32 req_index, static void http2_app_close_callback (http_conn_t *hc, u32 req_index, - clib_thread_index_t thread_index) + clib_thread_index_t thread_index, u8 is_shutdown) { http2_req_t *req; @@ -2839,9 +2843,10 @@ http2_app_close_callback (http_conn_t *hc, u32 req_index, req->stream_state == HTTP2_STREAM_STATE_IDLE || hc->state == HTTP_CONN_STATE_CLOSED) { + u8 is_parent = req->flags & HTTP2_REQ_F_IS_PARENT; HTTP_DBG (1, "nothing more to send, confirm close"); http2_stream_close (req, hc); - if (req->flags & HTTP2_REQ_F_IS_PARENT) + if (is_parent) { HTTP_DBG (1, "client app closed parent, closing connection"); ASSERT (!(hc->flags & HTTP_CONN_F_IS_SERVER)); @@ -2850,6 +2855,7 @@ http2_app_close_callback (http_conn_t *hc, u32 req_index, } else if (req->base.is_tunnel) { + req->flags |= is_shutdown ? HTTP2_REQ_F_SHUTDOWN_TUNNEL : 0; switch (req->stream_state) { case HTTP2_STREAM_STATE_OPEN: @@ -2860,7 +2866,7 @@ http2_app_close_callback (http_conn_t *hc, u32 req_index, HTTP_DBG (1, "wait for all data to be written to ts"); return; } - if (req->our_window == 0) + if (req->our_window == 0 && !is_shutdown) { HTTP_DBG (1, "app has unread data, going to reset stream"); http2_stream_error (hc, req, HTTP2_ERROR_CONNECT_ERROR, 0); diff --git a/src/plugins/http/http_private.h b/src/plugins/http/http_private.h index 0ca599a4a9d..49d41961fe3 100644 --- a/src/plugins/http/http_private.h +++ b/src/plugins/http/http_private.h @@ -286,7 +286,8 @@ typedef struct http_engine_vft_ void (*app_rx_evt_callback) (http_conn_t *hc, u32 req_index, clib_thread_index_t thread_index); void (*app_close_callback) (http_conn_t *hc, u32 req_index, - clib_thread_index_t thread_index); + clib_thread_index_t thread_index, + u8 is_shutdown); void (*app_reset_callback) (http_conn_t *hc, u32 req_index, clib_thread_index_t thread_index); int (*transport_connected_callback) (http_conn_t *hc); -- 2.16.6