diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/plugins/http/CMakeLists.txt | 6 | ||||
-rw-r--r-- | src/plugins/http/http2/hpack.c | 1 | ||||
-rw-r--r-- | src/plugins/http/http2/hpack.h | 1 | ||||
-rw-r--r-- | src/plugins/http/http2/http2.c | 411 |
4 files changed, 418 insertions, 1 deletions
diff --git a/src/plugins/http/CMakeLists.txt b/src/plugins/http/CMakeLists.txt index 58cb4c000e3..ca2c0a9dc05 100644 --- a/src/plugins/http/CMakeLists.txt +++ b/src/plugins/http/CMakeLists.txt @@ -11,9 +11,15 @@ # See the License for the specific language governing permissions and # limitations under the License. +option(VPP_ENABLE_HTTP_2 "Build http plugin with HTTP/2 enabled" OFF) +if(VPP_ENABLE_HTTP_2) + add_compile_definitions(HTTP_2_ENABLE=1) +endif() + add_vpp_plugin(http SOURCES http2/hpack.c + http2/http2.c http2/frame.c http.c http_buffer.c diff --git a/src/plugins/http/http2/hpack.c b/src/plugins/http/http2/hpack.c index 6dcf5f6c19b..24fc3202dc5 100644 --- a/src/plugins/http/http2/hpack.c +++ b/src/plugins/http/http2/hpack.c @@ -4,7 +4,6 @@ #include <vppinfra/error.h> #include <vppinfra/ring.h> -#include <http/http.h> #include <http/http2/hpack.h> #include <http/http2/huffman_table.h> #include <http/http_status_codes.h> diff --git a/src/plugins/http/http2/hpack.h b/src/plugins/http/http2/hpack.h index 2a2936b7611..9f3e62e65ce 100644 --- a/src/plugins/http/http2/hpack.h +++ b/src/plugins/http/http2/hpack.h @@ -7,6 +7,7 @@ #include <vppinfra/types.h> #include <http/http2/http2.h> +#include <http/http.h> #define HPACK_INVALID_INT CLIB_UWORD_MAX #if uword_bits == 64 diff --git a/src/plugins/http/http2/http2.c b/src/plugins/http/http2/http2.c new file mode 100644 index 00000000000..035620c5184 --- /dev/null +++ b/src/plugins/http/http2/http2.c @@ -0,0 +1,411 @@ +/* SPDX-License-Identifier: Apache-2.0 + * Copyright(c) 2025 Cisco Systems, Inc. + */ + +#include <http/http2/hpack.h> +#include <http/http_private.h> + +#ifndef HTTP_2_ENABLE +#define HTTP_2_ENABLE 0 +#endif + +typedef struct http2_req_ +{ + http_req_t base; + u32 stream_id; + u64 peer_window; +} http2_req_t; + +typedef struct http2_conn_ctx_ +{ + http2_conn_settings_t peer_settings; + hpack_dynamic_table_t decoder_dynamic_table; + u64 peer_window; + uword *req_by_stream_id; +} http2_conn_ctx_t; + +typedef struct http2_main_ +{ + http2_conn_ctx_t **conn_pool; + http2_req_t **req_pool; + http2_conn_settings_t settings; +} http2_main_t; + +static http2_main_t http2_main; + +http2_conn_ctx_t * +http2_conn_ctx_alloc_w_thread (http_conn_t *hc) +{ + http2_main_t *h2m = &http2_main; + http2_conn_ctx_t *h2c; + + pool_get_aligned_safe (h2m->conn_pool[hc->c_thread_index], h2c, + CLIB_CACHE_LINE_BYTES); + clib_memset (h2c, 0, sizeof (*h2c)); + h2c->peer_settings = http2_default_conn_settings; + h2c->peer_window = h2c->peer_settings.initial_window_size; + h2c->req_by_stream_id = hash_create (0, sizeof (uword)); + hc->opaque = + uword_to_pointer (h2c - h2m->conn_pool[hc->c_thread_index], void *); + return h2c; +} + +static inline http2_conn_ctx_t * +http2_conn_ctx_get_w_thread (http_conn_t *hc) +{ + http2_main_t *h2m = &http2_main; + u32 h2c_index = pointer_to_uword (hc->opaque); + return pool_elt_at_index (h2m->conn_pool[hc->c_thread_index], h2c_index); +} + +static inline void +http2_conn_ctx_free (http_conn_t *hc) +{ + http2_main_t *h2m = &http2_main; + http2_conn_ctx_t *h2c; + + h2c = http2_conn_ctx_get_w_thread (hc); + hpack_dynamic_table_free (&h2c->decoder_dynamic_table); + hash_free (h2c->req_by_stream_id); + if (CLIB_DEBUG) + memset (h2c, 0xba, sizeof (*h2c)); + pool_put (h2m->conn_pool[hc->c_thread_index], h2c); +} + +http2_req_t * +http2_conn_alloc_req (http_conn_t *hc, u32 stream_id) +{ + http2_main_t *h2m = &http2_main; + http2_conn_ctx_t *h2c; + http2_req_t *req; + u32 req_index; + http_req_handle_t hr_handle; + + pool_get_aligned_safe (h2m->req_pool[hc->c_thread_index], req, + CLIB_CACHE_LINE_BYTES); + clib_memset (req, 0, sizeof (*req)); + req->base.hr_pa_session_handle = SESSION_INVALID_HANDLE; + req_index = req - h2m->req_pool[hc->c_thread_index]; + hr_handle.version = HTTP_VERSION_2; + hr_handle.req_index = req_index; + req->base.hr_req_handle = hr_handle.as_u32; + req->base.hr_hc_index = hc->hc_hc_index; + req->base.c_thread_index = hc->c_thread_index; + req->stream_id = stream_id; + h2c = http2_conn_ctx_get_w_thread (hc); + req->peer_window = h2c->peer_settings.initial_window_size; + hash_set (h2c->req_by_stream_id, stream_id, req_index); + return req; +} + +static inline void +http2_conn_free_req (http2_conn_ctx_t *h2c, http2_req_t *req, u32 thread_index) +{ + http2_main_t *h2m = &http2_main; + + vec_free (req->base.headers); + vec_free (req->base.target); + http_buffer_free (&req->base.tx_buf); + hash_unset (h2c->req_by_stream_id, req->stream_id); + if (CLIB_DEBUG) + memset (req, 0xba, sizeof (*req)); + pool_put (h2m->req_pool[thread_index], req); +} + +http2_req_t * +http2_conn_get_req (http_conn_t *hc, u32 stream_id) +{ + http2_main_t *h2m = &http2_main; + http2_conn_ctx_t *h2c; + uword *p; + + h2c = http2_conn_ctx_get_w_thread (hc); + p = hash_get (h2c->req_by_stream_id, stream_id); + if (p) + { + return pool_elt_at_index (h2m->req_pool[hc->c_thread_index], p[0]); + } + else + { + HTTP_DBG (1, "hc [%u]%x streamId %u not found", hc->c_thread_index, + hc->hc_hc_index, stream_id); + return 0; + } +} + +always_inline http2_req_t * +http2_req_get (u32 req_index, u32 thread_index) +{ + http2_main_t *h2m = &http2_main; + + return pool_elt_at_index (h2m->req_pool[thread_index], req_index); +} + +/*****************/ +/* http core VFT */ +/*****************/ + +static u32 +http2_hc_index_get_by_req_index (u32 req_index, u32 thread_index) +{ + http2_req_t *req; + + req = http2_req_get (req_index, thread_index); + return req->base.hr_hc_index; +} + +static transport_connection_t * +http2_req_get_connection (u32 req_index, u32 thread_index) +{ + http2_req_t *req; + req = http2_req_get (req_index, thread_index); + return &(req->base.connection); +} + +static u8 * +format_http2_req (u8 *s, va_list *args) +{ + http2_req_t *req = va_arg (*args, http2_req_t *); + http_conn_t *hc = va_arg (*args, http_conn_t *); + session_t *ts; + + ts = session_get_from_handle (hc->hc_tc_session_handle); + s = format (s, "[%d:%d][H2] stream_id %u app_wrk %u hc_index %u ts %d:%d", + req->base.c_thread_index, req->base.c_s_index, req->stream_id, + req->base.hr_pa_wrk_index, req->base.hr_hc_index, + ts->thread_index, ts->session_index); + + return s; +} + +static u8 * +http2_format_req (u8 *s, va_list *args) +{ + u32 req_index = va_arg (*args, u32); + u32 thread_index = va_arg (*args, u32); + http_conn_t *hc = va_arg (*args, http_conn_t *); + u32 verbose = va_arg (*args, u32); + http2_req_t *req; + + req = http2_req_get (req_index, thread_index); + + s = format (s, "%-" SESSION_CLI_ID_LEN "U", format_http2_req, req, hc); + if (verbose) + { + s = + format (s, "%-" SESSION_CLI_STATE_LEN "U", format_http_conn_state, hc); + if (verbose > 1) + s = format (s, "\n"); + } + + return s; +} + +static void +http2_app_tx_callback (http_conn_t *hc, u32 req_index, + transport_send_params_t *sp) +{ + /* TODO: run state machine */ +} + +static void +http2_app_rx_evt_callback (http_conn_t *hc, u32 req_index, u32 thread_index) +{ + /* TODO: continue tunnel RX */ +} + +static void +http2_app_close_callback (http_conn_t *hc, u32 req_index, u32 thread_index) +{ + /* TODO: confirm close or wait until all app data drained */ +} + +static void +http2_app_reset_callback (http_conn_t *hc, u32 req_index, u32 thread_index) +{ + /* TODO: send RST_STREAM frame */ +} + +static int +http2_transport_connected_callback (http_conn_t *hc) +{ + /* TODO */ + return -1; +} + +static void +http2_transport_rx_callback (http_conn_t *hc) +{ + /* TODO: run state machine or handle control frames on stream 0 */ +} + +static void +http2_transport_close_callback (http_conn_t *hc) +{ + u32 req_index, stream_id; + http2_req_t *req; + http2_conn_ctx_t *h2c; + + if (!(hc->flags & HTTP_CONN_F_HAS_REQUEST)) + return; + + h2c = http2_conn_ctx_get_w_thread (hc); + hash_foreach (stream_id, req_index, h2c->req_by_stream_id, ({ + req = http2_req_get (req_index, hc->c_thread_index); + session_transport_closing_notify (&req->base.connection); + })); +} + +static void +http2_transport_reset_callback (http_conn_t *hc) +{ + u32 req_index, stream_id; + http2_req_t *req; + http2_conn_ctx_t *h2c; + + if (!(hc->flags & HTTP_CONN_F_HAS_REQUEST)) + return; + + h2c = http2_conn_ctx_get_w_thread (hc); + hash_foreach (stream_id, req_index, h2c->req_by_stream_id, ({ + req = http2_req_get (req_index, hc->c_thread_index); + session_transport_reset_notify (&req->base.connection); + })); +} + +static void +http2_transport_conn_reschedule_callback (http_conn_t *hc) +{ + /* TODO */ +} + +static void +http2_conn_cleanup_callback (http_conn_t *hc) +{ + u32 req_index, stream_id, *req_index_p, *req_indices = 0; + http2_req_t *req; + http2_conn_ctx_t *h2c; + + h2c = http2_conn_ctx_get_w_thread (hc); + hash_foreach (stream_id, req_index, h2c->req_by_stream_id, + ({ vec_add1 (req_indices, req_index); })); + + vec_foreach (req_index_p, req_indices) + { + req = http2_req_get (*req_index_p, hc->c_thread_index); + session_transport_delete_notify (&req->base.connection); + http2_conn_free_req (h2c, req, hc->c_thread_index); + } + + vec_free (req_indices); + http2_conn_ctx_free (hc); +} + +static void +http2_enable_callback (void) +{ + http2_main_t *h2m = &http2_main; + vlib_thread_main_t *vtm = vlib_get_thread_main (); + u32 num_threads; + + num_threads = 1 /* main thread */ + vtm->n_threads; + + vec_validate (h2m->conn_pool, num_threads - 1); + vec_validate (h2m->req_pool, num_threads - 1); +} + +static int +http2_update_settings (http_settings_t type, u32 value) +{ + http2_main_t *h2m = &http2_main; + + switch (type) + { +#define _(v, label, member, min, max, default_value, err_code) \ + case HTTP2_SETTINGS_##label: \ + if (!(value >= min && value <= max)) \ + return -1; \ + h2m->settings.member = value; \ + return 0; + foreach_http2_settings +#undef _ + default : return -1; + } +} + +static uword +http2_unformat_config_callback (unformat_input_t *input) +{ + u32 value; + + if (!input) + return 0; + + unformat_skip_white_space (input); + while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT) + { + if (unformat (input, "initial-window-size %u", &value)) + { + if (http2_update_settings (HTTP2_SETTINGS_INITIAL_WINDOW_SIZE, + value)) + return 0; + } + else if (unformat (input, "max-frame-size %u", &value)) + { + if (http2_update_settings (HTTP2_SETTINGS_MAX_FRAME_SIZE, value)) + return 0; + } + else if (unformat (input, "max-header-list-size %u", &value)) + { + if (http2_update_settings (HTTP2_SETTINGS_MAX_HEADER_LIST_SIZE, + value)) + return 0; + } + else if (unformat (input, "header-table-size %u", &value)) + { + if (http2_update_settings (HTTP2_SETTINGS_HEADER_TABLE_SIZE, value)) + return 0; + } + else + return 0; + } + return 1; +} + +const static http_engine_vft_t http2_engine = { + .name = "http2", + .hc_index_get_by_req_index = http2_hc_index_get_by_req_index, + .req_get_connection = http2_req_get_connection, + .format_req = http2_format_req, + .app_tx_callback = http2_app_tx_callback, + .app_rx_evt_callback = http2_app_rx_evt_callback, + .app_close_callback = http2_app_close_callback, + .app_reset_callback = http2_app_reset_callback, + .transport_connected_callback = http2_transport_connected_callback, + .transport_rx_callback = http2_transport_rx_callback, + .transport_close_callback = http2_transport_close_callback, + .transport_reset_callback = http2_transport_reset_callback, + .transport_conn_reschedule_callback = + http2_transport_conn_reschedule_callback, + .conn_cleanup_callback = http2_conn_cleanup_callback, + .enable_callback = http2_enable_callback, + .unformat_cfg_callback = http2_unformat_config_callback, +}; + +clib_error_t * +http2_init (vlib_main_t *vm) +{ + http2_main_t *h2m = &http2_main; + + clib_warning ("http/2 enabled"); + h2m->settings = http2_default_conn_settings; + http_register_engine (&http2_engine, HTTP_VERSION_2); + + return 0; +} + +#if HTTP_2_ENABLE > 0 +VLIB_INIT_FUNCTION (http2_init) = { + .runs_after = VLIB_INITS ("http_transport_init"), +}; +#endif |