diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/plugins/hs_apps/http_client.c | 448 | ||||
-rw-r--r-- | src/plugins/http/CMakeLists.txt | 1 | ||||
-rw-r--r-- | src/plugins/http/http.c | 2086 | ||||
-rw-r--r-- | src/plugins/http/http.h | 359 | ||||
-rw-r--r-- | src/plugins/http/http1.c | 1750 | ||||
-rw-r--r-- | src/plugins/http/http_private.h | 672 | ||||
-rw-r--r-- | src/plugins/http/http_timer.h | 8 | ||||
-rw-r--r-- | src/plugins/quic/quic_crypto.c | 9 | ||||
-rw-r--r-- | src/plugins/tlspicotls/pico_vpp_crypto.c | 6 | ||||
-rw-r--r-- | src/vlib/main.c | 2 | ||||
-rw-r--r-- | src/vlib/threads.c | 8 | ||||
-rw-r--r-- | src/vlib/threads.h | 7 | ||||
-rw-r--r-- | src/vnet/session/session_lookup.c | 65 | ||||
-rw-r--r-- | src/vnet/session/session_lookup.h | 3 | ||||
-rw-r--r-- | src/vnet/session/transport.c | 10 | ||||
-rw-r--r-- | src/vppinfra/clib_error.h | 2 | ||||
-rw-r--r-- | src/vppinfra/elog.h | 12 | ||||
-rw-r--r-- | src/vppinfra/error_bootstrap.h | 2 | ||||
-rw-r--r-- | src/vppinfra/mem.h | 2 | ||||
-rw-r--r-- | src/vppinfra/string.c | 2 |
20 files changed, 3135 insertions, 2319 deletions
diff --git a/src/plugins/hs_apps/http_client.c b/src/plugins/hs_apps/http_client.c index 20271fc4aea..e4759317cbe 100644 --- a/src/plugins/hs_apps/http_client.c +++ b/src/plugins/hs_apps/http_client.c @@ -1,5 +1,5 @@ /* SPDX-License-Identifier: Apache-2.0 - * Copyright(c) 2024 Cisco Systems, Inc. + * Copyright(c) 2025 Cisco Systems, Inc. */ #include <vnet/session/application.h> @@ -12,29 +12,36 @@ typedef struct { + u64 req_per_wrk; + u64 request_count; + f64 start, end; + f64 elapsed_time; +} hc_stats_t; + +typedef struct +{ CLIB_CACHE_LINE_ALIGN_MARK (cacheline0); u32 session_index; u32 thread_index; - u32 vpp_session_index; u64 to_recv; u8 is_closed; + hc_stats_t stats; + u64 data_offset; + u8 *resp_headers; + u8 *http_response; + u8 *response_status; } hc_session_t; typedef struct { - u64 request_count; - f64 start, end; - f64 elapsed_time; -} hc_stats_t; - -typedef struct -{ hc_session_t *sessions; u32 thread_index; vlib_main_t *vlib_main; u8 *headers_buf; http_headers_ctx_t req_headers; http_msg_t msg; + u32 session_index; + bool has_common_headers; } hc_worker_t; typedef struct @@ -52,11 +59,7 @@ typedef struct session_endpoint_cfg_t connect_sep; u8 *target; u8 *data; - u64 data_offset; hc_worker_t *wrk; - u8 *resp_headers; - u8 *http_response; - u8 *response_status; hc_http_header_t *custom_header; u8 is_file; u8 use_ptr; @@ -67,6 +70,18 @@ typedef struct u64 repeat_count; f64 duration; bool repeat; + bool multi_session; + u32 done_count; + u32 connected_counter; + u32 worker_index; + u32 max_sessions; + u32 private_segment_size; + u32 prealloc_fifos; + u32 fifo_size; + u8 *appns_id; + u64 appns_secret; + clib_spinlock_t lock; + bool was_transport_closed; } hc_main_t; typedef enum @@ -95,13 +110,6 @@ hc_session_get (u32 session_index, u32 thread_index) return pool_elt_at_index (wrk->sessions, session_index); } -static void -hc_ho_session_free (u32 hs_index) -{ - hc_worker_t *wrk = hc_worker_get (0); - pool_put_index (wrk->sessions, hs_index); -} - static hc_session_t * hc_session_alloc (hc_worker_t *wrk) { @@ -115,14 +123,14 @@ hc_session_alloc (hc_worker_t *wrk) } static int -hc_request (session_t *s, session_error_t err) +hc_request (session_t *s, hc_worker_t *wrk, hc_session_t *hc_session, + session_error_t err) { hc_main_t *hcm = &hc_main; u64 to_send; u32 n_enq; u8 n_segs; int rv; - hc_worker_t *wrk = hc_worker_get (s->thread_index); if (hcm->use_ptr) { @@ -166,7 +174,7 @@ hc_request (session_t *s, session_error_t err) rv = svm_fifo_enqueue (s->tx_fifo, n_enq, hcm->data); if (rv < to_send) { - hcm->data_offset = (rv > 0) ? rv : 0; + hc_session->data_offset = (rv > 0) ? rv : 0; svm_fifo_add_want_deq_ntf (s->tx_fifo, SVM_FIFO_WANT_DEQ_NOTIF); } } @@ -185,9 +193,8 @@ hc_session_connected_callback (u32 app_index, u32 hc_session_index, { hc_main_t *hcm = &hc_main; hc_worker_t *wrk; - u32 new_hc_index; + hc_session_t *hc_session; hc_http_header_t *header; - HTTP_DBG (1, "ho hc_index: %d", hc_session_index); if (err) { @@ -199,68 +206,89 @@ hc_session_connected_callback (u32 app_index, u32 hc_session_index, } wrk = hc_worker_get (s->thread_index); - hc_session_t *hc_session, *new_hc_session = hc_session_alloc (wrk); - hc_session = hc_session_get (hc_session_index, 0); - new_hc_index = new_hc_session->session_index; - clib_memcpy_fast (new_hc_session, hc_session, sizeof (*hc_session)); - new_hc_session->session_index = new_hc_index; - new_hc_session->thread_index = s->thread_index; - new_hc_session->vpp_session_index = s->session_index; - HTTP_DBG (1, "new hc_index: %d", new_hc_session->session_index); - s->opaque = new_hc_index; + hc_session = hc_session_alloc (wrk); + clib_spinlock_lock_if_init (&hcm->lock); + hcm->connected_counter++; + clib_spinlock_unlock_if_init (&hcm->lock); - if (hcm->req_method == HTTP_REQ_POST) + hc_session->thread_index = s->thread_index; + s->opaque = hc_session->session_index; + wrk->session_index = hc_session->session_index; + + if (hcm->multi_session) { - if (hcm->is_file) - http_add_header ( - &wrk->req_headers, HTTP_HEADER_CONTENT_TYPE, - http_content_type_token (HTTP_CONTENT_APP_OCTET_STREAM)); - else - http_add_header ( - &wrk->req_headers, HTTP_HEADER_CONTENT_TYPE, - http_content_type_token (HTTP_CONTENT_APP_X_WWW_FORM_URLENCODED)); + hc_session->stats.req_per_wrk = hcm->repeat_count / hcm->max_sessions; + clib_spinlock_lock_if_init (&hcm->lock); + /* add remaining requests to the first connected session */ + if (hcm->connected_counter == 1) + { + hc_session->stats.req_per_wrk += + hcm->repeat_count % hcm->max_sessions; + } + clib_spinlock_unlock_if_init (&hcm->lock); } - http_add_header (&wrk->req_headers, HTTP_HEADER_ACCEPT, "*", 1); - - vec_foreach (header, hcm->custom_header) - http_add_custom_header ( - &wrk->req_headers, (const char *) header->name, vec_len (header->name), - (const char *) header->value, vec_len (header->value)); - - clib_warning ("%U", format_http_bytes, wrk->headers_buf, - wrk->req_headers.tail_offset); - wrk->msg.method_type = hcm->req_method; - if (hcm->req_method == HTTP_REQ_POST) - wrk->msg.data.body_len = vec_len (hcm->data); else - wrk->msg.data.body_len = 0; - - wrk->msg.type = HTTP_MSG_REQUEST; - /* request target */ - wrk->msg.data.target_path_len = vec_len (hcm->target); - /* custom headers */ - wrk->msg.data.headers_len = wrk->req_headers.tail_offset; - /* total length */ - wrk->msg.data.len = wrk->msg.data.target_path_len + - wrk->msg.data.headers_len + wrk->msg.data.body_len; - - if (hcm->use_ptr) { - wrk->msg.data.type = HTTP_MSG_DATA_PTR; + hc_session->stats.req_per_wrk = hcm->repeat_count; + hcm->worker_index = s->thread_index; } - else + + if (!wrk->has_common_headers) { - wrk->msg.data.type = HTTP_MSG_DATA_INLINE; - wrk->msg.data.target_path_offset = 0; - wrk->msg.data.headers_offset = wrk->msg.data.target_path_len; - wrk->msg.data.body_offset = - wrk->msg.data.headers_offset + wrk->msg.data.headers_len; + wrk->has_common_headers = true; + if (hcm->req_method == HTTP_REQ_POST) + { + if (hcm->is_file) + http_add_header ( + &wrk->req_headers, HTTP_HEADER_CONTENT_TYPE, + http_content_type_token (HTTP_CONTENT_APP_OCTET_STREAM)); + else + http_add_header (&wrk->req_headers, HTTP_HEADER_CONTENT_TYPE, + http_content_type_token ( + HTTP_CONTENT_APP_X_WWW_FORM_URLENCODED)); + } + http_add_header (&wrk->req_headers, HTTP_HEADER_ACCEPT, "*", 1); + + vec_foreach (header, hcm->custom_header) + http_add_custom_header (&wrk->req_headers, (const char *) header->name, + vec_len (header->name), + (const char *) header->value, + vec_len (header->value)); + + wrk->msg.method_type = hcm->req_method; + if (hcm->req_method == HTTP_REQ_POST) + wrk->msg.data.body_len = vec_len (hcm->data); + else + wrk->msg.data.body_len = 0; + + wrk->msg.type = HTTP_MSG_REQUEST; + /* request target */ + wrk->msg.data.target_path_len = vec_len (hcm->target); + /* custom headers */ + wrk->msg.data.headers_len = wrk->req_headers.tail_offset; + /* total length */ + wrk->msg.data.len = wrk->msg.data.target_path_len + + wrk->msg.data.headers_len + wrk->msg.data.body_len; + + if (hcm->use_ptr) + { + wrk->msg.data.type = HTTP_MSG_DATA_PTR; + } + else + { + wrk->msg.data.type = HTTP_MSG_DATA_INLINE; + wrk->msg.data.target_path_offset = 0; + wrk->msg.data.headers_offset = wrk->msg.data.target_path_len; + wrk->msg.data.body_offset = + wrk->msg.data.headers_offset + wrk->msg.data.headers_len; + } } if (hcm->repeat) - hc_stats.start = vlib_time_now (vlib_get_main_by_index (s->thread_index)); + hc_session->stats.start = + vlib_time_now (vlib_get_main_by_index (s->thread_index)); - return hc_request (s, err); + return hc_request (s, wrk, hc_session, err); } static void @@ -275,21 +303,38 @@ hc_session_disconnect_callback (session_t *s) if ((rv = vnet_disconnect_session (a))) clib_warning ("warning: disconnect returned: %U", format_session_error, rv); + clib_spinlock_lock_if_init (&hcm->lock); + hcm->done_count++; + clib_spinlock_unlock_if_init (&hcm->lock); } static void hc_session_transport_closed_callback (session_t *s) { hc_main_t *hcm = &hc_main; - vlib_process_signal_event_mt (hcm->wrk->vlib_main, hcm->cli_node_index, - HC_TRANSPORT_CLOSED, 0); -} + hc_worker_t *wrk = hc_worker_get (s->thread_index); -static void -hc_ho_cleanup_callback (session_t *s) -{ - HTTP_DBG (1, "ho hc_index: %d:", s->opaque); - hc_ho_session_free (s->opaque); + clib_spinlock_lock_if_init (&hcm->lock); + if (s->session_state == SESSION_STATE_TRANSPORT_CLOSED) + { + hcm->was_transport_closed = true; + } + + /* send an event when all sessions are closed */ + if (hcm->done_count >= hcm->max_sessions) + { + if (hcm->was_transport_closed) + { + vlib_process_signal_event_mt (wrk->vlib_main, hcm->cli_node_index, + HC_TRANSPORT_CLOSED, 0); + } + else + { + vlib_process_signal_event_mt (wrk->vlib_main, hcm->cli_node_index, + HC_REPEAT_DONE, 0); + } + } + clib_spinlock_unlock_if_init (&hcm->lock); } static void @@ -315,20 +360,23 @@ hc_rx_callback (session_t *s) { hc_main_t *hcm = &hc_main; hc_worker_t *wrk = hc_worker_get (s->thread_index); - hc_session_t *hc_session; + hc_session_t *hc_session = hc_session_get (s->opaque, s->thread_index); http_msg_t msg; int rv; + u32 max_deq; session_error_t session_err = 0; int send_err = 0; - hc_session = hc_session_get (s->opaque, s->thread_index); - if (hc_session->is_closed) { clib_warning ("hc_session_index[%d] is closed", s->opaque); return -1; } + max_deq = svm_fifo_max_dequeue_cons (s->rx_fifo); + if (PREDICT_FALSE (max_deq == 0)) + goto done; + if (hc_session->to_recv == 0) { rv = svm_fifo_dequeue (s->rx_fifo, sizeof (msg), (u8 *) &msg); @@ -344,17 +392,20 @@ hc_rx_callback (session_t *s) if (msg.data.headers_len) { - hcm->response_status = - format (0, "%U", format_http_status_code, msg.code); + + if (!hcm->repeat) + hc_session->response_status = + format (0, "%U", format_http_status_code, msg.code); + svm_fifo_dequeue_drop (s->rx_fifo, msg.data.headers_offset); - vec_validate (hcm->resp_headers, msg.data.headers_len - 1); - vec_set_len (hcm->resp_headers, msg.data.headers_len); + vec_validate (hc_session->resp_headers, msg.data.headers_len - 1); + vec_set_len (hc_session->resp_headers, msg.data.headers_len); rv = svm_fifo_dequeue (s->rx_fifo, msg.data.headers_len, - hcm->resp_headers); + hc_session->resp_headers); ASSERT (rv == msg.data.headers_len); - HTTP_DBG (1, (char *) format (0, "%v", hcm->resp_headers)); + HTTP_DBG (1, (char *) format (0, "%v", hc_session->resp_headers)); msg.data.body_offset -= msg.data.headers_len + msg.data.headers_offset; } @@ -372,18 +423,18 @@ hc_rx_callback (session_t *s) { goto done; } - vec_validate (hcm->http_response, msg.data.body_len - 1); - vec_reset_length (hcm->http_response); + vec_validate (hc_session->http_response, msg.data.body_len - 1); + vec_reset_length (hc_session->http_response); } - u32 max_deq = svm_fifo_max_dequeue (s->rx_fifo); + max_deq = svm_fifo_max_dequeue (s->rx_fifo); if (!max_deq) { goto done; } u32 n_deq = clib_min (hc_session->to_recv, max_deq); - u32 curr = vec_len (hcm->http_response); - rv = svm_fifo_dequeue (s->rx_fifo, n_deq, hcm->http_response + curr); + u32 curr = vec_len (hc_session->http_response); + rv = svm_fifo_dequeue (s->rx_fifo, n_deq, hc_session->http_response + curr); if (rv < 0) { clib_warning ("app dequeue(n=%d) failed; rv = %d", n_deq, rv); @@ -393,7 +444,7 @@ hc_rx_callback (session_t *s) } ASSERT (rv == n_deq); - vec_set_len (hcm->http_response, curr + n_deq); + vec_set_len (hc_session->http_response, curr + n_deq); ASSERT (hc_session->to_recv >= rv); hc_session->to_recv -= rv; @@ -402,20 +453,19 @@ done: { if (hcm->repeat) { - hc_stats.request_count++; - hc_stats.end = vlib_time_now (wrk->vlib_main); - hc_stats.elapsed_time = hc_stats.end - hc_stats.start; + hc_session->stats.request_count++; + hc_session->stats.end = vlib_time_now (wrk->vlib_main); + hc_session->stats.elapsed_time = + hc_session->stats.end - hc_session->stats.start; - if (hc_stats.elapsed_time >= hcm->duration && - hc_stats.request_count >= hcm->repeat_count) + if (hc_session->stats.elapsed_time >= hcm->duration && + hc_session->stats.request_count >= hc_session->stats.req_per_wrk) { - vlib_process_signal_event_mt ( - wrk->vlib_main, hcm->cli_node_index, HC_REPEAT_DONE, 0); hc_session_disconnect_callback (s); } else { - send_err = hc_request (s, session_err); + send_err = hc_request (s, wrk, hc_session, session_err); if (send_err) clib_warning ("failed to send request, error %d", send_err); } @@ -434,11 +484,13 @@ static int hc_tx_callback (session_t *s) { hc_main_t *hcm = &hc_main; + hc_session_t *hc_session = hc_session_get (s->opaque, s->thread_index); u64 to_send; int rv; - to_send = vec_len (hcm->data) - hcm->data_offset; - rv = svm_fifo_enqueue (s->tx_fifo, to_send, hcm->data + hcm->data_offset); + to_send = vec_len (hcm->data) - hc_session->data_offset; + rv = svm_fifo_enqueue (s->tx_fifo, to_send, + hcm->data + hc_session->data_offset); if (rv <= 0) { @@ -448,7 +500,7 @@ hc_tx_callback (session_t *s) if (rv < to_send) { - hcm->data_offset += rv; + hc_session->data_offset += rv; svm_fifo_add_want_deq_ntf (s->tx_fifo, SVM_FIFO_WANT_DEQ_NOTIF); } @@ -465,7 +517,6 @@ static session_cb_vft_t hc_session_cb_vft = { .session_reset_callback = hc_session_reset_callback, .builtin_app_rx_callback = hc_rx_callback, .builtin_app_tx_callback = hc_tx_callback, - .half_open_cleanup_callback = hc_ho_cleanup_callback, }; static clib_error_t * @@ -474,8 +525,12 @@ hc_attach () hc_main_t *hcm = &hc_main; vnet_app_attach_args_t _a, *a = &_a; u64 options[18]; + u32 segment_size = 128 << 20; int rv; + if (hcm->private_segment_size) + segment_size = hcm->private_segment_size; + clib_memset (a, 0, sizeof (*a)); clib_memset (options, 0, sizeof (options)); @@ -483,7 +538,19 @@ hc_attach () a->name = format (0, "http_client"); a->session_cb_vft = &hc_session_cb_vft; a->options = options; + a->options[APP_OPTIONS_SEGMENT_SIZE] = segment_size; + a->options[APP_OPTIONS_ADD_SEGMENT_SIZE] = segment_size; + a->options[APP_OPTIONS_RX_FIFO_SIZE] = + hcm->fifo_size ? hcm->fifo_size : 8 << 10; + a->options[APP_OPTIONS_TX_FIFO_SIZE] = + hcm->fifo_size ? hcm->fifo_size : 32 << 10; a->options[APP_OPTIONS_FLAGS] = APP_OPTIONS_FLAGS_IS_BUILTIN; + a->options[APP_OPTIONS_PREALLOC_FIFO_PAIRS] = hcm->prealloc_fifos; + if (hcm->appns_id) + { + a->namespace_id = hcm->appns_id; + a->options[APP_OPTIONS_NAMESPACE_SECRET] = hcm->appns_secret; + } if ((rv = vnet_application_attach (a))) return clib_error_return (0, "attach returned: %U", format_session_error, @@ -500,14 +567,19 @@ static int hc_connect_rpc (void *rpc_args) { vnet_connect_args_t *a = rpc_args; - int rv; + int rv = ~0; + hc_main_t *hcm = &hc_main; - rv = vnet_connect (a); - if (rv > 0) - clib_warning (0, "connect returned: %U", format_session_error, rv); + for (u32 i = 0; i < hcm->max_sessions; i++) + { + rv = vnet_connect (a); + if (rv > 0) + clib_warning (0, "connect returned: %U", format_session_error, rv); + } session_endpoint_free_ext_cfgs (&a->sep_ext); vec_free (a); + return rv; } @@ -516,14 +588,10 @@ hc_connect () { hc_main_t *hcm = &hc_main; vnet_connect_args_t *a = 0; - hc_worker_t *wrk; - hc_session_t *hc_session; transport_endpt_ext_cfg_t *ext_cfg; transport_endpt_cfg_http_t http_cfg = { (u32) hcm->timeout, 0 }; - vec_validate (a, 0); clib_memset (a, 0, sizeof (a[0])); - clib_memcpy (&a->sep_ext, &hcm->connect_sep, sizeof (hcm->connect_sep)); a->app_index = hcm->app_index; @@ -531,15 +599,41 @@ hc_connect () &a->sep_ext, TRANSPORT_ENDPT_EXT_CFG_HTTP, sizeof (http_cfg)); clib_memcpy (ext_cfg->data, &http_cfg, sizeof (http_cfg)); - /* allocate http session on main thread */ - wrk = hc_worker_get (0); - hc_session = hc_session_alloc (wrk); - a->api_context = hc_session->session_index; - session_send_rpc_evt_to_thread_force (transport_cl_thread (), hc_connect_rpc, a); } +static void +hc_get_repeat_stats (vlib_main_t *vm) +{ + hc_main_t *hcm = &hc_main; + hc_worker_t *wrk; + hc_session_t *hc_session; + + if (hcm->repeat) + { + vec_foreach (wrk, hcm->wrk) + { + vec_foreach (hc_session, wrk->sessions) + { + hc_stats.request_count += hc_session->stats.request_count; + hc_session->stats.request_count = 0; + if (hc_stats.elapsed_time < hc_session->stats.elapsed_time) + { + hc_stats.elapsed_time = hc_session->stats.elapsed_time; + hc_session->stats.elapsed_time = 0; + } + } + } + vlib_cli_output (vm, + "< %d request(s) in %.6fs\n< avg latency " + "%.4fms\n< %.2f req/sec", + hc_stats.request_count, hc_stats.elapsed_time, + (hc_stats.elapsed_time / hc_stats.request_count) * 1000, + hc_stats.request_count / hc_stats.elapsed_time); + } +} + static clib_error_t * hc_get_event (vlib_main_t *vm) { @@ -548,6 +642,8 @@ hc_get_event (vlib_main_t *vm) clib_error_t *err = NULL; FILE *file_ptr; u64 event_timeout; + hc_worker_t *wrk; + hc_session_t *hc_session; event_timeout = hcm->timeout ? hcm->timeout : 10; if (event_timeout == hcm->duration) @@ -558,20 +654,26 @@ hc_get_event (vlib_main_t *vm) switch (event_type) { case ~0: + hc_get_repeat_stats (vm); err = clib_error_return (0, "error: timeout"); break; case HC_CONNECT_FAILED: + hc_get_repeat_stats (vm); err = clib_error_return (0, "error: failed to connect"); break; case HC_TRANSPORT_CLOSED: + hc_get_repeat_stats (vm); err = clib_error_return (0, "error: transport closed"); break; case HC_GENERIC_ERR: + hc_get_repeat_stats (vm); err = clib_error_return (0, "error: unknown"); break; case HC_REPLY_RECEIVED: if (hcm->filename) { + wrk = hc_worker_get (hcm->worker_index); + hc_session = hc_session_get (wrk->session_index, wrk->thread_index); file_ptr = fopen ((char *) format (0, "/tmp/%v", hcm->filename), "a"); if (file_ptr == NULL) @@ -580,26 +682,27 @@ hc_get_event (vlib_main_t *vm) } else { - fprintf (file_ptr, "< %s\n< %s\n< %s", hcm->response_status, - hcm->resp_headers, hcm->http_response); + fprintf (file_ptr, "< %s\n< %s\n< %s", + hc_session->response_status, hc_session->resp_headers, + hc_session->http_response); fclose (file_ptr); vlib_cli_output (vm, "file saved (/tmp/%v)", hcm->filename); } } if (hcm->verbose) - vlib_cli_output (vm, "< %v< %v", hcm->response_status, - hcm->resp_headers); - vlib_cli_output (vm, "\n%v\n", hcm->http_response); + { + wrk = hc_worker_get (hcm->worker_index); + hc_session = hc_session_get (wrk->session_index, wrk->thread_index); + vlib_cli_output (vm, "< %v< %v", hc_session->response_status, + hc_session->resp_headers); + vlib_cli_output (vm, "\n%v\n", hc_session->http_response); + } break; case HC_REPEAT_DONE: - vlib_cli_output (vm, - "< %d request(s) in %.6fs\n< avg latency " - "%.4fms\n< %.2f req/sec", - hc_stats.request_count, hc_stats.elapsed_time, - (hc_stats.elapsed_time / hc_stats.request_count) * 1000, - hc_stats.request_count / hc_stats.elapsed_time); + hc_get_repeat_stats (vm); break; default: + hc_get_repeat_stats (vm); err = clib_error_return (0, "error: unexpected event %d", event_type); break; } @@ -612,15 +715,17 @@ static clib_error_t * hc_run (vlib_main_t *vm) { hc_main_t *hcm = &hc_main; - vlib_thread_main_t *vtm = vlib_get_thread_main (); u32 num_threads; hc_worker_t *wrk; clib_error_t *err; - num_threads = 1 /* main thread */ + vtm->n_threads; + num_threads = 1 /* main thread */ + vlib_num_workers (); + if (vlib_num_workers ()) + clib_spinlock_init (&hcm->lock); vec_validate (hcm->wrk, num_threads - 1); vec_foreach (wrk, hcm->wrk) { + wrk->has_common_headers = false; wrk->thread_index = wrk - hcm->wrk; /* 4k for headers should be enough */ vec_validate (wrk->headers_buf, 4095); @@ -657,10 +762,18 @@ hc_detach () } static void -hcc_worker_cleanup (hc_worker_t *wrk) +hc_worker_cleanup (hc_worker_t *wrk) { - HTTP_DBG (1, "worker cleanup"); + hc_session_t *hc_session; + HTTP_DBG (1, "worker and worker sessions cleanup"); + vec_free (wrk->headers_buf); + vec_foreach (hc_session, wrk->sessions) + { + vec_free (hc_session->resp_headers); + vec_free (hc_session->http_response); + vec_free (hc_session->response_status); + } pool_free (wrk->sessions); } @@ -673,16 +786,14 @@ hc_cleanup () hc_http_header_t *header; vec_foreach (wrk, hcm->wrk) - hcc_worker_cleanup (wrk); + hc_worker_cleanup (wrk); vec_free (hcm->uri); vec_free (hcm->target); vec_free (hcm->data); - vec_free (hcm->resp_headers); - vec_free (hcm->http_response); - vec_free (hcm->response_status); vec_free (hcm->wrk); vec_free (hcm->filename); + vec_free (hcm->appns_id); vec_foreach (header, hcm->custom_header) { vec_free (header->name); @@ -698,6 +809,8 @@ hc_command_fn (vlib_main_t *vm, unformat_input_t *input, hc_main_t *hcm = &hc_main; clib_error_t *err = 0; unformat_input_t _line_input, *line_input = &_line_input; + u64 mem_size; + u8 *appns_id = 0; u8 *path = 0; u8 *file_data; hc_http_header_t new_header; @@ -708,7 +821,16 @@ hc_command_fn (vlib_main_t *vm, unformat_input_t *input, hcm->repeat_count = 0; hcm->duration = 0; hcm->repeat = false; + hcm->multi_session = false; + hcm->done_count = 0; + hcm->connected_counter = 0; + hcm->max_sessions = 1; + hcm->prealloc_fifos = 0; + hcm->private_segment_size = 0; + hcm->fifo_size = 0; + hcm->was_transport_closed = false; hc_stats.request_count = 0; + hc_stats.elapsed_time = 0; if (hcm->attached) return clib_error_return (0, "failed: already running!"); @@ -761,6 +883,29 @@ hc_command_fn (vlib_main_t *vm, unformat_input_t *input, } else if (unformat (line_input, "duration %f", &hcm->duration)) hcm->repeat = true; + else if (unformat (line_input, "sessions %d", &hcm->max_sessions)) + { + hcm->multi_session = true; + if (hcm->max_sessions <= 1) + { + err = clib_error_return (0, "sessions must be > 1"); + goto done; + } + } + else if (unformat (line_input, "prealloc-fifos %d", + &hcm->prealloc_fifos)) + ; + else if (unformat (line_input, "private-segment-size %U", + unformat_memory_size, &mem_size)) + hcm->private_segment_size = mem_size; + else if (unformat (line_input, "fifo-size %U", unformat_memory_size, + &mem_size)) + hcm->fifo_size = mem_size; + else if (unformat (line_input, "appns %_%v%_", &appns_id)) + ; + else if (unformat (line_input, "secret %lu", &hcm->appns_secret)) + ; + else { err = clib_error_return (0, "unknown input `%U'", @@ -801,6 +946,13 @@ hc_command_fn (vlib_main_t *vm, unformat_input_t *input, goto done; } + if (hcm->multi_session && !hcm->repeat) + { + err = clib_error_return ( + 0, "multiple sessions are only supported with request repeating"); + goto done; + } + if ((rv = parse_uri ((char *) hcm->uri, &hcm->connect_sep))) { err = @@ -808,6 +960,12 @@ hc_command_fn (vlib_main_t *vm, unformat_input_t *input, goto done; } + if (hcm->duration >= hcm->timeout) + { + hcm->timeout = hcm->duration + 10; + } + hcm->appns_id = appns_id; + if (hcm->repeat) vlib_cli_output (vm, "Running, please wait..."); @@ -845,7 +1003,9 @@ VLIB_CLI_COMMAND (hc_command, static) = { "[post] uri http://<ip-addr> target <origin-form> " "[data <form-urlencoded> | file <file-path>] [use-ptr] " "[save-to <filename>] [header <Key:Value>] [verbose] " - "[timeout <seconds> (default = 10)] [repeat <count> | duration <seconds>]", + "[timeout <seconds> (default = 10)] [repeat <count> | duration <seconds>] " + "[sessions <# of sessions>] [appns <app-ns> secret <appns-secret>] " + "[fifo-size <nM|G>] [private-segment-size <nM|G>] [prealloc-fifos <n>]", .function = hc_command_fn, .is_mp_safe = 1, }; diff --git a/src/plugins/http/CMakeLists.txt b/src/plugins/http/CMakeLists.txt index 075b8d6817b..ad584821c06 100644 --- a/src/plugins/http/CMakeLists.txt +++ b/src/plugins/http/CMakeLists.txt @@ -16,6 +16,7 @@ add_vpp_plugin(http http.c http_buffer.c http_timer.c + http1.c ) add_vpp_plugin(http_unittest diff --git a/src/plugins/http/http.c b/src/plugins/http/http.c index 04a4ad3e0a9..c995c996433 100644 --- a/src/plugins/http/http.c +++ b/src/plugins/http/http.c @@ -13,43 +13,79 @@ * limitations under the License. */ +#include <vpp/app/version.h> +#include <vnet/session/application_interface.h> +#include <vnet/session/application.h> + #include <http/http.h> -#include <vnet/session/session.h> +#include <http/http_private.h> #include <http/http_timer.h> -#include <http/http_status_codes.h> -#include <http/http_header_names.h> static http_main_t http_main; - -#define HTTP_FIFO_THRESH (16 << 10) - -/* HTTP state machine result */ -typedef enum http_sm_result_t_ -{ - HTTP_SM_STOP = 0, - HTTP_SM_CONTINUE = 1, - HTTP_SM_ERROR = -1, -} http_sm_result_t; +static http_engine_vft_t *http_vfts; const http_buffer_type_t msg_to_buf_type[] = { [HTTP_MSG_DATA_INLINE] = HTTP_BUFFER_FIFO, [HTTP_MSG_DATA_PTR] = HTTP_BUFFER_PTR, }; -const char *http_upgrade_proto_str[] = { "", -#define _(sym, str) str, - foreach_http_upgrade_proto -#undef _ -}; +void +http_register_engine (const http_engine_vft_t *vft, http_version_t version) +{ + vec_validate (http_vfts, version); + http_vfts[version] = *vft; +} + +always_inline http_version_t +http_version_from_handle (http_conn_handle_t hc_handle) +{ + /* the first 3 bits are http version */ + return hc_handle >> 29; +} + +always_inline u32 +http_conn_index_from_handle (http_conn_handle_t hc_handle) +{ + return hc_handle & 0x1FFFFFFF; +} + +always_inline http_conn_handle_t +http_make_handle (u32 hc_index, http_version_t version) +{ + ASSERT (hc_index <= 0x1FFFFFFF); + return (version << 29) | hc_index; +} + +int +http_v_find_index (u8 *vec, u32 offset, u32 num, char *str) +{ + int start_index = offset; + u32 slen = (u32) strnlen_s_inline (str, 16); + u32 vlen = vec_len (vec); + + ASSERT (slen > 0); + + if (vlen <= slen) + return -1; -#define expect_char(c) \ - if (*p++ != c) \ - { \ - clib_warning ("unexpected character"); \ - return -1; \ + int end_index = vlen - slen; + if (num) + { + if (num < slen) + return -1; + end_index = clib_min (end_index, offset + num - slen); } -static u8 * + for (; start_index <= end_index; start_index++) + { + if (!memcmp (vec + start_index, str, slen)) + return start_index; + } + + return -1; +} + +u8 * format_http_req_state (u8 *s, va_list *va) { http_req_state_t state = va_arg (*va, http_req_state_t); @@ -68,18 +104,7 @@ format_http_req_state (u8 *s, va_list *va) return format (s, "%s", t); } -#define http_req_state_change(_hc, _state) \ - do \ - { \ - HTTP_DBG (1, "changing http req state: %U -> %U", \ - format_http_req_state, (_hc)->req.state, \ - format_http_req_state, _state); \ - ASSERT ((_hc)->req.state != HTTP_REQ_STATE_TUNNEL); \ - (_hc)->req.state = _state; \ - } \ - while (0) - -static u8 * +u8 * format_http_conn_state (u8 *s, va_list *args) { http_conn_t *hc = va_arg (*args, http_conn_t *); @@ -98,6 +123,15 @@ format_http_conn_state (u8 *s, va_list *args) return format (s, "%s", t); } +u8 * +format_http_time_now (u8 *s, va_list *args) +{ + http_conn_t __clib_unused *hc = va_arg (*args, http_conn_t *); + http_main_t *hm = &http_main; + f64 now = clib_timebase_now (&hm->timebase); + return format (s, "%U", format_clib_timebase_time, now); +} + static inline http_worker_t * http_worker_get (u32 thread_index) { @@ -116,6 +150,7 @@ http_conn_alloc_w_thread (u32 thread_index) hc->h_hc_index = hc - wrk->conn_pool; hc->h_pa_session_handle = SESSION_INVALID_HANDLE; hc->h_tc_session_handle = SESSION_INVALID_HANDLE; + hc->version = HTTP_VERSION_NA; return hc->h_hc_index; } @@ -135,10 +170,12 @@ http_conn_get_w_thread_if_valid (u32 hc_index, u32 thread_index) return pool_elt_at_index (wrk->conn_pool, hc_index); } -void +static void http_conn_free (http_conn_t *hc) { http_worker_t *wrk = http_worker_get (hc->c_thread_index); + if (CLIB_DEBUG) + memset (hc, 0xba, sizeof (*hc)); pool_put (wrk->conn_pool, hc); } @@ -149,10 +186,12 @@ http_ho_conn_get (u32 ho_hc_index) return pool_elt_at_index (hm->ho_conn_pool, ho_hc_index); } -void +static void http_ho_conn_free (http_conn_t *ho_hc) { http_main_t *hm = &http_main; + if (CLIB_DEBUG) + memset (ho_hc, 0xba, sizeof (*ho_hc)); pool_put (hm->ho_conn_pool, ho_hc); } @@ -168,6 +207,7 @@ http_ho_conn_alloc (void) hc->h_pa_session_handle = SESSION_INVALID_HANDLE; hc->h_tc_session_handle = SESSION_INVALID_HANDLE; hc->timeout = HTTP_CONN_TIMEOUT; + hc->version = HTTP_VERSION_NA; return hc->h_hc_index; } @@ -178,18 +218,19 @@ http_listener_alloc (void) http_conn_t *lhc; pool_get_zero (hm->listener_pool, lhc); - lhc->c_c_index = lhc - hm->listener_pool; + lhc->h_hc_index = lhc - hm->listener_pool; lhc->timeout = HTTP_CONN_TIMEOUT; - return lhc->c_c_index; + lhc->version = HTTP_VERSION_NA; + return lhc->h_hc_index; } -http_conn_t * +static http_conn_t * http_listener_get (u32 lhc_index) { return pool_elt_at_index (http_main.listener_pool, lhc_index); } -void +static void http_listener_free (http_conn_t *lhc) { http_main_t *hm = &http_main; @@ -214,6 +255,96 @@ http_disconnect_transport (http_conn_t *hc) clib_warning ("disconnect returned"); } +http_status_code_t +http_sc_by_u16 (u16 status_code) +{ + http_main_t *hm = &http_main; + return hm->sc_by_u16[status_code]; +} + +u8 * +http_get_app_header_list (http_conn_t *hc, http_msg_t *msg) +{ + http_main_t *hm = &http_main; + session_t *as; + u8 *app_headers; + int rv; + + as = session_get_from_handle (hc->h_pa_session_handle); + + if (msg->data.type == HTTP_MSG_DATA_PTR) + { + uword app_headers_ptr; + rv = svm_fifo_dequeue (as->tx_fifo, sizeof (app_headers_ptr), + (u8 *) &app_headers_ptr); + ASSERT (rv == sizeof (app_headers_ptr)); + app_headers = uword_to_pointer (app_headers_ptr, u8 *); + } + else + { + app_headers = hm->app_header_lists[hc->c_thread_index]; + rv = svm_fifo_dequeue (as->tx_fifo, msg->data.headers_len, app_headers); + ASSERT (rv == msg->data.headers_len); + } + + return app_headers; +} + +u8 * +http_get_app_target (http_req_t *req, http_msg_t *msg) +{ + session_t *as; + u8 *target; + int rv; + + as = session_get_from_handle (req->app_session_handle); + + if (msg->data.type == HTTP_MSG_DATA_PTR) + { + uword target_ptr; + rv = svm_fifo_dequeue (as->tx_fifo, sizeof (target_ptr), + (u8 *) &target_ptr); + ASSERT (rv == sizeof (target_ptr)); + target = uword_to_pointer (target_ptr, u8 *); + } + else + { + vec_reset_length (req->target); + vec_validate (req->target, msg->data.target_path_len - 1); + rv = + svm_fifo_dequeue (as->tx_fifo, msg->data.target_path_len, req->target); + ASSERT (rv == msg->data.target_path_len); + target = req->target; + } + return target; +} + +u8 * +http_get_tx_buf (http_conn_t *hc) +{ + http_main_t *hm = &http_main; + u8 *buf = hm->tx_bufs[hc->c_thread_index]; + vec_reset_length (buf); + return buf; +} + +u8 * +http_get_rx_buf (http_conn_t *hc) +{ + http_main_t *hm = &http_main; + u8 *buf = hm->rx_bufs[hc->c_thread_index]; + vec_reset_length (buf); + return buf; +} + +void +http_req_tx_buffer_init (http_req_t *req, http_msg_t *msg) +{ + session_t *as = session_get_from_handle (req->app_session_handle); + http_buffer_init (&req->tx_buf, msg_to_buf_type[msg->data.type], as->tx_fifo, + msg->data.body_len); +} + static void http_conn_invalidate_timer_cb (u32 hs_handle) { @@ -260,6 +391,10 @@ http_conn_timeout_cb (void *hc_handlep) http_disconnect_transport (hc); } +/*************************/ +/* session VFT callbacks */ +/*************************/ + int http_ts_accept_callback (session_t *ts) { @@ -281,12 +416,12 @@ http_ts_accept_callback (session_t *ts) hc->h_tc_session_handle = session_handle (ts); hc->c_flags |= TRANSPORT_CONNECTION_F_NO_LOOKUP; - hc->state = HTTP_CONN_STATE_ESTABLISHED; - http_req_state_change (hc, HTTP_REQ_STATE_WAIT_TRANSPORT_METHOD); ts->session_state = SESSION_STATE_READY; - ts->opaque = hc_index; + /* TODO: TLS set by ALPN result, TCP: first try HTTP/1 */ + hc->version = HTTP_VERSION_1; + ts->opaque = http_make_handle (hc_index, hc->version); /* * Alloc session and initialize @@ -295,7 +430,7 @@ http_ts_accept_callback (session_t *ts) hc->c_s_index = as->session_index; as->app_wrk_index = hc->h_pa_wrk_index; - as->connection_index = hc->c_c_index; + as->connection_index = hc->h_hc_index; as->session_state = SESSION_STATE_ACCEPTING; asl = listen_session_get_from_handle (lhc->h_pa_session_handle); @@ -371,19 +506,18 @@ http_ts_connected_callback (u32 http_app_index, u32 ho_hc_index, session_t *ts, hc->timer_handle = HTTP_TIMER_HANDLE_INVALID; hc->c_thread_index = ts->thread_index; hc->h_tc_session_handle = session_handle (ts); - hc->c_c_index = new_hc_index; + hc->h_hc_index = new_hc_index; hc->c_flags |= TRANSPORT_CONNECTION_F_NO_LOOKUP; hc->state = HTTP_CONN_STATE_ESTABLISHED; - http_req_state_change (hc, HTTP_REQ_STATE_WAIT_APP_METHOD); - ts->session_state = SESSION_STATE_READY; - ts->opaque = new_hc_index; + /* TODO: TLS set by ALPN result, TCP: prior knowledge (set in ho) */ + ts->opaque = http_make_handle (new_hc_index, hc->version); /* allocate app session and initialize */ as = session_alloc (hc->c_thread_index); hc->c_s_index = as->session_index; - as->connection_index = hc->c_c_index; + as->connection_index = new_hc_index; as->app_wrk_index = hc->h_pa_wrk_index; as->session_state = SESSION_STATE_READY; as->opaque = hc->h_pa_app_api_ctx; @@ -417,1768 +551,43 @@ static void http_ts_disconnect_callback (session_t *ts) { http_conn_t *hc; + u32 hc_index = http_conn_index_from_handle (ts->opaque); - hc = http_conn_get_w_thread (ts->opaque, ts->thread_index); + HTTP_DBG (1, "hc [%u]%x", ts->thread_index, hc_index); + + hc = http_conn_get_w_thread (hc_index, ts->thread_index); if (hc->state < HTTP_CONN_STATE_TRANSPORT_CLOSED) hc->state = HTTP_CONN_STATE_TRANSPORT_CLOSED; - /* Nothing more to rx, propagate to app */ - if (!svm_fifo_max_dequeue_cons (ts->rx_fifo)) - session_transport_closing_notify (&hc->connection); + http_vfts[hc->version].transport_close_callback (hc); } static void http_ts_reset_callback (session_t *ts) { http_conn_t *hc; + u32 hc_index = http_conn_index_from_handle (ts->opaque); - hc = http_conn_get_w_thread (ts->opaque, ts->thread_index); + HTTP_DBG (1, "hc [%u]%x", ts->thread_index, hc_index); + + hc = http_conn_get_w_thread (hc_index, ts->thread_index); hc->state = HTTP_CONN_STATE_CLOSED; - http_buffer_free (&hc->req.tx_buf); - http_req_state_change (hc, HTTP_REQ_STATE_WAIT_TRANSPORT_METHOD); session_transport_reset_notify (&hc->connection); http_disconnect_transport (hc); } -/** - * http error boilerplate - */ -static const char *http_error_template = "HTTP/1.1 %s\r\n" - "Date: %U GMT\r\n" - "Connection: close\r\n" - "Content-Length: 0\r\n\r\n"; - -/** - * http response boilerplate - */ -static const char *http_response_template = "HTTP/1.1 %s\r\n" - "Date: %U GMT\r\n" - "Server: %v\r\n"; - -static const char *content_len_template = "Content-Length: %llu\r\n"; - -static const char *connection_upgrade_template = "Connection: upgrade\r\n" - "Upgrade: %s\r\n"; - -/** - * http request boilerplate - */ -static const char *http_get_request_template = "GET %s HTTP/1.1\r\n" - "Host: %v\r\n" - "User-Agent: %v\r\n"; - -static const char *http_post_request_template = "POST %s HTTP/1.1\r\n" - "Host: %v\r\n" - "User-Agent: %v\r\n" - "Content-Length: %llu\r\n"; - -static u32 -http_send_data (http_conn_t *hc, u8 *data, u32 length) -{ - const u32 max_burst = 64 << 10; - session_t *ts; - u32 to_send; - int rv; - - ts = session_get_from_handle (hc->h_tc_session_handle); - - to_send = clib_min (length, max_burst); - rv = svm_fifo_enqueue (ts->tx_fifo, to_send, data); - if (rv <= 0) - { - clib_warning ("svm_fifo_enqueue failed, rv %d", rv); - return 0; - } - - if (svm_fifo_set_event (ts->tx_fifo)) - session_program_tx_io_evt (ts->handle, SESSION_IO_EVT_TX); - - return rv; -} - -static void -http_send_error (http_conn_t *hc, http_status_code_t ec) -{ - http_main_t *hm = &http_main; - u8 *data; - f64 now; - - if (ec >= HTTP_N_STATUS) - ec = HTTP_STATUS_INTERNAL_ERROR; - - now = clib_timebase_now (&hm->timebase); - data = format (0, http_error_template, http_status_code_str[ec], - format_clib_timebase_time, now); - HTTP_DBG (3, "%v", data); - http_send_data (hc, data, vec_len (data)); - vec_free (data); -} - -static int -http_read_message (http_conn_t *hc) -{ - u32 max_deq; - session_t *ts; - int n_read; - - ts = session_get_from_handle (hc->h_tc_session_handle); - - max_deq = svm_fifo_max_dequeue (ts->rx_fifo); - if (PREDICT_FALSE (max_deq == 0)) - return -1; - - vec_validate (hc->req.rx_buf, max_deq - 1); - n_read = svm_fifo_peek (ts->rx_fifo, 0, max_deq, hc->req.rx_buf); - ASSERT (n_read == max_deq); - HTTP_DBG (1, "read %u bytes from rx_fifo", n_read); - - return 0; -} - -static void -http_read_message_drop (http_conn_t *hc, u32 len) -{ - session_t *ts; - - ts = session_get_from_handle (hc->h_tc_session_handle); - svm_fifo_dequeue_drop (ts->rx_fifo, len); - vec_reset_length (hc->req.rx_buf); - - if (svm_fifo_is_empty (ts->rx_fifo)) - svm_fifo_unset_event (ts->rx_fifo); -} - -static void -http_read_message_drop_all (http_conn_t *hc) -{ - session_t *ts; - - ts = session_get_from_handle (hc->h_tc_session_handle); - svm_fifo_dequeue_drop_all (ts->rx_fifo); - vec_reset_length (hc->req.rx_buf); - - if (svm_fifo_is_empty (ts->rx_fifo)) - svm_fifo_unset_event (ts->rx_fifo); -} - -/** - * @brief Find the first occurrence of the string in the vector. - * - * @param vec The vector to be scanned. - * @param offset Search offset in the vector. - * @param num Maximum number of characters to be searched if non-zero. - * @param str The string to be searched. - * - * @return @c -1 if the string is not found within the vector; index otherwise. - */ -static inline int -v_find_index (u8 *vec, u32 offset, u32 num, char *str) -{ - int start_index = offset; - u32 slen = (u32) strnlen_s_inline (str, 16); - u32 vlen = vec_len (vec); - - ASSERT (slen > 0); - - if (vlen <= slen) - return -1; - - int end_index = vlen - slen; - if (num) - { - if (num < slen) - return -1; - end_index = clib_min (end_index, offset + num - slen); - } - - for (; start_index <= end_index; start_index++) - { - if (!memcmp (vec + start_index, str, slen)) - return start_index; - } - - return -1; -} - -static void -http_identify_optional_query (http_req_t *req) -{ - int i; - for (i = req->target_path_offset; - i < (req->target_path_offset + req->target_path_len); i++) - { - if (req->rx_buf[i] == '?') - { - req->target_query_offset = i + 1; - req->target_query_len = req->target_path_offset + - req->target_path_len - - req->target_query_offset; - req->target_path_len = - req->target_path_len - req->target_query_len - 1; - break; - } - } -} - -static int -http_parse_target (http_req_t *req) -{ - int i; - u8 *p, *end; - - /* asterisk-form = "*" */ - if ((req->rx_buf[req->target_path_offset] == '*') && - (req->target_path_len == 1)) - { - req->target_form = HTTP_TARGET_ASTERISK_FORM; - /* we do not support OPTIONS request */ - return -1; - } - - /* origin-form = 1*( "/" segment ) [ "?" query ] */ - if (req->rx_buf[req->target_path_offset] == '/') - { - /* drop leading slash */ - req->target_path_len--; - req->target_path_offset++; - req->target_form = HTTP_TARGET_ORIGIN_FORM; - http_identify_optional_query (req); - /* can't be CONNECT method */ - return req->method == HTTP_REQ_CONNECT ? -1 : 0; - } - - /* absolute-form = - * scheme "://" host [ ":" port ] *( "/" segment ) [ "?" query ] */ - if (req->target_path_len > 8 && - !memcmp (req->rx_buf + req->target_path_offset, "http", 4)) - { - req->scheme = HTTP_URL_SCHEME_HTTP; - p = req->rx_buf + req->target_path_offset + 4; - if (*p == 's') - { - p++; - req->scheme = HTTP_URL_SCHEME_HTTPS; - } - if (*p++ == ':') - { - expect_char ('/'); - expect_char ('/'); - req->target_form = HTTP_TARGET_ABSOLUTE_FORM; - req->target_authority_offset = p - req->rx_buf; - req->target_authority_len = 0; - end = req->rx_buf + req->target_path_offset + req->target_path_len; - while (p < end) - { - if (*p == '/') - { - p++; /* drop leading slash */ - req->target_path_offset = p - req->rx_buf; - req->target_path_len = end - p; - break; - } - req->target_authority_len++; - p++; - } - if (!req->target_path_len) - { - clib_warning ("zero length host"); - return -1; - } - http_identify_optional_query (req); - /* can't be CONNECT method */ - return req->method == HTTP_REQ_CONNECT ? -1 : 0; - } - } - - /* authority-form = host ":" port */ - for (i = req->target_path_offset; - i < (req->target_path_offset + req->target_path_len); i++) - { - if ((req->rx_buf[i] == ':') && (isdigit (req->rx_buf[i + 1]))) - { - req->target_authority_len = req->target_path_len; - req->target_path_len = 0; - req->target_authority_offset = req->target_path_offset; - req->target_path_offset = 0; - req->target_form = HTTP_TARGET_AUTHORITY_FORM; - /* "authority-form" is only used for CONNECT requests */ - return req->method == HTTP_REQ_CONNECT ? 0 : -1; - } - } - - return -1; -} - -static int -http_parse_request_line (http_req_t *req, http_status_code_t *ec) -{ - int i, target_len; - u32 next_line_offset, method_offset; - - /* request-line = method SP request-target SP HTTP-version CRLF */ - i = v_find_index (req->rx_buf, 8, 0, "\r\n"); - if (i < 0) - { - clib_warning ("request line incomplete"); - *ec = HTTP_STATUS_BAD_REQUEST; - return -1; - } - HTTP_DBG (2, "request line length: %d", i); - req->control_data_len = i + 2; - next_line_offset = req->control_data_len; - - /* there should be at least one more CRLF */ - if (vec_len (req->rx_buf) < (next_line_offset + 2)) - { - clib_warning ("malformed message, too short"); - *ec = HTTP_STATUS_BAD_REQUEST; - return -1; - } - - /* - * RFC9112 2.2: - * In the interest of robustness, a server that is expecting to receive and - * parse a request-line SHOULD ignore at least one empty line (CRLF) - * received prior to the request-line. - */ - method_offset = req->rx_buf[0] == '\r' && req->rx_buf[1] == '\n' ? 2 : 0; - /* parse method */ - if (!memcmp (req->rx_buf + method_offset, "GET ", 4)) - { - HTTP_DBG (0, "GET method"); - req->method = HTTP_REQ_GET; - req->target_path_offset = method_offset + 4; - } - else if (!memcmp (req->rx_buf + method_offset, "POST ", 5)) - { - HTTP_DBG (0, "POST method"); - req->method = HTTP_REQ_POST; - req->target_path_offset = method_offset + 5; - } - else if (!memcmp (req->rx_buf + method_offset, "CONNECT ", 8)) - { - HTTP_DBG (0, "CONNECT method"); - req->method = HTTP_REQ_CONNECT; - req->upgrade_proto = HTTP_UPGRADE_PROTO_NA; - req->target_path_offset = method_offset + 8; - req->is_tunnel = 1; - } - else - { - if (req->rx_buf[method_offset] - 'A' <= 'Z' - 'A') - { - clib_warning ("method not implemented: %8v", req->rx_buf); - *ec = HTTP_STATUS_NOT_IMPLEMENTED; - return -1; - } - else - { - clib_warning ("not method name: %8v", req->rx_buf); - *ec = HTTP_STATUS_BAD_REQUEST; - return -1; - } - } - - /* find version */ - i = v_find_index (req->rx_buf, next_line_offset - 11, 11, " HTTP/"); - if (i < 0) - { - clib_warning ("HTTP version not present"); - *ec = HTTP_STATUS_BAD_REQUEST; - return -1; - } - /* verify major version */ - if (isdigit (req->rx_buf[i + 6])) - { - if (req->rx_buf[i + 6] != '1') - { - clib_warning ("HTTP major version '%c' not supported", - req->rx_buf[i + 6]); - *ec = HTTP_STATUS_HTTP_VERSION_NOT_SUPPORTED; - return -1; - } - } - else - { - clib_warning ("HTTP major version '%c' is not digit", - req->rx_buf[i + 6]); - *ec = HTTP_STATUS_BAD_REQUEST; - return -1; - } - - /* parse request-target */ - HTTP_DBG (2, "http at %d", i); - target_len = i - req->target_path_offset; - HTTP_DBG (2, "target_len %d", target_len); - if (target_len < 1) - { - clib_warning ("request-target not present"); - *ec = HTTP_STATUS_BAD_REQUEST; - return -1; - } - req->target_path_len = target_len; - req->target_query_offset = 0; - req->target_query_len = 0; - req->target_authority_len = 0; - req->target_authority_offset = 0; - if (http_parse_target (req)) - { - clib_warning ("invalid target"); - *ec = HTTP_STATUS_BAD_REQUEST; - return -1; - } - HTTP_DBG (2, "request-target path length: %u", req->target_path_len); - HTTP_DBG (2, "request-target path offset: %u", req->target_path_offset); - HTTP_DBG (2, "request-target query length: %u", req->target_query_len); - HTTP_DBG (2, "request-target query offset: %u", req->target_query_offset); - - /* set buffer offset to nex line start */ - req->rx_buf_offset = next_line_offset; - - return 0; -} - -#define parse_int(val, mul) \ - do \ - { \ - if (!isdigit (*p)) \ - { \ - clib_warning ("expected digit"); \ - return -1; \ - } \ - val += mul * (*p++ - '0'); \ - } \ - while (0) - -static int -http_parse_status_line (http_req_t *req) -{ - int i; - u32 next_line_offset; - u8 *p, *end; - u16 status_code = 0; - http_main_t *hm = &http_main; - - i = v_find_index (req->rx_buf, 0, 0, "\r\n"); - /* status-line = HTTP-version SP status-code SP [ reason-phrase ] CRLF */ - if (i < 0) - { - clib_warning ("status line incomplete"); - return -1; - } - HTTP_DBG (2, "status line length: %d", i); - if (i < 12) - { - clib_warning ("status line too short (%d)", i); - return -1; - } - req->control_data_len = i + 2; - next_line_offset = req->control_data_len; - p = req->rx_buf; - end = req->rx_buf + i; - - /* there should be at least one more CRLF */ - if (vec_len (req->rx_buf) < (next_line_offset + 2)) - { - clib_warning ("malformed message, too short"); - return -1; - } - - /* parse version */ - expect_char ('H'); - expect_char ('T'); - expect_char ('T'); - expect_char ('P'); - expect_char ('/'); - expect_char ('1'); - expect_char ('.'); - if (!isdigit (*p++)) - { - clib_warning ("invalid HTTP minor version"); - return -1; - } - - /* skip space(s) */ - if (*p != ' ') - { - clib_warning ("no space after HTTP version"); - return -1; - } - do - { - p++; - if (p == end) - { - clib_warning ("no status code"); - return -1; - } - } - while (*p == ' '); - - /* parse status code */ - if ((end - p) < 3) - { - clib_warning ("not enough characters for status code"); - return -1; - } - parse_int (status_code, 100); - parse_int (status_code, 10); - parse_int (status_code, 1); - if (status_code < 100 || status_code > 599) - { - clib_warning ("invalid status code %d", status_code); - return -1; - } - req->status_code = hm->sc_by_u16[status_code]; - HTTP_DBG (0, "status code: %d", status_code); - - /* set buffer offset to nex line start */ - req->rx_buf_offset = next_line_offset; - - return 0; -} - -static int -http_identify_headers (http_req_t *req, http_status_code_t *ec) -{ - int rv; - u8 *p, *end, *name_start, *value_start; - u32 name_len, value_len; - http_field_line_t *field_line; - uword header_index; - - vec_reset_length (req->headers); - req->content_len_header_index = ~0; - req->connection_header_index = ~0; - req->upgrade_header_index = ~0; - req->host_header_index = ~0; - req->headers_offset = req->rx_buf_offset; - - /* check if we have any header */ - if ((req->rx_buf[req->rx_buf_offset] == '\r') && - (req->rx_buf[req->rx_buf_offset + 1] == '\n')) - { - /* just another CRLF -> no headers */ - HTTP_DBG (2, "no headers"); - req->headers_len = 0; - req->control_data_len += 2; - return 0; - } - - end = req->rx_buf + vec_len (req->rx_buf); - p = req->rx_buf + req->rx_buf_offset; - - while (1) - { - rv = _parse_field_name (&p, end, &name_start, &name_len); - if (rv != 0) - { - *ec = HTTP_STATUS_BAD_REQUEST; - return -1; - } - rv = _parse_field_value (&p, end, &value_start, &value_len); - if (rv != 0 || (end - p) < 2) - { - *ec = HTTP_STATUS_BAD_REQUEST; - return -1; - } - - vec_add2 (req->headers, field_line, 1); - field_line->name_offset = - (name_start - req->rx_buf) - req->headers_offset; - field_line->name_len = name_len; - field_line->value_offset = - (value_start - req->rx_buf) - req->headers_offset; - field_line->value_len = value_len; - header_index = field_line - req->headers; - - /* find headers that will be used later in preprocessing */ - /* names are case-insensitive (RFC9110 section 5.1) */ - if (req->content_len_header_index == ~0 && - http_token_is_case ( - (const char *) name_start, name_len, - http_header_name_token (HTTP_HEADER_CONTENT_LENGTH))) - req->content_len_header_index = header_index; - else if (req->connection_header_index == ~0 && - http_token_is_case ( - (const char *) name_start, name_len, - http_header_name_token (HTTP_HEADER_CONNECTION))) - req->connection_header_index = header_index; - else if (req->upgrade_header_index == ~0 && - http_token_is_case ( - (const char *) name_start, name_len, - http_header_name_token (HTTP_HEADER_UPGRADE))) - req->upgrade_header_index = header_index; - else if (req->host_header_index == ~0 && - http_token_is_case ((const char *) name_start, name_len, - http_header_name_token (HTTP_HEADER_HOST))) - req->host_header_index = header_index; - - /* are we done? */ - if (*p == '\r' && *(p + 1) == '\n') - break; - } - - req->headers_len = p - (req->rx_buf + req->headers_offset); - req->control_data_len += (req->headers_len + 2); - HTTP_DBG (2, "headers length: %u", req->headers_len); - HTTP_DBG (2, "headers offset: %u", req->headers_offset); - - return 0; -} - -static int -http_identify_message_body (http_req_t *req, http_status_code_t *ec) -{ - int i; - u8 *p; - u64 body_len = 0, digit; - http_field_line_t *field_line; - - req->body_len = 0; - - if (req->headers_len == 0) - { - HTTP_DBG (2, "no header, no message-body"); - return 0; - } - if (req->is_tunnel) - { - HTTP_DBG (2, "tunnel, no message-body"); - return 0; - } - - /* TODO check for chunked transfer coding */ - - if (req->content_len_header_index == ~0) - { - HTTP_DBG (2, "Content-Length header not present, no message-body"); - return 0; - } - field_line = vec_elt_at_index (req->headers, req->content_len_header_index); - - p = req->rx_buf + req->headers_offset + field_line->value_offset; - for (i = 0; i < field_line->value_len; i++) - { - /* check for digit */ - if (!isdigit (*p)) - { - clib_warning ("expected digit"); - *ec = HTTP_STATUS_BAD_REQUEST; - return -1; - } - digit = *p - '0'; - u64 new_body_len = body_len * 10 + digit; - /* check for overflow */ - if (new_body_len < body_len) - { - clib_warning ("too big number, overflow"); - *ec = HTTP_STATUS_BAD_REQUEST; - return -1; - } - body_len = new_body_len; - p++; - } - - req->body_len = body_len; - - req->body_offset = req->headers_offset + req->headers_len + 2; - HTTP_DBG (2, "body length: %llu", req->body_len); - HTTP_DBG (2, "body offset: %u", req->body_offset); - - return 0; -} - -static http_sm_result_t -http_req_state_wait_transport_reply (http_conn_t *hc, - transport_send_params_t *sp) -{ - int rv; - http_msg_t msg = {}; - app_worker_t *app_wrk; - session_t *as; - u32 len, max_enq, body_sent; - http_status_code_t ec; - - rv = http_read_message (hc); - - /* Nothing yet, wait for data or timer expire */ - if (rv) - { - HTTP_DBG (1, "no data to deq"); - return HTTP_SM_STOP; - } - - HTTP_DBG (3, "%v", hc->req.rx_buf); - - if (vec_len (hc->req.rx_buf) < 8) - { - clib_warning ("response buffer too short"); - goto error; - } - - rv = http_parse_status_line (&hc->req); - if (rv) - goto error; - - rv = http_identify_headers (&hc->req, &ec); - if (rv) - goto error; - - rv = http_identify_message_body (&hc->req, &ec); - if (rv) - goto error; - - /* send at least "control data" which is necessary minimum, - * if there is some space send also portion of body */ - as = session_get_from_handle (hc->h_pa_session_handle); - max_enq = svm_fifo_max_enqueue (as->rx_fifo); - max_enq -= sizeof (msg); - if (max_enq < hc->req.control_data_len) - { - clib_warning ("not enough room for control data in app's rx fifo"); - goto error; - } - len = clib_min (max_enq, vec_len (hc->req.rx_buf)); - - msg.type = HTTP_MSG_REPLY; - msg.code = hc->req.status_code; - msg.data.headers_offset = hc->req.headers_offset; - msg.data.headers_len = hc->req.headers_len; - msg.data.body_offset = hc->req.body_offset; - msg.data.body_len = hc->req.body_len; - msg.data.type = HTTP_MSG_DATA_INLINE; - msg.data.len = len; - msg.data.headers_ctx = pointer_to_uword (hc->req.headers); - - svm_fifo_seg_t segs[2] = { { (u8 *) &msg, sizeof (msg) }, - { hc->req.rx_buf, len } }; - - rv = svm_fifo_enqueue_segments (as->rx_fifo, segs, 2, 0 /* allow partial */); - ASSERT (rv == (sizeof (msg) + len)); - - http_read_message_drop (hc, len); - - body_sent = len - hc->req.control_data_len; - hc->req.to_recv = hc->req.body_len - body_sent; - if (hc->req.to_recv == 0) - { - /* all sent, we are done */ - http_req_state_change (hc, HTTP_REQ_STATE_WAIT_APP_METHOD); - } - else - { - /* stream rest of the response body */ - http_req_state_change (hc, HTTP_REQ_STATE_TRANSPORT_IO_MORE_DATA); - } - - app_wrk = app_worker_get_if_valid (as->app_wrk_index); - if (app_wrk) - app_worker_rx_notify (app_wrk, as); - return HTTP_SM_STOP; - -error: - http_read_message_drop_all (hc); - session_transport_closing_notify (&hc->connection); - session_transport_closed_notify (&hc->connection); - http_disconnect_transport (hc); - return HTTP_SM_ERROR; -} - -#define http_field_line_value_token(_fl, _req) \ - (const char *) ((_req)->rx_buf + (_req)->headers_offset + \ - (_fl)->value_offset), \ - (_fl)->value_len - -static void -http_check_connection_upgrade (http_req_t *req) -{ - http_field_line_t *connection, *upgrade; - u8 skip; - - skip = (req->method != HTTP_REQ_GET) + (req->connection_header_index == ~0) + - (req->upgrade_header_index == ~0); - if (skip) - return; - - connection = vec_elt_at_index (req->headers, req->connection_header_index); - /* connection options are case-insensitive (RFC9110 7.6.1) */ - if (http_token_is_case (http_field_line_value_token (connection, req), - http_token_lit ("upgrade"))) - { - upgrade = vec_elt_at_index (req->headers, req->upgrade_header_index); - - /* check upgrade protocol, we want to ignore something like upgrade to - * newer HTTP version, only tunnels are supported */ - if (0) - ; -#define _(sym, str) \ - else if (http_token_is_case (http_field_line_value_token (upgrade, req), \ - http_token_lit (str))) req->upgrade_proto = \ - HTTP_UPGRADE_PROTO_##sym; - foreach_http_upgrade_proto -#undef _ - else return; - - HTTP_DBG (1, "connection upgrade: %U", format_http_bytes, - req->rx_buf + req->headers_offset + upgrade->value_offset, - upgrade->value_len); - req->is_tunnel = 1; - req->method = HTTP_REQ_CONNECT; - } -} - -static void -http_target_fixup (http_conn_t *hc) -{ - http_field_line_t *host; - - if (hc->req.target_form == HTTP_TARGET_ABSOLUTE_FORM) - return; - - /* scheme fixup */ - hc->req.scheme = session_get_transport_proto (session_get_from_handle ( - hc->h_tc_session_handle)) == TRANSPORT_PROTO_TLS ? - HTTP_URL_SCHEME_HTTPS : - HTTP_URL_SCHEME_HTTP; - - if (hc->req.target_form == HTTP_TARGET_AUTHORITY_FORM || - hc->req.connection_header_index == ~0) - return; - - /* authority fixup */ - host = vec_elt_at_index (hc->req.headers, hc->req.connection_header_index); - hc->req.target_authority_offset = host->value_offset; - hc->req.target_authority_len = host->value_len; -} - -static http_sm_result_t -http_req_state_wait_transport_method (http_conn_t *hc, - transport_send_params_t *sp) -{ - http_status_code_t ec; - app_worker_t *app_wrk; - http_msg_t msg; - session_t *as; - int rv; - u32 len, max_enq, body_sent; - u64 max_deq; - - rv = http_read_message (hc); - - /* Nothing yet, wait for data or timer expire */ - if (rv) - return HTTP_SM_STOP; - - HTTP_DBG (3, "%v", hc->req.rx_buf); - - if (vec_len (hc->req.rx_buf) < 8) - { - ec = HTTP_STATUS_BAD_REQUEST; - goto error; - } - - rv = http_parse_request_line (&hc->req, &ec); - if (rv) - goto error; - - rv = http_identify_headers (&hc->req, &ec); - if (rv) - goto error; - - http_target_fixup (hc); - http_check_connection_upgrade (&hc->req); - - rv = http_identify_message_body (&hc->req, &ec); - if (rv) - goto error; - - /* send at least "control data" which is necessary minimum, - * if there is some space send also portion of body */ - as = session_get_from_handle (hc->h_pa_session_handle); - max_enq = svm_fifo_max_enqueue (as->rx_fifo); - if (max_enq < hc->req.control_data_len) - { - clib_warning ("not enough room for control data in app's rx fifo"); - ec = HTTP_STATUS_INTERNAL_ERROR; - goto error; - } - /* do not dequeue more than one HTTP request, we do not support pipelining */ - max_deq = clib_min (hc->req.control_data_len + hc->req.body_len, - vec_len (hc->req.rx_buf)); - len = clib_min (max_enq, max_deq); - - msg.type = HTTP_MSG_REQUEST; - msg.method_type = hc->req.method; - msg.data.type = HTTP_MSG_DATA_INLINE; - msg.data.len = len; - msg.data.scheme = hc->req.scheme; - msg.data.target_authority_offset = hc->req.target_authority_offset; - msg.data.target_authority_len = hc->req.target_authority_len; - msg.data.target_path_offset = hc->req.target_path_offset; - msg.data.target_path_len = hc->req.target_path_len; - msg.data.target_query_offset = hc->req.target_query_offset; - msg.data.target_query_len = hc->req.target_query_len; - msg.data.headers_offset = hc->req.headers_offset; - msg.data.headers_len = hc->req.headers_len; - msg.data.body_offset = hc->req.body_offset; - msg.data.body_len = hc->req.body_len; - msg.data.headers_ctx = pointer_to_uword (hc->req.headers); - msg.data.upgrade_proto = hc->req.upgrade_proto; - - svm_fifo_seg_t segs[2] = { { (u8 *) &msg, sizeof (msg) }, - { hc->req.rx_buf, len } }; - - rv = svm_fifo_enqueue_segments (as->rx_fifo, segs, 2, 0 /* allow partial */); - ASSERT (rv == (sizeof (msg) + len)); - - body_sent = len - hc->req.control_data_len; - hc->req.to_recv = hc->req.body_len - body_sent; - if (hc->req.to_recv == 0) - { - /* drop everything, we do not support pipelining */ - http_read_message_drop_all (hc); - /* all sent, we are done */ - http_req_state_change (hc, HTTP_REQ_STATE_WAIT_APP_REPLY); - } - else - { - http_read_message_drop (hc, len); - /* stream rest of the response body */ - http_req_state_change (hc, HTTP_REQ_STATE_TRANSPORT_IO_MORE_DATA); - } - - app_wrk = app_worker_get_if_valid (as->app_wrk_index); - if (app_wrk) - app_worker_rx_notify (app_wrk, as); - - return HTTP_SM_STOP; - -error: - http_read_message_drop_all (hc); - http_send_error (hc, ec); - session_transport_closing_notify (&hc->connection); - http_disconnect_transport (hc); - - return HTTP_SM_ERROR; -} - -static void -http_write_app_headers (http_conn_t *hc, http_msg_t *msg, u8 **tx_buf) -{ - http_main_t *hm = &http_main; - session_t *as; - u8 *app_headers, *p, *end; - u32 *tmp; - int rv; - - as = session_get_from_handle (hc->h_pa_session_handle); - - /* read app header list */ - if (msg->data.type == HTTP_MSG_DATA_PTR) - { - uword app_headers_ptr; - rv = svm_fifo_dequeue (as->tx_fifo, sizeof (app_headers_ptr), - (u8 *) &app_headers_ptr); - ASSERT (rv == sizeof (app_headers_ptr)); - app_headers = uword_to_pointer (app_headers_ptr, u8 *); - } - else - { - app_headers = hm->app_header_lists[hc->c_thread_index]; - rv = svm_fifo_dequeue (as->tx_fifo, msg->data.headers_len, app_headers); - ASSERT (rv == msg->data.headers_len); - } - - /* serialize app headers to tx_buf */ - end = app_headers + msg->data.headers_len; - while (app_headers < end) - { - /* custom header name? */ - tmp = (u32 *) app_headers; - if (PREDICT_FALSE (*tmp & HTTP_CUSTOM_HEADER_NAME_BIT)) - { - http_custom_token_t *name, *value; - name = (http_custom_token_t *) app_headers; - u32 name_len = name->len & ~HTTP_CUSTOM_HEADER_NAME_BIT; - app_headers += sizeof (http_custom_token_t) + name_len; - value = (http_custom_token_t *) app_headers; - app_headers += sizeof (http_custom_token_t) + value->len; - vec_add2 (*tx_buf, p, name_len + value->len + 4); - clib_memcpy (p, name->token, name_len); - p += name_len; - *p++ = ':'; - *p++ = ' '; - clib_memcpy (p, value->token, value->len); - p += value->len; - *p++ = '\r'; - *p++ = '\n'; - } - else - { - http_app_header_t *header; - header = (http_app_header_t *) app_headers; - app_headers += sizeof (http_app_header_t) + header->value.len; - http_token_t name = { http_header_name_token (header->name) }; - vec_add2 (*tx_buf, p, name.len + header->value.len + 4); - clib_memcpy (p, name.base, name.len); - p += name.len; - *p++ = ':'; - *p++ = ' '; - clib_memcpy (p, header->value.token, header->value.len); - p += header->value.len; - *p++ = '\r'; - *p++ = '\n'; - } - } -} - -static http_sm_result_t -http_req_state_wait_app_reply (http_conn_t *hc, transport_send_params_t *sp) -{ - http_main_t *hm = &http_main; - u8 *response; - u32 sent; - f64 now; - session_t *as; - http_status_code_t sc; - http_msg_t msg; - int rv; - http_sm_result_t sm_result = HTTP_SM_ERROR; - http_req_state_t next_state = HTTP_REQ_STATE_WAIT_TRANSPORT_METHOD; - - as = session_get_from_handle (hc->h_pa_session_handle); - - rv = svm_fifo_dequeue (as->tx_fifo, sizeof (msg), (u8 *) &msg); - ASSERT (rv == sizeof (msg)); - - if (msg.data.type > HTTP_MSG_DATA_PTR) - { - clib_warning ("no data"); - sc = HTTP_STATUS_INTERNAL_ERROR; - goto error; - } - - if (msg.type != HTTP_MSG_REPLY) - { - clib_warning ("unexpected message type %d", msg.type); - sc = HTTP_STATUS_INTERNAL_ERROR; - goto error; - } - - if (msg.code >= HTTP_N_STATUS) - { - clib_warning ("unsupported status code: %d", msg.code); - return HTTP_SM_ERROR; - } - - response = hm->tx_bufs[hc->c_thread_index]; - vec_reset_length (response); - /* - * Add "protocol layer" headers: - * - current time - * - server name - * - data length - */ - now = clib_timebase_now (&hm->timebase); - response = - format (response, http_response_template, http_status_code_str[msg.code], - /* Date */ - format_clib_timebase_time, now, - /* Server */ - hc->app_name); - - /* RFC9110 8.6: A server MUST NOT send Content-Length header field in a - * 2xx (Successful) response to CONNECT or with a status code of 101 - * (Switching Protocols). */ - if (hc->req.is_tunnel && (http_status_code_str[msg.code][0] == '2' || - msg.code == HTTP_STATUS_SWITCHING_PROTOCOLS)) - { - ASSERT (msg.data.body_len == 0); - next_state = HTTP_REQ_STATE_TUNNEL; - if (hc->req.upgrade_proto > HTTP_UPGRADE_PROTO_NA) - { - response = format (response, connection_upgrade_template, - http_upgrade_proto_str[hc->req.upgrade_proto]); - if (hc->req.upgrade_proto == HTTP_UPGRADE_PROTO_CONNECT_UDP && - hc->udp_tunnel_mode == HTTP_UDP_TUNNEL_DGRAM) - next_state = HTTP_REQ_STATE_UDP_TUNNEL; - } - /* cleanup some stuff we don't need anymore in tunnel mode */ - vec_free (hc->req.rx_buf); - vec_free (hc->req.headers); - http_buffer_free (&hc->req.tx_buf); - hc->req.to_skip = 0; - } - else - response = format (response, content_len_template, msg.data.body_len); - - /* Add headers from app (if any) */ - if (msg.data.headers_len) - { - HTTP_DBG (0, "got headers from app, len %d", msg.data.headers_len); - http_write_app_headers (hc, &msg, &response); - } - /* Add empty line after headers */ - response = format (response, "\r\n"); - HTTP_DBG (3, "%v", response); - - sent = http_send_data (hc, response, vec_len (response)); - if (sent != vec_len (response)) - { - clib_warning ("sending status-line and headers failed!"); - sc = HTTP_STATUS_INTERNAL_ERROR; - goto error; - } - - if (msg.data.body_len) - { - /* Start sending the actual data */ - http_buffer_init (&hc->req.tx_buf, msg_to_buf_type[msg.data.type], - as->tx_fifo, msg.data.body_len); - next_state = HTTP_REQ_STATE_APP_IO_MORE_DATA; - sm_result = HTTP_SM_CONTINUE; - } - else - { - /* No response body, we are done */ - sm_result = HTTP_SM_STOP; - } - - http_req_state_change (hc, next_state); - - ASSERT (sp->max_burst_size >= sent); - sp->max_burst_size -= sent; - return sm_result; - -error: - http_send_error (hc, sc); - session_transport_closing_notify (&hc->connection); - http_disconnect_transport (hc); - return HTTP_SM_STOP; -} - -static http_sm_result_t -http_req_state_wait_app_method (http_conn_t *hc, transport_send_params_t *sp) -{ - http_main_t *hm = &http_main; - http_msg_t msg; - session_t *as; - u8 *target_buff = 0, *request = 0, *target; - u32 sent; - int rv; - http_sm_result_t sm_result = HTTP_SM_ERROR; - http_req_state_t next_state; - - as = session_get_from_handle (hc->h_pa_session_handle); - - rv = svm_fifo_dequeue (as->tx_fifo, sizeof (msg), (u8 *) &msg); - ASSERT (rv == sizeof (msg)); - - if (msg.data.type > HTTP_MSG_DATA_PTR) - { - clib_warning ("no data"); - goto error; - } - - if (msg.type != HTTP_MSG_REQUEST) - { - clib_warning ("unexpected message type %d", msg.type); - goto error; - } - - /* read request target */ - if (msg.data.type == HTTP_MSG_DATA_PTR) - { - uword target_ptr; - rv = svm_fifo_dequeue (as->tx_fifo, sizeof (target_ptr), - (u8 *) &target_ptr); - ASSERT (rv == sizeof (target_ptr)); - target = uword_to_pointer (target_ptr, u8 *); - } - else - { - vec_validate (target_buff, msg.data.target_path_len - 1); - rv = - svm_fifo_dequeue (as->tx_fifo, msg.data.target_path_len, target_buff); - ASSERT (rv == msg.data.target_path_len); - target = target_buff; - } - - request = hm->tx_bufs[hc->c_thread_index]; - vec_reset_length (request); - /* currently we support only GET and POST method */ - if (msg.method_type == HTTP_REQ_GET) - { - if (msg.data.body_len) - { - clib_warning ("GET request shouldn't include data"); - goto error; - } - /* - * Add "protocol layer" headers: - * - host - * - user agent - */ - request = format (request, http_get_request_template, - /* target */ - target, - /* Host */ - hc->host, - /* User-Agent */ - hc->app_name); - - next_state = HTTP_REQ_STATE_WAIT_TRANSPORT_REPLY; - sm_result = HTTP_SM_STOP; - } - else if (msg.method_type == HTTP_REQ_POST) - { - if (!msg.data.body_len) - { - clib_warning ("POST request should include data"); - goto error; - } - /* - * Add "protocol layer" headers: - * - host - * - user agent - * - content length - */ - request = format (request, http_post_request_template, - /* target */ - target, - /* Host */ - hc->host, - /* User-Agent */ - hc->app_name, - /* Content-Length */ - msg.data.body_len); - - http_buffer_init (&hc->req.tx_buf, msg_to_buf_type[msg.data.type], - as->tx_fifo, msg.data.body_len); - - next_state = HTTP_REQ_STATE_APP_IO_MORE_DATA; - sm_result = HTTP_SM_CONTINUE; - } - else - { - clib_warning ("unsupported method %d", msg.method_type); - goto error; - } - - /* Add headers from app (if any) */ - if (msg.data.headers_len) - { - HTTP_DBG (0, "got headers from app, len %d", msg.data.headers_len); - http_write_app_headers (hc, &msg, &request); - } - /* Add empty line after headers */ - request = format (request, "\r\n"); - HTTP_DBG (3, "%v", request); - - sent = http_send_data (hc, request, vec_len (request)); - if (sent != vec_len (request)) - { - clib_warning ("sending request-line and headers failed!"); - sm_result = HTTP_SM_ERROR; - goto error; - } - - http_req_state_change (hc, next_state); - goto done; - -error: - svm_fifo_dequeue_drop_all (as->tx_fifo); - session_transport_closing_notify (&hc->connection); - session_transport_closed_notify (&hc->connection); - http_disconnect_transport (hc); - -done: - vec_free (target_buff); - return sm_result; -} - -static http_sm_result_t -http_req_state_transport_io_more_data (http_conn_t *hc, - transport_send_params_t *sp) -{ - session_t *as, *ts; - app_worker_t *app_wrk; - svm_fifo_seg_t _seg, *seg = &_seg; - u32 max_len, max_deq, max_enq, n_segs = 1; - int rv, len; - - as = session_get_from_handle (hc->h_pa_session_handle); - ts = session_get_from_handle (hc->h_tc_session_handle); - - max_deq = svm_fifo_max_dequeue (ts->rx_fifo); - if (max_deq == 0) - { - HTTP_DBG (1, "no data to deq"); - return HTTP_SM_STOP; - } - - max_enq = svm_fifo_max_enqueue (as->rx_fifo); - if (max_enq == 0) - { - HTTP_DBG (1, "app's rx fifo full"); - svm_fifo_add_want_deq_ntf (as->rx_fifo, SVM_FIFO_WANT_DEQ_NOTIF); - return HTTP_SM_STOP; - } - - max_len = clib_min (max_enq, max_deq); - len = svm_fifo_segments (ts->rx_fifo, 0, seg, &n_segs, max_len); - if (len < 0) - { - HTTP_DBG (1, "svm_fifo_segments() len %d", len); - return HTTP_SM_STOP; - } - - rv = svm_fifo_enqueue_segments (as->rx_fifo, seg, 1, 0 /* allow partial */); - if (rv < 0) - { - clib_warning ("data enqueue failed, rv: %d", rv); - return HTTP_SM_ERROR; - } - - svm_fifo_dequeue_drop (ts->rx_fifo, rv); - if (rv > hc->req.to_recv) - { - clib_warning ("http protocol error: received more data than expected"); - session_transport_closing_notify (&hc->connection); - http_disconnect_transport (hc); - http_req_state_change (hc, HTTP_REQ_STATE_WAIT_APP_METHOD); - return HTTP_SM_ERROR; - } - hc->req.to_recv -= rv; - HTTP_DBG (1, "drained %d from ts; remains %lu", rv, hc->req.to_recv); - - /* Finished transaction: - * server back to HTTP_REQ_STATE_WAIT_APP_REPLY - * client to HTTP_REQ_STATE_WAIT_APP_METHOD */ - if (hc->req.to_recv == 0) - http_req_state_change (hc, hc->is_server ? HTTP_REQ_STATE_WAIT_APP_REPLY : - HTTP_REQ_STATE_WAIT_APP_METHOD); - - app_wrk = app_worker_get_if_valid (as->app_wrk_index); - if (app_wrk) - app_worker_rx_notify (app_wrk, as); - - if (svm_fifo_max_dequeue_cons (ts->rx_fifo)) - session_enqueue_notify (ts); - - return HTTP_SM_STOP; -} - -static http_sm_result_t -http_req_state_app_io_more_data (http_conn_t *hc, transport_send_params_t *sp) -{ - u32 max_send = 64 << 10, n_segs; - http_buffer_t *hb = &hc->req.tx_buf; - svm_fifo_seg_t *seg; - session_t *ts; - int sent = 0; - - max_send = clib_min (max_send, sp->max_burst_size); - ts = session_get_from_handle (hc->h_tc_session_handle); - if ((seg = http_buffer_get_segs (hb, max_send, &n_segs))) - sent = svm_fifo_enqueue_segments (ts->tx_fifo, seg, n_segs, - 1 /* allow partial */); - - if (sent > 0) - { - /* Ask scheduler to notify app of deq event if needed */ - sp->bytes_dequeued += http_buffer_drain (hb, sent); - sp->max_burst_size -= sent; - } - - /* Not finished sending all data */ - if (!http_buffer_is_drained (hb)) - { - if (sent && svm_fifo_set_event (ts->tx_fifo)) - session_program_tx_io_evt (ts->handle, SESSION_IO_EVT_TX); - - if (svm_fifo_max_enqueue (ts->tx_fifo) < HTTP_FIFO_THRESH) - { - /* Deschedule http session and wait for deq notification if - * underlying ts tx fifo almost full */ - svm_fifo_add_want_deq_ntf (ts->tx_fifo, SVM_FIFO_WANT_DEQ_NOTIF); - transport_connection_deschedule (&hc->connection); - sp->flags |= TRANSPORT_SND_F_DESCHED; - } - } - else - { - if (sent && svm_fifo_set_event (ts->tx_fifo)) - session_program_tx_io_evt (ts->handle, SESSION_IO_EVT_TX_FLUSH); - - /* Finished transaction: - * server back to HTTP_REQ_STATE_WAIT_TRANSPORT_METHOD - * client to HTTP_REQ_STATE_WAIT_TRANSPORT_REPLY */ - http_req_state_change (hc, hc->is_server ? - HTTP_REQ_STATE_WAIT_TRANSPORT_METHOD : - HTTP_REQ_STATE_WAIT_TRANSPORT_REPLY); - http_buffer_free (hb); - } - - return HTTP_SM_STOP; -} - -static http_sm_result_t -http_req_state_tunnel_rx (http_conn_t *hc, transport_send_params_t *sp) -{ - u32 max_deq, max_enq, max_read, n_segs = 2; - svm_fifo_seg_t segs[n_segs]; - int n_written = 0; - session_t *as, *ts; - app_worker_t *app_wrk; - - HTTP_DBG (1, "tunnel received data from client"); - - as = session_get_from_handle (hc->h_pa_session_handle); - ts = session_get_from_handle (hc->h_tc_session_handle); - - max_deq = svm_fifo_max_dequeue (ts->rx_fifo); - if (PREDICT_FALSE (max_deq == 0)) - { - HTTP_DBG (1, "max_deq == 0"); - return HTTP_SM_STOP; - } - max_enq = svm_fifo_max_enqueue (as->rx_fifo); - if (max_enq == 0) - { - HTTP_DBG (1, "app's rx fifo full"); - svm_fifo_add_want_deq_ntf (as->rx_fifo, SVM_FIFO_WANT_DEQ_NOTIF); - return HTTP_SM_STOP; - } - max_read = clib_min (max_enq, max_deq); - svm_fifo_segments (ts->rx_fifo, 0, segs, &n_segs, max_read); - n_written = svm_fifo_enqueue_segments (as->rx_fifo, segs, n_segs, 0); - ASSERT (n_written > 0); - HTTP_DBG (1, "transfered %u bytes", n_written); - svm_fifo_dequeue_drop (ts->rx_fifo, n_written); - app_wrk = app_worker_get_if_valid (as->app_wrk_index); - if (app_wrk) - app_worker_rx_notify (app_wrk, as); - if (svm_fifo_max_dequeue_cons (ts->rx_fifo)) - session_program_rx_io_evt (session_handle (ts)); - - return HTTP_SM_STOP; -} - -static http_sm_result_t -http_req_state_tunnel_tx (http_conn_t *hc, transport_send_params_t *sp) -{ - u32 max_deq, max_enq, max_read, n_segs = 2; - svm_fifo_seg_t segs[n_segs]; - session_t *as, *ts; - int n_written = 0; - - HTTP_DBG (1, "tunnel received data from target"); - - as = session_get_from_handle (hc->h_pa_session_handle); - ts = session_get_from_handle (hc->h_tc_session_handle); - - max_deq = svm_fifo_max_dequeue_cons (as->tx_fifo); - if (PREDICT_FALSE (max_deq == 0)) - { - HTTP_DBG (1, "max_deq == 0"); - goto check_fifo; - } - max_enq = svm_fifo_max_enqueue_prod (ts->tx_fifo); - if (max_enq == 0) - { - HTTP_DBG (1, "ts tx fifo full"); - goto check_fifo; - } - max_read = clib_min (max_enq, max_deq); - max_read = clib_min (max_read, sp->max_burst_size); - svm_fifo_segments (as->tx_fifo, 0, segs, &n_segs, max_read); - n_written = svm_fifo_enqueue_segments (ts->tx_fifo, segs, n_segs, 0); - ASSERT (n_written > 0); - HTTP_DBG (1, "transfered %u bytes", n_written); - sp->bytes_dequeued += n_written; - sp->max_burst_size -= n_written; - svm_fifo_dequeue_drop (as->tx_fifo, n_written); - if (svm_fifo_set_event (ts->tx_fifo)) - session_program_tx_io_evt (ts->handle, SESSION_IO_EVT_TX); - -check_fifo: - /* Deschedule and wait for deq notification if ts fifo is almost full */ - if (svm_fifo_max_enqueue (ts->tx_fifo) < HTTP_FIFO_THRESH) - { - svm_fifo_add_want_deq_ntf (ts->tx_fifo, SVM_FIFO_WANT_DEQ_NOTIF); - transport_connection_deschedule (&hc->connection); - sp->flags |= TRANSPORT_SND_F_DESCHED; - } - - return HTTP_SM_STOP; -} - -static http_sm_result_t -http_req_state_udp_tunnel_rx (http_conn_t *hc, transport_send_params_t *sp) -{ - http_main_t *hm = &http_main; - u32 to_deq, capsule_size, dgram_size, n_written = 0; - int rv, n_read; - session_t *as, *ts; - app_worker_t *app_wrk; - u8 payload_offset; - u64 payload_len; - session_dgram_hdr_t hdr; - u8 *buf = 0; - - HTTP_DBG (1, "udp tunnel received data from client"); - - as = session_get_from_handle (hc->h_pa_session_handle); - ts = session_get_from_handle (hc->h_tc_session_handle); - buf = hm->rx_bufs[hc->c_thread_index]; - to_deq = svm_fifo_max_dequeue_cons (ts->rx_fifo); - - while (to_deq > 0) - { - /* some bytes remaining to skip? */ - if (PREDICT_FALSE (hc->req.to_skip)) - { - if (hc->req.to_skip >= to_deq) - { - svm_fifo_dequeue_drop (ts->rx_fifo, to_deq); - hc->req.to_skip -= to_deq; - goto done; - } - else - { - svm_fifo_dequeue_drop (ts->rx_fifo, hc->req.to_skip); - hc->req.to_skip = 0; - } - } - n_read = - svm_fifo_peek (ts->rx_fifo, 0, HTTP_CAPSULE_HEADER_MAX_SIZE, buf); - ASSERT (n_read > 0); - rv = http_decap_udp_payload_datagram (buf, n_read, &payload_offset, - &payload_len); - HTTP_DBG (1, "rv=%d, payload_offset=%u, payload_len=%llu", rv, - payload_offset, payload_len); - if (PREDICT_FALSE (rv != 0)) - { - if (rv < 0) - { - /* capsule datagram is invalid (session need to be aborted) */ - svm_fifo_dequeue_drop_all (ts->rx_fifo); - session_transport_closing_notify (&hc->connection); - session_transport_closed_notify (&hc->connection); - http_disconnect_transport (hc); - return HTTP_SM_STOP; - } - else - { - /* unknown capsule should be skipped */ - if (payload_len <= to_deq) - { - svm_fifo_dequeue_drop (ts->rx_fifo, payload_len); - to_deq -= payload_len; - continue; - } - else - { - svm_fifo_dequeue_drop (ts->rx_fifo, to_deq); - hc->req.to_skip = payload_len - to_deq; - goto done; - } - } - } - capsule_size = payload_offset + payload_len; - /* check if we have the full capsule */ - if (PREDICT_FALSE (to_deq < capsule_size)) - { - HTTP_DBG (1, "capsule not complete"); - goto done; - } - - dgram_size = sizeof (hdr) + payload_len; - if (svm_fifo_max_enqueue_prod (as->rx_fifo) < dgram_size) - { - HTTP_DBG (1, "app's rx fifo full"); - svm_fifo_add_want_deq_ntf (as->rx_fifo, SVM_FIFO_WANT_DEQ_NOTIF); - goto done; - } - - /* read capsule payload */ - rv = svm_fifo_peek (ts->rx_fifo, payload_offset, payload_len, buf); - ASSERT (rv == payload_len); - svm_fifo_dequeue_drop (ts->rx_fifo, capsule_size); - - hdr.data_length = payload_len; - hdr.data_offset = 0; - - /* send datagram header and payload */ - svm_fifo_seg_t segs[2] = { { (u8 *) &hdr, sizeof (hdr) }, - { buf, payload_len } }; - rv = svm_fifo_enqueue_segments (as->rx_fifo, segs, 2, 0); - ASSERT (rv > 0); - - n_written += dgram_size; - to_deq -= capsule_size; - } - -done: - HTTP_DBG (1, "written %lu bytes", n_written); - - if (n_written) - { - app_wrk = app_worker_get_if_valid (as->app_wrk_index); - if (app_wrk) - app_worker_rx_notify (app_wrk, as); - } - if (svm_fifo_max_dequeue_cons (ts->rx_fifo)) - session_program_rx_io_evt (session_handle (ts)); - - return HTTP_SM_STOP; -} - -static http_sm_result_t -http_req_state_udp_tunnel_tx (http_conn_t *hc, transport_send_params_t *sp) -{ - http_main_t *hm = &http_main; - u32 to_deq, capsule_size, dgram_size, n_written = 0; - session_t *as, *ts; - int rv; - session_dgram_pre_hdr_t hdr; - u8 *buf; - u8 *payload; - - HTTP_DBG (1, "udp tunnel received data from target"); - - as = session_get_from_handle (hc->h_pa_session_handle); - ts = session_get_from_handle (hc->h_tc_session_handle); - buf = hm->tx_bufs[hc->c_thread_index]; - to_deq = svm_fifo_max_dequeue_cons (as->tx_fifo); - - while (to_deq > 0) - { - /* read datagram header */ - rv = svm_fifo_peek (as->tx_fifo, 0, sizeof (hdr), (u8 *) &hdr); - ASSERT (rv == sizeof (hdr) && - hdr.data_length <= HTTP_UDP_PAYLOAD_MAX_LEN); - ASSERT (to_deq >= hdr.data_length + SESSION_CONN_HDR_LEN); - dgram_size = hdr.data_length + SESSION_CONN_HDR_LEN; - - if (svm_fifo_max_enqueue_prod (ts->tx_fifo) < - (hdr.data_length + HTTP_UDP_PROXY_DATAGRAM_CAPSULE_OVERHEAD)) - { - HTTP_DBG (1, "ts tx fifo full"); - goto done; - } - - /* create capsule header */ - payload = http_encap_udp_payload_datagram (buf, hdr.data_length); - capsule_size = (payload - buf) + hdr.data_length; - /* read payload */ - rv = svm_fifo_peek (as->tx_fifo, SESSION_CONN_HDR_LEN, hdr.data_length, - payload); - ASSERT (rv == hdr.data_length); - svm_fifo_dequeue_drop (as->tx_fifo, dgram_size); - /* send capsule */ - rv = svm_fifo_enqueue (ts->tx_fifo, capsule_size, buf); - ASSERT (rv == capsule_size); - - n_written += capsule_size; - to_deq -= dgram_size; - } - -done: - HTTP_DBG (1, "written %lu bytes", n_written); - if (n_written) - { - if (svm_fifo_set_event (ts->tx_fifo)) - session_program_tx_io_evt (ts->handle, SESSION_IO_EVT_TX); - } - - /* Deschedule and wait for deq notification if ts fifo is almost full */ - if (svm_fifo_max_enqueue (ts->tx_fifo) < HTTP_FIFO_THRESH) - { - svm_fifo_add_want_deq_ntf (ts->tx_fifo, SVM_FIFO_WANT_DEQ_NOTIF); - transport_connection_deschedule (&hc->connection); - sp->flags |= TRANSPORT_SND_F_DESCHED; - } - - return HTTP_SM_STOP; -} - -typedef http_sm_result_t (*http_sm_handler) (http_conn_t *, - transport_send_params_t *sp); - -static http_sm_handler tx_state_funcs[HTTP_REQ_N_STATES] = { - 0, /* idle */ - http_req_state_wait_app_method, - 0, /* wait transport reply */ - 0, /* transport io more data */ - 0, /* wait transport method */ - http_req_state_wait_app_reply, - http_req_state_app_io_more_data, - http_req_state_tunnel_tx, - http_req_state_udp_tunnel_tx, -}; - -static_always_inline int -http_req_state_is_tx_valid (http_conn_t *hc) -{ - return tx_state_funcs[hc->req.state] ? 1 : 0; -} - -static http_sm_handler rx_state_funcs[HTTP_REQ_N_STATES] = { - 0, /* idle */ - 0, /* wait app method */ - http_req_state_wait_transport_reply, - http_req_state_transport_io_more_data, - http_req_state_wait_transport_method, - 0, /* wait app reply */ - 0, /* app io more data */ - http_req_state_tunnel_rx, - http_req_state_udp_tunnel_rx, -}; - -static_always_inline int -http_req_state_is_rx_valid (http_conn_t *hc) -{ - return rx_state_funcs[hc->req.state] ? 1 : 0; -} - -static_always_inline void -http_req_run_state_machine (http_conn_t *hc, transport_send_params_t *sp, - u8 is_tx) -{ - http_sm_result_t res; - - do - { - if (is_tx) - res = tx_state_funcs[hc->req.state](hc, sp); - else - res = rx_state_funcs[hc->req.state](hc, sp); - if (res == HTTP_SM_ERROR) - { - HTTP_DBG (1, "error in state machine %d", res); - return; - } - } - while (res == HTTP_SM_CONTINUE); - - /* Reset the session expiration timer */ - http_conn_timer_update (hc); -} - static int http_ts_rx_callback (session_t *ts) { http_conn_t *hc; + u32 hc_index = http_conn_index_from_handle (ts->opaque); - HTTP_DBG (1, "hc [%u]%x", ts->thread_index, ts->opaque); + HTTP_DBG (1, "hc [%u]%x", ts->thread_index, hc_index); - hc = http_conn_get_w_thread (ts->opaque, ts->thread_index); + hc = http_conn_get_w_thread (hc_index, ts->thread_index); if (hc->state == HTTP_CONN_STATE_CLOSED) { @@ -2187,18 +596,7 @@ http_ts_rx_callback (session_t *ts) return 0; } - if (!http_req_state_is_rx_valid (hc)) - { - clib_warning ("hc [%u]%x invalid rx state: http req state " - "'%U', session state '%U'", - ts->thread_index, ts->opaque, format_http_req_state, - hc->req.state, format_http_conn_state, hc); - svm_fifo_dequeue_drop_all (ts->rx_fifo); - return 0; - } - - HTTP_DBG (1, "run state machine"); - http_req_run_state_machine (hc, 0, 0); + http_vfts[http_version_from_handle (ts->opaque)].transport_rx_callback (hc); if (hc->state == HTTP_CONN_STATE_TRANSPORT_CLOSED) { @@ -2213,7 +611,8 @@ http_ts_builtin_tx_callback (session_t *ts) { http_conn_t *hc; - hc = http_conn_get_w_thread (ts->opaque, ts->thread_index); + hc = http_conn_get_w_thread (http_conn_index_from_handle (ts->opaque), + ts->thread_index); HTTP_DBG (1, "transport connection reschedule"); transport_connection_reschedule (&hc->connection); @@ -2224,18 +623,24 @@ static void http_ts_cleanup_callback (session_t *ts, session_cleanup_ntf_t ntf) { http_conn_t *hc; + http_req_t *req; + u32 hc_index; if (ntf == SESSION_CLEANUP_TRANSPORT) return; - hc = http_conn_get_w_thread (ts->opaque, ts->thread_index); - - HTTP_DBG (1, "going to free hc [%u]%x", ts->thread_index, ts->opaque); + hc_index = http_conn_index_from_handle (ts->opaque); + hc = http_conn_get_w_thread (hc_index, ts->thread_index); - vec_free (hc->req.rx_buf); - vec_free (hc->req.headers); + HTTP_DBG (1, "going to free hc [%u]%x", ts->thread_index, hc_index); - http_buffer_free (&hc->req.tx_buf); + pool_foreach (req, hc->req_pool) + { + vec_free (req->headers); + vec_free (req->target); + http_buffer_free (&req->tx_buf); + } + pool_free (hc->req_pool); if (hc->pending_timer == 0) http_conn_timer_stop (hc); @@ -2254,8 +659,9 @@ static void http_ts_ho_cleanup_callback (session_t *ts) { http_conn_t *ho_hc; - HTTP_DBG (1, "half open: %x", ts->opaque); - ho_hc = http_ho_conn_get (ts->opaque); + u32 ho_hc_index = http_conn_index_from_handle (ts->opaque); + HTTP_DBG (1, "half open: %x", ho_hc_index); + ho_hc = http_ho_conn_get (ho_hc_index); session_half_open_delete_notify (&ho_hc->connection); http_ho_conn_free (ho_hc); } @@ -2286,6 +692,10 @@ static session_cb_vft_t http_app_cb_vft = { .builtin_app_tx_callback = http_ts_builtin_tx_callback, }; +/*********************************/ +/* transport proto VFT callbacks */ +/*********************************/ + static clib_error_t * http_transport_enable (vlib_main_t *vm, u8 is_en) { @@ -2380,6 +790,8 @@ http_transport_connect (transport_endpoint_cfg_t *tep) hc->h_pa_wrk_index = sep->app_wrk_index; hc->h_pa_app_api_ctx = sep->opaque; hc->state = HTTP_CONN_STATE_CONNECTING; + /* TODO: set to HTTP_VERSION_NA in case of TLS (when supported) */ + hc->version = HTTP_VERSION_1; cargs->api_context = hc_index; ext_cfg = session_endpoint_get_ext_cfg (sep, TRANSPORT_ENDPT_EXT_CFG_HTTP); @@ -2518,7 +930,6 @@ http_stop_listen (u32 listener_index) static void http_transport_close (u32 hc_index, u32 thread_index) { - session_t *as; http_conn_t *hc; HTTP_DBG (1, "App disconnecting [%u]%x", thread_index, hc_index); @@ -2535,19 +946,25 @@ http_transport_close (u32 hc_index, u32 thread_index) HTTP_DBG (1, "nothing to do, already closed"); return; } - as = session_get_from_handle (hc->h_pa_session_handle); - /* Nothing more to send, confirm close */ - if (!svm_fifo_max_dequeue_cons (as->tx_fifo)) - { - session_transport_closed_notify (&hc->connection); - http_disconnect_transport (hc); - } - else + http_vfts[hc->version].app_close_callback (hc); +} + +static void +http_transport_reset (u32 hc_index, u32 thread_index) +{ + http_conn_t *hc; + + HTTP_DBG (1, "App disconnecting [%u]%x", thread_index, hc_index); + + hc = http_conn_get_w_thread (hc_index, thread_index); + if (hc->state == HTTP_CONN_STATE_CLOSED) { - /* Wait for all data to be written to ts */ - hc->state = HTTP_CONN_STATE_APP_CLOSED; + HTTP_DBG (1, "nothing to do, already closed"); + return; } + + http_vfts[hc->version].app_reset_callback (hc); } static transport_connection_t * @@ -2585,30 +1002,7 @@ http_app_tx_callback (void *session, transport_send_params_t *sp) max_burst_sz = sp->max_burst_size * TRANSPORT_PACER_MIN_MSS; sp->max_burst_size = max_burst_sz; - if (!http_req_state_is_tx_valid (hc)) - { - /* Sometimes the server apps can send the response earlier - * than expected (e.g when rejecting a bad request)*/ - if (hc->req.state == HTTP_REQ_STATE_TRANSPORT_IO_MORE_DATA && - hc->is_server) - { - svm_fifo_dequeue_drop_all (as->rx_fifo); - hc->req.state = HTTP_REQ_STATE_WAIT_APP_REPLY; - } - else - { - clib_warning ("hc [%u]%x invalid tx state: http req state " - "'%U', session state '%U'", - as->thread_index, as->connection_index, - format_http_req_state, hc->req.state, - format_http_conn_state, hc); - svm_fifo_dequeue_drop_all (as->tx_fifo); - return 0; - } - } - - HTTP_DBG (1, "run state machine"); - http_req_run_state_machine (hc, sp, 1); + http_vfts[hc->version].app_tx_callback (hc, sp); if (hc->state == HTTP_CONN_STATE_APP_CLOSED) { @@ -2630,8 +1024,7 @@ http_app_rx_evt_cb (transport_connection_t *tc) http_conn_t *hc = (http_conn_t *) tc; HTTP_DBG (1, "hc [%u]%x", vlib_get_thread_index (), hc->h_hc_index); - if (hc->req.state == HTTP_REQ_STATE_TUNNEL) - http_req_state_tunnel_rx (hc, 0); + http_vfts[hc->version].app_rx_evt_callback (hc); return 0; } @@ -2759,6 +1152,7 @@ static const transport_proto_vft_t http_proto = { .start_listen = http_start_listen, .stop_listen = http_stop_listen, .close = http_transport_close, + .reset = http_transport_reset, .cleanup_ho = http_transport_cleanup_ho, .custom_tx = http_app_tx_callback, .app_rx_evt = http_app_rx_evt_cb, diff --git a/src/plugins/http/http.h b/src/plugins/http/http.h index d1e81ab0617..178bdd14881 100644 --- a/src/plugins/http/http.h +++ b/src/plugins/http/http.h @@ -17,15 +17,9 @@ #define SRC_PLUGINS_HTTP_HTTP_H_ #include <ctype.h> - #include <vnet/plugin/plugin.h> -#include <vpp/app/version.h> - -#include <vppinfra/time_range.h> - -#include <vnet/session/application_interface.h> -#include <vnet/session/application.h> -#include <http/http_buffer.h> +#include <vnet/ip/format.h> +#include <vnet/ip/ip46_address.h> #define HTTP_DEBUG 0 @@ -49,20 +43,6 @@ typedef struct transport_endpt_cfg_http http_udp_tunnel_mode_t udp_tunnel_mode; /**< connect-udp mode */ } transport_endpt_cfg_http_t; -typedef struct http_conn_id_ -{ - union - { - session_handle_t app_session_handle; - u32 parent_app_api_ctx; - }; - session_handle_t tc_session_handle; - u32 parent_app_wrk_index; -} http_conn_id_t; - -STATIC_ASSERT (sizeof (http_conn_id_t) <= TRANSPORT_CONN_ID_LEN, - "ctx id must be less than TRANSPORT_CONN_ID_LEN"); - typedef struct { char *base; @@ -71,40 +51,6 @@ typedef struct #define http_token_lit(s) (s), sizeof (s) - 1 -#define foreach_http_conn_state \ - _ (LISTEN, "LISTEN") \ - _ (CONNECTING, "CONNECTING") \ - _ (ESTABLISHED, "ESTABLISHED") \ - _ (TRANSPORT_CLOSED, "TRANSPORT-CLOSED") \ - _ (APP_CLOSED, "APP-CLOSED") \ - _ (CLOSED, "CLOSED") - -typedef enum http_conn_state_ -{ -#define _(s, str) HTTP_CONN_STATE_##s, - foreach_http_conn_state -#undef _ -} http_conn_state_t; - -#define foreach_http_req_state \ - _ (0, IDLE, "idle") \ - _ (1, WAIT_APP_METHOD, "wait app method") \ - _ (2, WAIT_TRANSPORT_REPLY, "wait transport reply") \ - _ (3, TRANSPORT_IO_MORE_DATA, "transport io more data") \ - _ (4, WAIT_TRANSPORT_METHOD, "wait transport method") \ - _ (5, WAIT_APP_REPLY, "wait app reply") \ - _ (6, APP_IO_MORE_DATA, "app io more data") \ - _ (7, TUNNEL, "tunnel") \ - _ (8, UDP_TUNNEL, "udp tunnel") - -typedef enum http_req_state_ -{ -#define _(n, s, str) HTTP_REQ_STATE_##s = n, - foreach_http_req_state -#undef _ - HTTP_REQ_N_STATES -} http_req_state_t; - typedef enum http_req_method_ { HTTP_REQ_GET = 0, @@ -118,14 +64,6 @@ typedef enum http_msg_type_ HTTP_MSG_REPLY } http_msg_type_t; -typedef enum http_target_form_ -{ - HTTP_TARGET_ORIGIN_FORM, - HTTP_TARGET_ABSOLUTE_FORM, - HTTP_TARGET_AUTHORITY_FORM, - HTTP_TARGET_ASTERISK_FORM -} http_target_form_t; - #define foreach_http_content_type \ _ (APP_7Z, ".7z", "application/x-7z-compressed") \ _ (APP_DOC, ".doc", "application/msword") \ @@ -432,118 +370,6 @@ typedef struct http_msg_ http_msg_data_t data; } http_msg_t; -typedef struct http_req_ -{ - http_req_state_t state; /* state-machine state */ - - http_buffer_t tx_buf; /* message body from app to be sent */ - - /* - * for parsing of incoming message from transport - */ - u8 *rx_buf; /* this should hold at least control data */ - u32 rx_buf_offset; /* current offset during parsing */ - u32 control_data_len; /* start line + headers + empty line */ - - union - { - u64 to_recv; /* remaining bytes of body to receive from transport */ - u64 to_skip; /* remaining bytes of capsule to skip */ - }; - - u8 is_tunnel; - - /* - * parsed metadata for app - */ - union - { - http_status_code_t status_code; - http_req_method_t method; - }; - - http_target_form_t target_form; - http_url_scheme_t scheme; - u32 target_authority_offset; - u32 target_authority_len; - u32 target_path_offset; - u32 target_path_len; - u32 target_query_offset; - u32 target_query_len; - - u32 headers_offset; - u32 headers_len; - - u32 body_offset; - u64 body_len; - - http_field_line_t *headers; - uword content_len_header_index; - uword connection_header_index; - uword upgrade_header_index; - uword host_header_index; - - http_upgrade_proto_t upgrade_proto; -} http_req_t; - -typedef struct http_tc_ -{ - union - { - transport_connection_t connection; - http_conn_id_t c_http_conn_id; - }; -#define h_tc_session_handle c_http_conn_id.tc_session_handle -#define h_pa_wrk_index c_http_conn_id.parent_app_wrk_index -#define h_pa_session_handle c_http_conn_id.app_session_handle -#define h_pa_app_api_ctx c_http_conn_id.parent_app_api_ctx -#define h_hc_index connection.c_index - - http_conn_state_t state; - u32 timer_handle; - u32 timeout; - u8 pending_timer; - u8 *app_name; - u8 *host; - u8 is_server; - http_udp_tunnel_mode_t udp_tunnel_mode; - - http_req_t req; -} http_conn_t; - -typedef struct http_worker_ -{ - http_conn_t *conn_pool; -} http_worker_t; - -typedef struct http_main_ -{ - http_worker_t *wrk; - http_conn_t *listener_pool; - http_conn_t *ho_conn_pool; - u32 app_index; - - u8 **rx_bufs; - u8 **tx_bufs; - u8 **app_header_lists; - - clib_timebase_t timebase; - - u16 *sc_by_u16; - /* - * Runtime config - */ - u8 debug_level; - u8 is_init; - - /* - * Config - */ - u64 first_seg_size; - u64 add_seg_size; - u32 fifo_size; -} http_main_t; - always_inline u8 * format_http_bytes (u8 *s, va_list *va) { @@ -736,124 +562,6 @@ http_path_remove_dot_segments (u8 *path) return new_path; } -always_inline int -_parse_field_name (u8 **pos, u8 *end, u8 **field_name_start, - u32 *field_name_len) -{ - u32 name_len = 0; - u8 *p; - - static uword tchar[4] = { - /* !#$%'*+-.0123456789 */ - 0x03ff6cba00000000, - /* ABCDEFGHIJKLMNOPQRSTUVWXYZ^_`abcdefghijklmnopqrstuvwxyz|~ */ - 0x57ffffffc7fffffe, - 0x0000000000000000, - 0x0000000000000000, - }; - - p = *pos; - - *field_name_start = p; - while (p != end) - { - if (clib_bitmap_get_no_check (tchar, *p)) - { - name_len++; - p++; - } - else if (*p == ':') - { - if (name_len == 0) - { - clib_warning ("empty field name"); - return -1; - } - *field_name_len = name_len; - p++; - *pos = p; - return 0; - } - else - { - clib_warning ("invalid character %d", *p); - return -1; - } - } - clib_warning ("field name end not found"); - return -1; -} - -always_inline int -_parse_field_value (u8 **pos, u8 *end, u8 **field_value_start, - u32 *field_value_len) -{ - u32 value_len = 0; - u8 *p; - - p = *pos; - - /* skip leading whitespace */ - while (1) - { - if (p == end) - { - clib_warning ("field value not found"); - return -1; - } - else if (*p != ' ' && *p != '\t') - { - break; - } - p++; - } - - *field_value_start = p; - while (p != end) - { - if (*p == '\r') - { - if ((end - p) < 1) - { - clib_warning ("incorrect field line end"); - return -1; - } - p++; - if (*p == '\n') - { - if (value_len == 0) - { - clib_warning ("empty field value"); - return -1; - } - p++; - *pos = p; - /* skip trailing whitespace */ - p = *field_value_start + value_len - 1; - while (*p == ' ' || *p == '\t') - { - p--; - value_len--; - } - *field_value_len = value_len; - return 0; - } - clib_warning ("CR without LF"); - return -1; - } - if (*p < ' ' && *p != '\t') - { - clib_warning ("invalid character %d", *p); - return -1; - } - p++; - value_len++; - } - - clib_warning ("field value end not found"); - return -1; -} - typedef struct { http_token_t name; @@ -873,6 +581,16 @@ typedef struct .values = 0, .value_by_name = 0, .buf = 0, .concatenated_values = 0, \ } +/** + * Case-sensitive comparison of two tokens. + * + * @param actual Pointer to the first token. + * @param actual_len Length of the first token. + * @param expected Pointer to the second token. + * @param expected_len Length of the second token. + * + * @return @c 1 if tokens are same, @c 0 otherwise. + */ always_inline u8 http_token_is (const char *actual, uword actual_len, const char *expected, uword expected_len) @@ -903,6 +621,16 @@ http_tolower_word (uword x) return (x | y); } +/** + * Case-insensitive comparison of two tokens. + * + * @param actual Pointer to the first token. + * @param actual_len Length of the first token. + * @param expected Pointer to the second token. + * @param expected_len Length of the second token. + * + * @return @c 1 if tokens are same, @c 0 otherwise. + */ always_inline u8 http_token_is_case (const char *actual, uword actual_len, const char *expected, uword expected_len) @@ -934,6 +662,16 @@ http_token_is_case (const char *actual, uword actual_len, const char *expected, return 1; } +/** + * Check if there is occurrence of token in another token. + * + * @param haystack Pointer to the token being searched. + * @param haystack_len Length of the token being searched. + * @param needle The token to search for. + * @param needle_len Length of the token to search for. + * + * @return @c 1 if in case of success, @c 0 otherwise. + */ always_inline u8 http_token_contains (const char *haystack, uword haystack_len, const char *needle, uword needle_len) @@ -1158,6 +896,13 @@ typedef struct /* Use high bit of header name length as custom header name bit. */ #define HTTP_CUSTOM_HEADER_NAME_BIT (1 << 31) +/** + * Initialize headers list context. + * + * @param ctx Headers list context. + * @param buf Buffer, which store headers list, provided by app. + * @param len Length of headers list buffer. + */ always_inline void http_init_headers_ctx (http_headers_ctx_t *ctx, u8 *buf, u32 len) { @@ -1166,6 +911,14 @@ http_init_headers_ctx (http_headers_ctx_t *ctx, u8 *buf, u32 len) ctx->buf = buf; } +/** + * Add header with predefined name to the headers list. + * + * @param ctx Headers list context. + * @param name Header name ID (see @ref http_header_name_t). + * @param value Header value pointer. + * @param value_len Header value length. + */ always_inline void http_add_header (http_headers_ctx_t *ctx, http_header_name_t name, const char *value, uword value_len) @@ -1182,6 +935,15 @@ http_add_header (http_headers_ctx_t *ctx, http_header_name_t name, ctx->tail_offset += sizeof (http_app_header_t) + value_len; } +/** + * Add header with custom name to the headers list. + * + * @param ctx Headers list context. + * @param name Header name pointer. + * @param name_len Header name length. + * @param value Header value pointer. + * @param value_len Header value length. + */ always_inline void http_add_custom_header (http_headers_ctx_t *ctx, const char *name, uword name_len, const char *value, uword value_len) @@ -1491,6 +1253,15 @@ http_parse_authority (u8 *authority, u32 authority_len, return token_start == end ? 0 : -1; } +/** + * Format given authority (RFC3986 section 3.2) + * + * @param authority Authority to format. + * + * @return New vector with formated authority. + * + * The caller is always responsible to free the returned vector. + */ always_inline u8 * http_serialize_authority (http_uri_authority_t *authority) { diff --git a/src/plugins/http/http1.c b/src/plugins/http/http1.c new file mode 100644 index 00000000000..44dd099ccee --- /dev/null +++ b/src/plugins/http/http1.c @@ -0,0 +1,1750 @@ +/* SPDX-License-Identifier: Apache-2.0 + * Copyright(c) 2025 Cisco Systems, Inc. + */ + +#include <vnet/session/application.h> + +#include <http/http.h> +#include <http/http_header_names.h> +#include <http/http_private.h> +#include <http/http_status_codes.h> +#include <http/http_timer.h> + +const char *http1_upgrade_proto_str[] = { "", +#define _(sym, str) str, + foreach_http_upgrade_proto +#undef _ +}; + +/** + * http error boilerplate + */ +static const char *error_template = "HTTP/1.1 %s\r\n" + "Date: %U GMT\r\n" + "Connection: close\r\n" + "Content-Length: 0\r\n\r\n"; + +/** + * http response boilerplate + */ +static const char *response_template = "HTTP/1.1 %s\r\n" + "Date: %U GMT\r\n" + "Server: %v\r\n"; + +static const char *content_len_template = "Content-Length: %llu\r\n"; + +static const char *connection_upgrade_template = "Connection: upgrade\r\n" + "Upgrade: %s\r\n"; + +/** + * http request boilerplate + */ +static const char *get_request_template = "GET %s HTTP/1.1\r\n" + "Host: %v\r\n" + "User-Agent: %v\r\n"; + +static const char *post_request_template = "POST %s HTTP/1.1\r\n" + "Host: %v\r\n" + "User-Agent: %v\r\n" + "Content-Length: %llu\r\n"; + +static void +http1_send_error (http_conn_t *hc, http_status_code_t ec, + transport_send_params_t *sp) +{ + u8 *data; + + if (ec >= HTTP_N_STATUS) + ec = HTTP_STATUS_INTERNAL_ERROR; + + data = format (0, error_template, http_status_code_str[ec], + format_http_time_now, hc); + HTTP_DBG (3, "%v", data); + http_io_ts_write (hc, data, vec_len (data), sp); + vec_free (data); + http_io_ts_after_write (hc, sp, 0, 1); +} + +static int +http1_read_message (http_conn_t *hc, u8 *rx_buf) +{ + u32 max_deq; + + max_deq = http_io_ts_max_read (hc); + if (PREDICT_FALSE (max_deq == 0)) + return -1; + + vec_validate (rx_buf, max_deq - 1); + http_io_ts_read (hc, rx_buf, max_deq, 1); + + return 0; +} + +static void +http1_identify_optional_query (http_req_t *req, u8 *rx_buf) +{ + int i; + for (i = req->target_path_offset; + i < (req->target_path_offset + req->target_path_len); i++) + { + if (rx_buf[i] == '?') + { + req->target_query_offset = i + 1; + req->target_query_len = req->target_path_offset + + req->target_path_len - + req->target_query_offset; + req->target_path_len = + req->target_path_len - req->target_query_len - 1; + break; + } + } +} + +static int +http1_parse_target (http_req_t *req, u8 *rx_buf) +{ + int i; + u8 *p, *end; + + /* asterisk-form = "*" */ + if ((rx_buf[req->target_path_offset] == '*') && (req->target_path_len == 1)) + { + req->target_form = HTTP_TARGET_ASTERISK_FORM; + /* we do not support OPTIONS request */ + return -1; + } + + /* origin-form = 1*( "/" segment ) [ "?" query ] */ + if (rx_buf[req->target_path_offset] == '/') + { + /* drop leading slash */ + req->target_path_len--; + req->target_path_offset++; + req->target_form = HTTP_TARGET_ORIGIN_FORM; + http1_identify_optional_query (req, rx_buf); + /* can't be CONNECT method */ + return req->method == HTTP_REQ_CONNECT ? -1 : 0; + } + + /* absolute-form = + * scheme "://" host [ ":" port ] *( "/" segment ) [ "?" query ] */ + if (req->target_path_len > 8 && + !memcmp (rx_buf + req->target_path_offset, "http", 4)) + { + req->scheme = HTTP_URL_SCHEME_HTTP; + p = rx_buf + req->target_path_offset + 4; + if (*p == 's') + { + p++; + req->scheme = HTTP_URL_SCHEME_HTTPS; + } + if (*p++ == ':') + { + expect_char ('/'); + expect_char ('/'); + req->target_form = HTTP_TARGET_ABSOLUTE_FORM; + req->target_authority_offset = p - rx_buf; + req->target_authority_len = 0; + end = rx_buf + req->target_path_offset + req->target_path_len; + while (p < end) + { + if (*p == '/') + { + p++; /* drop leading slash */ + req->target_path_offset = p - rx_buf; + req->target_path_len = end - p; + break; + } + req->target_authority_len++; + p++; + } + if (!req->target_path_len) + { + clib_warning ("zero length host"); + return -1; + } + http1_identify_optional_query (req, rx_buf); + /* can't be CONNECT method */ + return req->method == HTTP_REQ_CONNECT ? -1 : 0; + } + } + + /* authority-form = host ":" port */ + for (i = req->target_path_offset; + i < (req->target_path_offset + req->target_path_len); i++) + { + if ((rx_buf[i] == ':') && (isdigit (rx_buf[i + 1]))) + { + req->target_authority_len = req->target_path_len; + req->target_path_len = 0; + req->target_authority_offset = req->target_path_offset; + req->target_path_offset = 0; + req->target_form = HTTP_TARGET_AUTHORITY_FORM; + /* "authority-form" is only used for CONNECT requests */ + return req->method == HTTP_REQ_CONNECT ? 0 : -1; + } + } + + return -1; +} + +static int +http1_parse_request_line (http_req_t *req, u8 *rx_buf, http_status_code_t *ec) +{ + int i, target_len; + u32 next_line_offset, method_offset; + + /* request-line = method SP request-target SP HTTP-version CRLF */ + i = http_v_find_index (rx_buf, 8, 0, "\r\n"); + if (i < 0) + { + clib_warning ("request line incomplete"); + *ec = HTTP_STATUS_BAD_REQUEST; + return -1; + } + HTTP_DBG (2, "request line length: %d", i); + req->control_data_len = i + 2; + next_line_offset = req->control_data_len; + + /* there should be at least one more CRLF */ + if (vec_len (rx_buf) < (next_line_offset + 2)) + { + clib_warning ("malformed message, too short"); + *ec = HTTP_STATUS_BAD_REQUEST; + return -1; + } + + /* + * RFC9112 2.2: + * In the interest of robustness, a server that is expecting to receive and + * parse a request-line SHOULD ignore at least one empty line (CRLF) + * received prior to the request-line. + */ + method_offset = rx_buf[0] == '\r' && rx_buf[1] == '\n' ? 2 : 0; + /* parse method */ + if (!memcmp (rx_buf + method_offset, "GET ", 4)) + { + HTTP_DBG (0, "GET method"); + req->method = HTTP_REQ_GET; + req->target_path_offset = method_offset + 4; + } + else if (!memcmp (rx_buf + method_offset, "POST ", 5)) + { + HTTP_DBG (0, "POST method"); + req->method = HTTP_REQ_POST; + req->target_path_offset = method_offset + 5; + } + else if (!memcmp (rx_buf + method_offset, "CONNECT ", 8)) + { + HTTP_DBG (0, "CONNECT method"); + req->method = HTTP_REQ_CONNECT; + req->upgrade_proto = HTTP_UPGRADE_PROTO_NA; + req->target_path_offset = method_offset + 8; + req->is_tunnel = 1; + } + else + { + if (rx_buf[method_offset] - 'A' <= 'Z' - 'A') + { + *ec = HTTP_STATUS_NOT_IMPLEMENTED; + return -1; + } + else + { + *ec = HTTP_STATUS_BAD_REQUEST; + return -1; + } + } + + /* find version */ + i = http_v_find_index (rx_buf, next_line_offset - 11, 11, " HTTP/"); + if (i < 0) + { + clib_warning ("HTTP version not present"); + *ec = HTTP_STATUS_BAD_REQUEST; + return -1; + } + /* verify major version */ + if (isdigit (rx_buf[i + 6])) + { + if (rx_buf[i + 6] != '1') + { + clib_warning ("HTTP major version '%c' not supported", + rx_buf[i + 6]); + *ec = HTTP_STATUS_HTTP_VERSION_NOT_SUPPORTED; + return -1; + } + } + else + { + clib_warning ("HTTP major version '%c' is not digit", rx_buf[i + 6]); + *ec = HTTP_STATUS_BAD_REQUEST; + return -1; + } + + /* parse request-target */ + HTTP_DBG (2, "http at %d", i); + target_len = i - req->target_path_offset; + HTTP_DBG (2, "target_len %d", target_len); + if (target_len < 1) + { + clib_warning ("request-target not present"); + *ec = HTTP_STATUS_BAD_REQUEST; + return -1; + } + req->target_path_len = target_len; + req->target_query_offset = 0; + req->target_query_len = 0; + req->target_authority_len = 0; + req->target_authority_offset = 0; + if (http1_parse_target (req, rx_buf)) + { + clib_warning ("invalid target"); + *ec = HTTP_STATUS_BAD_REQUEST; + return -1; + } + HTTP_DBG (2, "request-target path length: %u", req->target_path_len); + HTTP_DBG (2, "request-target path offset: %u", req->target_path_offset); + HTTP_DBG (2, "request-target query length: %u", req->target_query_len); + HTTP_DBG (2, "request-target query offset: %u", req->target_query_offset); + + /* set buffer offset to nex line start */ + req->rx_buf_offset = next_line_offset; + + return 0; +} + +static int +http1_parse_status_line (http_req_t *req, u8 *rx_buf) +{ + int i; + u32 next_line_offset; + u8 *p, *end; + u16 status_code = 0; + + i = http_v_find_index (rx_buf, 0, 0, "\r\n"); + /* status-line = HTTP-version SP status-code SP [ reason-phrase ] CRLF */ + if (i < 0) + { + clib_warning ("status line incomplete"); + return -1; + } + HTTP_DBG (2, "status line length: %d", i); + if (i < 12) + { + clib_warning ("status line too short (%d)", i); + return -1; + } + req->control_data_len = i + 2; + next_line_offset = req->control_data_len; + p = rx_buf; + end = rx_buf + i; + + /* there should be at least one more CRLF */ + if (vec_len (rx_buf) < (next_line_offset + 2)) + { + clib_warning ("malformed message, too short"); + return -1; + } + + /* parse version */ + expect_char ('H'); + expect_char ('T'); + expect_char ('T'); + expect_char ('P'); + expect_char ('/'); + expect_char ('1'); + expect_char ('.'); + if (!isdigit (*p++)) + { + clib_warning ("invalid HTTP minor version"); + return -1; + } + + /* skip space(s) */ + if (*p != ' ') + { + clib_warning ("no space after HTTP version"); + return -1; + } + do + { + p++; + if (p == end) + { + clib_warning ("no status code"); + return -1; + } + } + while (*p == ' '); + + /* parse status code */ + if ((end - p) < 3) + { + clib_warning ("not enough characters for status code"); + return -1; + } + parse_int (status_code, 100); + parse_int (status_code, 10); + parse_int (status_code, 1); + if (status_code < 100 || status_code > 599) + { + clib_warning ("invalid status code %d", status_code); + return -1; + } + req->status_code = http_sc_by_u16 (status_code); + HTTP_DBG (0, "status code: %d", status_code); + + /* set buffer offset to nex line start */ + req->rx_buf_offset = next_line_offset; + + return 0; +} + +always_inline int +http1_parse_field_name (u8 **pos, u8 *end, u8 **field_name_start, + u32 *field_name_len) +{ + u32 name_len = 0; + u8 *p; + + static uword tchar[4] = { + /* !#$%'*+-.0123456789 */ + 0x03ff6cba00000000, + /* ABCDEFGHIJKLMNOPQRSTUVWXYZ^_`abcdefghijklmnopqrstuvwxyz|~ */ + 0x57ffffffc7fffffe, + 0x0000000000000000, + 0x0000000000000000, + }; + + p = *pos; + + *field_name_start = p; + while (p != end) + { + if (clib_bitmap_get_no_check (tchar, *p)) + { + name_len++; + p++; + } + else if (*p == ':') + { + if (name_len == 0) + { + clib_warning ("empty field name"); + return -1; + } + *field_name_len = name_len; + p++; + *pos = p; + return 0; + } + else + { + clib_warning ("invalid character %d", *p); + return -1; + } + } + clib_warning ("field name end not found"); + return -1; +} + +always_inline int +http1_parse_field_value (u8 **pos, u8 *end, u8 **field_value_start, + u32 *field_value_len) +{ + u32 value_len = 0; + u8 *p; + + p = *pos; + + /* skip leading whitespace */ + while (1) + { + if (p == end) + { + clib_warning ("field value not found"); + return -1; + } + else if (*p != ' ' && *p != '\t') + { + break; + } + p++; + } + + *field_value_start = p; + while (p != end) + { + if (*p == '\r') + { + if ((end - p) < 1) + { + clib_warning ("incorrect field line end"); + return -1; + } + p++; + if (*p == '\n') + { + if (value_len == 0) + { + clib_warning ("empty field value"); + return -1; + } + p++; + *pos = p; + /* skip trailing whitespace */ + p = *field_value_start + value_len - 1; + while (*p == ' ' || *p == '\t') + { + p--; + value_len--; + } + *field_value_len = value_len; + return 0; + } + clib_warning ("CR without LF"); + return -1; + } + if (*p < ' ' && *p != '\t') + { + clib_warning ("invalid character %d", *p); + return -1; + } + p++; + value_len++; + } + + clib_warning ("field value end not found"); + return -1; +} + +static int +http1_identify_headers (http_req_t *req, u8 *rx_buf, http_status_code_t *ec) +{ + int rv; + u8 *p, *end, *name_start, *value_start; + u32 name_len, value_len; + http_field_line_t *field_line; + uword header_index; + + vec_reset_length (req->headers); + req->content_len_header_index = ~0; + req->connection_header_index = ~0; + req->upgrade_header_index = ~0; + req->host_header_index = ~0; + req->headers_offset = req->rx_buf_offset; + + /* check if we have any header */ + if ((rx_buf[req->rx_buf_offset] == '\r') && + (rx_buf[req->rx_buf_offset + 1] == '\n')) + { + /* just another CRLF -> no headers */ + HTTP_DBG (2, "no headers"); + req->headers_len = 0; + req->control_data_len += 2; + return 0; + } + + end = vec_end (rx_buf); + p = rx_buf + req->rx_buf_offset; + + while (1) + { + rv = http1_parse_field_name (&p, end, &name_start, &name_len); + if (rv != 0) + { + *ec = HTTP_STATUS_BAD_REQUEST; + return -1; + } + rv = http1_parse_field_value (&p, end, &value_start, &value_len); + if (rv != 0 || (end - p) < 2) + { + *ec = HTTP_STATUS_BAD_REQUEST; + return -1; + } + + vec_add2 (req->headers, field_line, 1); + field_line->name_offset = (name_start - rx_buf) - req->headers_offset; + field_line->name_len = name_len; + field_line->value_offset = (value_start - rx_buf) - req->headers_offset; + field_line->value_len = value_len; + header_index = field_line - req->headers; + + /* find headers that will be used later in preprocessing */ + /* names are case-insensitive (RFC9110 section 5.1) */ + if (req->content_len_header_index == ~0 && + http_token_is_case ( + (const char *) name_start, name_len, + http_header_name_token (HTTP_HEADER_CONTENT_LENGTH))) + req->content_len_header_index = header_index; + else if (req->connection_header_index == ~0 && + http_token_is_case ( + (const char *) name_start, name_len, + http_header_name_token (HTTP_HEADER_CONNECTION))) + req->connection_header_index = header_index; + else if (req->upgrade_header_index == ~0 && + http_token_is_case ( + (const char *) name_start, name_len, + http_header_name_token (HTTP_HEADER_UPGRADE))) + req->upgrade_header_index = header_index; + else if (req->host_header_index == ~0 && + http_token_is_case ((const char *) name_start, name_len, + http_header_name_token (HTTP_HEADER_HOST))) + req->host_header_index = header_index; + + /* are we done? */ + if (*p == '\r' && *(p + 1) == '\n') + break; + } + + req->headers_len = p - (rx_buf + req->headers_offset); + req->control_data_len += (req->headers_len + 2); + HTTP_DBG (2, "headers length: %u", req->headers_len); + HTTP_DBG (2, "headers offset: %u", req->headers_offset); + + return 0; +} + +static int +http1_identify_message_body (http_req_t *req, u8 *rx_buf, + http_status_code_t *ec) +{ + int i; + u8 *p; + u64 body_len = 0, digit; + http_field_line_t *field_line; + + req->body_len = 0; + + if (req->headers_len == 0) + { + HTTP_DBG (2, "no header, no message-body"); + return 0; + } + if (req->is_tunnel) + { + HTTP_DBG (2, "tunnel, no message-body"); + return 0; + } + + /* TODO check for chunked transfer coding */ + + if (req->content_len_header_index == ~0) + { + HTTP_DBG (2, "Content-Length header not present, no message-body"); + return 0; + } + field_line = vec_elt_at_index (req->headers, req->content_len_header_index); + + p = rx_buf + req->headers_offset + field_line->value_offset; + for (i = 0; i < field_line->value_len; i++) + { + /* check for digit */ + if (!isdigit (*p)) + { + clib_warning ("expected digit"); + *ec = HTTP_STATUS_BAD_REQUEST; + return -1; + } + digit = *p - '0'; + u64 new_body_len = body_len * 10 + digit; + /* check for overflow */ + if (new_body_len < body_len) + { + clib_warning ("too big number, overflow"); + *ec = HTTP_STATUS_BAD_REQUEST; + return -1; + } + body_len = new_body_len; + p++; + } + + req->body_len = body_len; + + req->body_offset = req->headers_offset + req->headers_len + 2; + HTTP_DBG (2, "body length: %llu", req->body_len); + HTTP_DBG (2, "body offset: %u", req->body_offset); + + return 0; +} + +static void +http1_check_connection_upgrade (http_req_t *req, u8 *rx_buf) +{ + http_field_line_t *connection, *upgrade; + u8 skip; + + skip = (req->method != HTTP_REQ_GET) + (req->connection_header_index == ~0) + + (req->upgrade_header_index == ~0); + if (skip) + return; + + connection = vec_elt_at_index (req->headers, req->connection_header_index); + /* connection options are case-insensitive (RFC9110 7.6.1) */ + if (http_token_is_case ( + http_field_line_value_token (connection, req, rx_buf), + http_token_lit ("upgrade"))) + { + upgrade = vec_elt_at_index (req->headers, req->upgrade_header_index); + + /* check upgrade protocol, we want to ignore something like upgrade to + * newer HTTP version, only tunnels are supported */ + if (0) + ; +#define _(sym, str) \ + else if (http_token_is_case ( \ + http_field_line_value_token (upgrade, req, rx_buf), \ + http_token_lit (str))) req->upgrade_proto = \ + HTTP_UPGRADE_PROTO_##sym; + foreach_http_upgrade_proto +#undef _ + else return; + + req->is_tunnel = 1; + req->method = HTTP_REQ_CONNECT; + } +} + +static void +http1_target_fixup (http_conn_t *hc, http_req_t *req) +{ + http_field_line_t *host; + + if (req->target_form == HTTP_TARGET_ABSOLUTE_FORM) + return; + + /* scheme fixup */ + req->scheme = http_get_transport_proto (hc) == TRANSPORT_PROTO_TLS ? + HTTP_URL_SCHEME_HTTPS : + HTTP_URL_SCHEME_HTTP; + + if (req->target_form == HTTP_TARGET_AUTHORITY_FORM || + req->connection_header_index == ~0) + return; + + /* authority fixup */ + host = vec_elt_at_index (req->headers, req->connection_header_index); + req->target_authority_offset = host->value_offset; + req->target_authority_len = host->value_len; +} + +static void +http1_write_app_headers (http_conn_t *hc, http_msg_t *msg, u8 **tx_buf) +{ + u8 *app_headers, *p, *end; + u32 *tmp; + + /* read app header list */ + app_headers = http_get_app_header_list (hc, msg); + + /* serialize app headers to tx_buf */ + end = app_headers + msg->data.headers_len; + while (app_headers < end) + { + /* custom header name? */ + tmp = (u32 *) app_headers; + if (PREDICT_FALSE (*tmp & HTTP_CUSTOM_HEADER_NAME_BIT)) + { + http_custom_token_t *name, *value; + name = (http_custom_token_t *) app_headers; + u32 name_len = name->len & ~HTTP_CUSTOM_HEADER_NAME_BIT; + app_headers += sizeof (http_custom_token_t) + name_len; + value = (http_custom_token_t *) app_headers; + app_headers += sizeof (http_custom_token_t) + value->len; + vec_add2 (*tx_buf, p, name_len + value->len + 4); + clib_memcpy (p, name->token, name_len); + p += name_len; + *p++ = ':'; + *p++ = ' '; + clib_memcpy (p, value->token, value->len); + p += value->len; + *p++ = '\r'; + *p++ = '\n'; + } + else + { + http_app_header_t *header; + header = (http_app_header_t *) app_headers; + app_headers += sizeof (http_app_header_t) + header->value.len; + http_token_t name = { http_header_name_token (header->name) }; + vec_add2 (*tx_buf, p, name.len + header->value.len + 4); + clib_memcpy (p, name.base, name.len); + p += name.len; + *p++ = ':'; + *p++ = ' '; + clib_memcpy (p, header->value.token, header->value.len); + p += header->value.len; + *p++ = '\r'; + *p++ = '\n'; + } + } +} + +/*************************************/ +/* request state machine handlers RX */ +/*************************************/ + +static http_sm_result_t +http1_req_state_wait_transport_reply (http_conn_t *hc, http_req_t *req, + transport_send_params_t *sp) +{ + int rv; + http_msg_t msg = {}; + u32 len, max_enq, body_sent; + http_status_code_t ec; + u8 *rx_buf; + + rx_buf = http_get_rx_buf (hc); + rv = http1_read_message (hc, rx_buf); + + /* Nothing yet, wait for data or timer expire */ + if (rv) + { + HTTP_DBG (1, "no data to deq"); + return HTTP_SM_STOP; + } + + HTTP_DBG (3, "%v", rx_buf); + + if (vec_len (rx_buf) < 8) + { + clib_warning ("response buffer too short"); + goto error; + } + + rv = http1_parse_status_line (req, rx_buf); + if (rv) + goto error; + + rv = http1_identify_headers (req, rx_buf, &ec); + if (rv) + goto error; + + rv = http1_identify_message_body (req, rx_buf, &ec); + if (rv) + goto error; + + /* send at least "control data" which is necessary minimum, + * if there is some space send also portion of body */ + max_enq = http_io_as_max_write (req); + max_enq -= sizeof (msg); + if (max_enq < req->control_data_len) + { + clib_warning ("not enough room for control data in app's rx fifo"); + goto error; + } + len = clib_min (max_enq, vec_len (rx_buf)); + + msg.type = HTTP_MSG_REPLY; + msg.code = req->status_code; + msg.data.headers_offset = req->headers_offset; + msg.data.headers_len = req->headers_len; + msg.data.body_offset = req->body_offset; + msg.data.body_len = req->body_len; + msg.data.type = HTTP_MSG_DATA_INLINE; + msg.data.len = len; + msg.data.headers_ctx = pointer_to_uword (req->headers); + + svm_fifo_seg_t segs[2] = { { (u8 *) &msg, sizeof (msg) }, { rx_buf, len } }; + + http_io_as_write_segs (req, segs, 2); + + body_sent = len - req->control_data_len; + req->to_recv = req->body_len - body_sent; + if (req->to_recv == 0) + { + /* all sent, we are done */ + http_req_state_change (req, HTTP_REQ_STATE_WAIT_APP_METHOD); + } + else + { + /* stream rest of the response body */ + http_req_state_change (req, HTTP_REQ_STATE_TRANSPORT_IO_MORE_DATA); + } + + http_io_ts_drain (hc, len); + http_io_ts_after_read (hc, 1); + http_app_worker_rx_notify (req); + return HTTP_SM_STOP; + +error: + http_io_ts_drain_all (hc); + http_io_ts_after_read (hc, 1); + session_transport_closing_notify (&hc->connection); + session_transport_closed_notify (&hc->connection); + http_disconnect_transport (hc); + return HTTP_SM_ERROR; +} + +static http_sm_result_t +http1_req_state_wait_transport_method (http_conn_t *hc, http_req_t *req, + transport_send_params_t *sp) +{ + http_status_code_t ec; + http_msg_t msg; + int rv; + u32 len, max_enq, body_sent; + u64 max_deq; + u8 *rx_buf; + + rx_buf = http_get_rx_buf (hc); + rv = http1_read_message (hc, rx_buf); + + /* Nothing yet, wait for data or timer expire */ + if (rv) + return HTTP_SM_STOP; + + HTTP_DBG (3, "%v", rx_buf); + + if (vec_len (rx_buf) < 8) + { + ec = HTTP_STATUS_BAD_REQUEST; + goto error; + } + + rv = http1_parse_request_line (req, rx_buf, &ec); + if (rv) + goto error; + + rv = http1_identify_headers (req, rx_buf, &ec); + if (rv) + goto error; + + http1_target_fixup (hc, req); + http1_check_connection_upgrade (req, rx_buf); + + rv = http1_identify_message_body (req, rx_buf, &ec); + if (rv) + goto error; + + /* send at least "control data" which is necessary minimum, + * if there is some space send also portion of body */ + max_enq = http_io_as_max_write (req); + if (max_enq < req->control_data_len) + { + clib_warning ("not enough room for control data in app's rx fifo"); + ec = HTTP_STATUS_INTERNAL_ERROR; + goto error; + } + /* do not dequeue more than one HTTP request, we do not support pipelining */ + max_deq = clib_min (req->control_data_len + req->body_len, vec_len (rx_buf)); + len = clib_min (max_enq, max_deq); + + msg.type = HTTP_MSG_REQUEST; + msg.method_type = req->method; + msg.data.type = HTTP_MSG_DATA_INLINE; + msg.data.len = len; + msg.data.scheme = req->scheme; + msg.data.target_authority_offset = req->target_authority_offset; + msg.data.target_authority_len = req->target_authority_len; + msg.data.target_path_offset = req->target_path_offset; + msg.data.target_path_len = req->target_path_len; + msg.data.target_query_offset = req->target_query_offset; + msg.data.target_query_len = req->target_query_len; + msg.data.headers_offset = req->headers_offset; + msg.data.headers_len = req->headers_len; + msg.data.body_offset = req->body_offset; + msg.data.body_len = req->body_len; + msg.data.headers_ctx = pointer_to_uword (req->headers); + msg.data.upgrade_proto = req->upgrade_proto; + + svm_fifo_seg_t segs[2] = { { (u8 *) &msg, sizeof (msg) }, { rx_buf, len } }; + + http_io_as_write_segs (req, segs, 2); + + body_sent = len - req->control_data_len; + req->to_recv = req->body_len - body_sent; + if (req->to_recv == 0) + { + /* drop everything, we do not support pipelining */ + http_io_ts_drain_all (hc); + /* all sent, we are done */ + http_req_state_change (req, HTTP_REQ_STATE_WAIT_APP_REPLY); + } + else + { + http_io_ts_drain (hc, len); + /* stream rest of the response body */ + http_req_state_change (req, HTTP_REQ_STATE_TRANSPORT_IO_MORE_DATA); + } + + http_app_worker_rx_notify (req); + http_io_ts_after_read (hc, 1); + + return HTTP_SM_STOP; + +error: + http_io_ts_drain_all (hc); + http_io_ts_after_read (hc, 1); + http1_send_error (hc, ec, 0); + session_transport_closing_notify (&hc->connection); + http_disconnect_transport (hc); + + return HTTP_SM_ERROR; +} + +static http_sm_result_t +http1_req_state_transport_io_more_data (http_conn_t *hc, http_req_t *req, + transport_send_params_t *sp) +{ + u32 max_len, max_deq, max_enq, n_segs = 2; + svm_fifo_seg_t segs[n_segs]; + int n_written; + + max_deq = http_io_ts_max_read (hc); + if (max_deq == 0) + { + HTTP_DBG (1, "no data to deq"); + return HTTP_SM_STOP; + } + + max_enq = http_io_as_max_write (req); + if (max_enq == 0) + { + HTTP_DBG (1, "app's rx fifo full"); + http_io_as_want_deq_ntf (req); + return HTTP_SM_STOP; + } + + max_len = clib_min (max_enq, max_deq); + http_io_ts_read_segs (hc, segs, &n_segs, max_len); + + n_written = http_io_as_write_segs (req, segs, n_segs); + + if (n_written > req->to_recv) + { + clib_warning ("http protocol error: received more data than expected"); + session_transport_closing_notify (&hc->connection); + http_disconnect_transport (hc); + http_req_state_change (req, HTTP_REQ_STATE_WAIT_APP_METHOD); + return HTTP_SM_ERROR; + } + req->to_recv -= n_written; + http_io_ts_drain (hc, n_written); + HTTP_DBG (1, "drained %d from ts; remains %lu", n_written, req->to_recv); + + /* Finished transaction: + * server back to HTTP_REQ_STATE_WAIT_APP_REPLY + * client to HTTP_REQ_STATE_WAIT_APP_METHOD */ + if (req->to_recv == 0) + http_req_state_change (req, hc->is_server ? + HTTP_REQ_STATE_WAIT_APP_REPLY : + HTTP_REQ_STATE_WAIT_APP_METHOD); + + http_app_worker_rx_notify (req); + + http_io_ts_after_read (hc, 0); + + return HTTP_SM_STOP; +} + +static http_sm_result_t +http1_req_state_tunnel_rx (http_conn_t *hc, http_req_t *req, + transport_send_params_t *sp) +{ + u32 max_deq, max_enq, max_read, n_segs = 2; + svm_fifo_seg_t segs[n_segs]; + int n_written = 0; + + HTTP_DBG (1, "tunnel received data from client"); + + max_deq = http_io_ts_max_read (hc); + if (PREDICT_FALSE (max_deq == 0)) + { + HTTP_DBG (1, "max_deq == 0"); + return HTTP_SM_STOP; + } + max_enq = http_io_as_max_write (req); + if (max_enq == 0) + { + HTTP_DBG (1, "app's rx fifo full"); + http_io_as_want_deq_ntf (req); + return HTTP_SM_STOP; + } + max_read = clib_min (max_enq, max_deq); + http_io_ts_read_segs (hc, segs, &n_segs, max_read); + n_written = http_io_as_write_segs (req, segs, n_segs); + http_io_ts_drain (hc, n_written); + HTTP_DBG (1, "transfered %u bytes", n_written); + http_app_worker_rx_notify (req); + http_io_ts_after_read (hc, 0); + + return HTTP_SM_STOP; +} + +static http_sm_result_t +http1_req_state_udp_tunnel_rx (http_conn_t *hc, http_req_t *req, + transport_send_params_t *sp) +{ + u32 to_deq, capsule_size, dgram_size, n_read, n_written = 0; + int rv; + u8 payload_offset = 0; + u64 payload_len = 0; + session_dgram_hdr_t hdr; + u8 *buf = 0; + + HTTP_DBG (1, "udp tunnel received data from client"); + + buf = http_get_rx_buf (hc); + to_deq = http_io_ts_max_read (hc); + + while (to_deq > 0) + { + /* some bytes remaining to skip? */ + if (PREDICT_FALSE (req->to_skip)) + { + if (req->to_skip >= to_deq) + { + http_io_ts_drain (hc, to_deq); + req->to_skip -= to_deq; + goto done; + } + else + { + http_io_ts_drain (hc, req->to_skip); + req->to_skip = 0; + } + } + n_read = http_io_ts_read (hc, buf, HTTP_CAPSULE_HEADER_MAX_SIZE, 1); + rv = http_decap_udp_payload_datagram (buf, n_read, &payload_offset, + &payload_len); + HTTP_DBG (1, "rv=%d, payload_offset=%u, payload_len=%llu", rv, + payload_offset, payload_len); + if (PREDICT_FALSE (rv != 0)) + { + if (rv < 0) + { + /* capsule datagram is invalid (session need to be aborted) */ + http_io_ts_drain_all (hc); + session_transport_closing_notify (&hc->connection); + session_transport_closed_notify (&hc->connection); + http_disconnect_transport (hc); + return HTTP_SM_STOP; + } + else + { + /* unknown capsule should be skipped */ + if (payload_len <= to_deq) + { + http_io_ts_drain (hc, payload_len); + to_deq -= payload_len; + continue; + } + else + { + http_io_ts_drain (hc, to_deq); + req->to_skip = payload_len - to_deq; + goto done; + } + } + } + capsule_size = payload_offset + payload_len; + /* check if we have the full capsule */ + if (PREDICT_FALSE (to_deq < capsule_size)) + { + HTTP_DBG (1, "capsule not complete"); + goto done; + } + + dgram_size = sizeof (hdr) + payload_len; + if (http_io_as_max_write (req) < dgram_size) + { + HTTP_DBG (1, "app's rx fifo full"); + http_io_as_want_deq_ntf (req); + goto done; + } + + http_io_ts_drain (hc, payload_offset); + + /* read capsule payload */ + http_io_ts_read (hc, buf, payload_len, 0); + + hdr.data_length = payload_len; + hdr.data_offset = 0; + + /* send datagram header and payload */ + svm_fifo_seg_t segs[2] = { { (u8 *) &hdr, sizeof (hdr) }, + { buf, payload_len } }; + http_io_as_write_segs (req, segs, 2); + + n_written += dgram_size; + to_deq -= capsule_size; + } + +done: + HTTP_DBG (1, "written %lu bytes", n_written); + + if (n_written) + http_app_worker_rx_notify (req); + + http_io_ts_after_read (hc, 0); + + return HTTP_SM_STOP; +} + +/*************************************/ +/* request state machine handlers TX */ +/*************************************/ + +static http_sm_result_t +http1_req_state_wait_app_reply (http_conn_t *hc, http_req_t *req, + transport_send_params_t *sp) +{ + u8 *response; + u32 max_enq; + http_status_code_t sc; + http_msg_t msg; + http_sm_result_t sm_result = HTTP_SM_ERROR; + http_req_state_t next_state = HTTP_REQ_STATE_WAIT_TRANSPORT_METHOD; + + http_get_app_msg (req, &msg); + + if (msg.data.type > HTTP_MSG_DATA_PTR) + { + clib_warning ("no data"); + sc = HTTP_STATUS_INTERNAL_ERROR; + goto error; + } + + if (msg.type != HTTP_MSG_REPLY) + { + clib_warning ("unexpected message type %d", msg.type); + sc = HTTP_STATUS_INTERNAL_ERROR; + goto error; + } + + if (msg.code >= HTTP_N_STATUS) + { + clib_warning ("unsupported status code: %d", msg.code); + return HTTP_SM_ERROR; + } + + response = http_get_tx_buf (hc); + /* + * Add "protocol layer" headers: + * - current time + * - server name + * - data length + */ + response = + format (response, response_template, http_status_code_str[msg.code], + /* Date */ + format_http_time_now, hc, + /* Server */ + hc->app_name); + + /* RFC9110 8.6: A server MUST NOT send Content-Length header field in a + * 2xx (Successful) response to CONNECT or with a status code of 101 + * (Switching Protocols). */ + if (req->is_tunnel && (http_status_code_str[msg.code][0] == '2' || + msg.code == HTTP_STATUS_SWITCHING_PROTOCOLS)) + { + ASSERT (msg.data.body_len == 0); + next_state = HTTP_REQ_STATE_TUNNEL; + if (req->upgrade_proto > HTTP_UPGRADE_PROTO_NA) + { + response = format (response, connection_upgrade_template, + http1_upgrade_proto_str[req->upgrade_proto]); + if (req->upgrade_proto == HTTP_UPGRADE_PROTO_CONNECT_UDP && + hc->udp_tunnel_mode == HTTP_UDP_TUNNEL_DGRAM) + next_state = HTTP_REQ_STATE_UDP_TUNNEL; + } + /* cleanup some stuff we don't need anymore in tunnel mode */ + vec_free (req->headers); + http_buffer_free (&req->tx_buf); + req->to_skip = 0; + } + else + response = format (response, content_len_template, msg.data.body_len); + + /* Add headers from app (if any) */ + if (msg.data.headers_len) + { + HTTP_DBG (0, "got headers from app, len %d", msg.data.headers_len); + http1_write_app_headers (hc, &msg, &response); + } + /* Add empty line after headers */ + response = format (response, "\r\n"); + HTTP_DBG (3, "%v", response); + + max_enq = http_io_ts_max_write (hc, sp); + if (max_enq < vec_len (response)) + { + clib_warning ("sending status-line and headers failed!"); + sc = HTTP_STATUS_INTERNAL_ERROR; + goto error; + } + http_io_ts_write (hc, response, vec_len (response), sp); + + if (msg.data.body_len) + { + /* Start sending the actual data */ + http_req_tx_buffer_init (req, &msg); + next_state = HTTP_REQ_STATE_APP_IO_MORE_DATA; + sm_result = HTTP_SM_CONTINUE; + } + else + { + /* No response body, we are done */ + sm_result = HTTP_SM_STOP; + } + + http_req_state_change (req, next_state); + + http_io_ts_after_write (hc, sp, 0, 1); + return sm_result; + +error: + http1_send_error (hc, sc, sp); + session_transport_closing_notify (&hc->connection); + http_disconnect_transport (hc); + return HTTP_SM_STOP; +} + +static http_sm_result_t +http1_req_state_wait_app_method (http_conn_t *hc, http_req_t *req, + transport_send_params_t *sp) +{ + http_msg_t msg; + u8 *request = 0, *target; + u32 max_enq; + http_sm_result_t sm_result = HTTP_SM_ERROR; + http_req_state_t next_state; + + http_get_app_msg (req, &msg); + + if (msg.data.type > HTTP_MSG_DATA_PTR) + { + clib_warning ("no data"); + goto error; + } + + if (msg.type != HTTP_MSG_REQUEST) + { + clib_warning ("unexpected message type %d", msg.type); + goto error; + } + + /* read request target */ + target = http_get_app_target (req, &msg); + + request = http_get_tx_buf (hc); + /* currently we support only GET and POST method */ + if (msg.method_type == HTTP_REQ_GET) + { + if (msg.data.body_len) + { + clib_warning ("GET request shouldn't include data"); + goto error; + } + /* + * Add "protocol layer" headers: + * - host + * - user agent + */ + request = format (request, get_request_template, + /* target */ + target, + /* Host */ + hc->host, + /* User-Agent */ + hc->app_name); + + next_state = HTTP_REQ_STATE_WAIT_TRANSPORT_REPLY; + sm_result = HTTP_SM_STOP; + } + else if (msg.method_type == HTTP_REQ_POST) + { + if (!msg.data.body_len) + { + clib_warning ("POST request should include data"); + goto error; + } + /* + * Add "protocol layer" headers: + * - host + * - user agent + * - content length + */ + request = format (request, post_request_template, + /* target */ + target, + /* Host */ + hc->host, + /* User-Agent */ + hc->app_name, + /* Content-Length */ + msg.data.body_len); + + http_req_tx_buffer_init (req, &msg); + + next_state = HTTP_REQ_STATE_APP_IO_MORE_DATA; + sm_result = HTTP_SM_CONTINUE; + } + else + { + clib_warning ("unsupported method %d", msg.method_type); + goto error; + } + + /* Add headers from app (if any) */ + if (msg.data.headers_len) + { + HTTP_DBG (0, "got headers from app, len %d", msg.data.headers_len); + http1_write_app_headers (hc, &msg, &request); + } + /* Add empty line after headers */ + request = format (request, "\r\n"); + HTTP_DBG (3, "%v", request); + + max_enq = http_io_ts_max_write (hc, sp); + if (max_enq < vec_len (request)) + { + clib_warning ("sending request-line and headers failed!"); + sm_result = HTTP_SM_ERROR; + goto error; + } + http_io_ts_write (hc, request, vec_len (request), sp); + + http_req_state_change (req, next_state); + + http_io_ts_after_write (hc, sp, 0, 1); + goto done; + +error: + http_io_as_drain_all (req); + session_transport_closing_notify (&hc->connection); + session_transport_closed_notify (&hc->connection); + http_disconnect_transport (hc); + +done: + return sm_result; +} + +static http_sm_result_t +http1_req_state_app_io_more_data (http_conn_t *hc, http_req_t *req, + transport_send_params_t *sp) +{ + u32 max_write, n_segs, n_written = 0; + http_buffer_t *hb = &req->tx_buf; + svm_fifo_seg_t *seg; + u8 finished = 0; + + ASSERT (!http_buffer_is_drained (hb)); + max_write = http_io_ts_max_write (hc, sp); + if (max_write == 0) + { + HTTP_DBG (1, "ts tx fifo full"); + goto check_fifo; + } + + seg = http_buffer_get_segs (hb, max_write, &n_segs); + if (!seg) + { + HTTP_DBG (1, "no data to deq"); + goto check_fifo; + } + + n_written = http_io_ts_write_segs (hc, seg, n_segs, sp); + + http_buffer_drain (hb, n_written); + finished = http_buffer_is_drained (hb); + + if (finished) + { + /* Finished transaction: + * server back to HTTP_REQ_STATE_WAIT_TRANSPORT_METHOD + * client to HTTP_REQ_STATE_WAIT_TRANSPORT_REPLY */ + http_req_state_change (req, hc->is_server ? + HTTP_REQ_STATE_WAIT_TRANSPORT_METHOD : + HTTP_REQ_STATE_WAIT_TRANSPORT_REPLY); + http_buffer_free (hb); + } + +check_fifo: + http_io_ts_after_write (hc, sp, finished, !!n_written); + return HTTP_SM_STOP; +} + +static http_sm_result_t +http1_req_state_tunnel_tx (http_conn_t *hc, http_req_t *req, + transport_send_params_t *sp) +{ + u32 max_deq, max_enq, max_read, n_segs = 2; + svm_fifo_seg_t segs[n_segs]; + int n_written = 0; + + HTTP_DBG (1, "tunnel received data from target"); + + max_deq = http_io_as_max_read (req); + if (PREDICT_FALSE (max_deq == 0)) + { + HTTP_DBG (1, "max_deq == 0"); + goto check_fifo; + } + max_enq = http_io_ts_max_write (hc, sp); + if (max_enq == 0) + { + HTTP_DBG (1, "ts tx fifo full"); + goto check_fifo; + } + max_read = clib_min (max_enq, max_deq); + http_io_as_read_segs (req, segs, &n_segs, max_read); + n_written = http_io_ts_write_segs (hc, segs, n_segs, sp); + http_io_as_drain (req, n_written); + +check_fifo: + http_io_ts_after_write (hc, sp, 0, !!n_written); + + return HTTP_SM_STOP; +} + +static http_sm_result_t +http1_req_state_udp_tunnel_tx (http_conn_t *hc, http_req_t *req, + transport_send_params_t *sp) +{ + u32 to_deq, capsule_size, dgram_size; + u8 written = 0; + session_dgram_hdr_t hdr; + u8 *buf; + u8 *payload; + + HTTP_DBG (1, "udp tunnel received data from target"); + + buf = http_get_tx_buf (hc); + to_deq = http_io_as_max_read (req); + + while (to_deq > 0) + { + /* read datagram header */ + http_io_as_read (req, (u8 *) &hdr, sizeof (hdr), 1); + ASSERT (hdr.data_length <= HTTP_UDP_PAYLOAD_MAX_LEN); + dgram_size = hdr.data_length + SESSION_CONN_HDR_LEN; + ASSERT (to_deq >= dgram_size); + + if (http_io_ts_max_write (hc, sp) < + (hdr.data_length + HTTP_UDP_PROXY_DATAGRAM_CAPSULE_OVERHEAD)) + { + HTTP_DBG (1, "ts tx fifo full"); + goto done; + } + + /* create capsule header */ + payload = http_encap_udp_payload_datagram (buf, hdr.data_length); + capsule_size = (payload - buf) + hdr.data_length; + /* read payload */ + http_io_as_read (req, payload, hdr.data_length, 1); + http_io_as_drain (req, dgram_size); + /* send capsule */ + http_io_ts_write (hc, buf, capsule_size, sp); + + written = 1; + to_deq -= dgram_size; + } + +done: + http_io_ts_after_write (hc, sp, 0, written); + + return HTTP_SM_STOP; +} + +/*************************/ +/* request state machine */ +/*************************/ + +static http_sm_handler tx_state_funcs[HTTP_REQ_N_STATES] = { + 0, /* idle */ + http1_req_state_wait_app_method, + 0, /* wait transport reply */ + 0, /* transport io more data */ + 0, /* wait transport method */ + http1_req_state_wait_app_reply, + http1_req_state_app_io_more_data, + http1_req_state_tunnel_tx, + http1_req_state_udp_tunnel_tx, +}; + +static http_sm_handler rx_state_funcs[HTTP_REQ_N_STATES] = { + 0, /* idle */ + 0, /* wait app method */ + http1_req_state_wait_transport_reply, + http1_req_state_transport_io_more_data, + http1_req_state_wait_transport_method, + 0, /* wait app reply */ + 0, /* app io more data */ + http1_req_state_tunnel_rx, + http1_req_state_udp_tunnel_rx, +}; + +static_always_inline int +http1_req_state_is_tx_valid (http_req_t *req) +{ + return tx_state_funcs[req->state] ? 1 : 0; +} + +static_always_inline int +http1_req_state_is_rx_valid (http_req_t *req) +{ + return rx_state_funcs[req->state] ? 1 : 0; +} + +static_always_inline void +http1_req_run_state_machine (http_conn_t *hc, http_req_t *req, + transport_send_params_t *sp, u8 is_tx) +{ + http_sm_result_t res; + + do + { + if (is_tx) + res = tx_state_funcs[req->state](hc, req, sp); + else + res = rx_state_funcs[req->state](hc, req, 0); + if (res == HTTP_SM_ERROR) + { + HTTP_DBG (1, "error in state machine %d", res); + return; + } + } + while (res == HTTP_SM_CONTINUE); + + /* Reset the session expiration timer */ + http_conn_timer_update (hc); +} + +/*****************/ +/* http core VFT */ +/*****************/ + +static void +http1_app_tx_callback (http_conn_t *hc, transport_send_params_t *sp) +{ + http_req_t *req; + + req = http_get_req_if_valid (hc, 0); + if (!req) + { + http_alloc_req (hc); + req = http_get_req (hc, 0); + req->app_session_handle = hc->h_pa_session_handle; + http_req_state_change (req, HTTP_REQ_STATE_WAIT_APP_METHOD); + } + + if (!http1_req_state_is_tx_valid (req)) + { + /* Sometimes the server apps can send the response earlier + * than expected (e.g when rejecting a bad request)*/ + if (req->state == HTTP_REQ_STATE_TRANSPORT_IO_MORE_DATA && hc->is_server) + { + http_io_ts_drain_all (hc); + http_req_state_change (req, HTTP_REQ_STATE_WAIT_APP_REPLY); + } + else + { + clib_warning ("hc [%u]%x invalid tx state: http req state " + "'%U', session state '%U'", + hc->c_thread_index, hc->h_hc_index, + format_http_req_state, req->state, + format_http_conn_state, hc); + http_io_as_drain_all (req); + return; + } + } + + HTTP_DBG (1, "run state machine"); + http1_req_run_state_machine (hc, req, sp, 1); +} + +static void +http1_app_rx_evt_callback (http_conn_t *hc) +{ + http_req_t *req; + + req = http_get_req (hc, 0); + + if (req->state == HTTP_REQ_STATE_TUNNEL) + http1_req_state_tunnel_rx (hc, req, 0); +} + +static void +http1_app_close_callback (http_conn_t *hc) +{ + http_req_t *req; + + req = http_get_req_if_valid (hc, 0); + /* Nothing more to send, confirm close */ + if (!req || !http_io_as_max_read (req)) + { + session_transport_closed_notify (&hc->connection); + http_disconnect_transport (hc); + } + else + { + /* Wait for all data to be written to ts */ + hc->state = HTTP_CONN_STATE_APP_CLOSED; + } +} + +static void +http1_app_reset_callback (http_conn_t *hc) +{ + session_transport_closed_notify (&hc->connection); + http_disconnect_transport (hc); +} + +static void +http1_transport_rx_callback (http_conn_t *hc) +{ + http_req_t *req; + + req = http_get_req_if_valid (hc, 0); + if (!req) + { + http_alloc_req (hc); + req = http_get_req (hc, 0); + req->app_session_handle = hc->h_pa_session_handle; + http_req_state_change (req, HTTP_REQ_STATE_WAIT_TRANSPORT_METHOD); + } + + if (!http1_req_state_is_rx_valid (req)) + { + clib_warning ("hc [%u]%x invalid rx state: http req state " + "'%U', session state '%U'", + hc->c_thread_index, hc->h_hc_index, format_http_req_state, + req->state, format_http_conn_state, hc); + http_io_ts_drain_all (hc); + return; + } + + HTTP_DBG (1, "run state machine"); + http1_req_run_state_machine (hc, req, 0, 0); +} + +static void +http1_transport_close_callback (http_conn_t *hc) +{ + /* Nothing more to rx, propagate to app */ + if (!http_io_ts_max_read (hc)) + session_transport_closing_notify (&hc->connection); +} + +const static http_engine_vft_t http1_engine = { + .app_tx_callback = http1_app_tx_callback, + .app_rx_evt_callback = http1_app_rx_evt_callback, + .app_close_callback = http1_app_close_callback, + .app_reset_callback = http1_app_reset_callback, + .transport_rx_callback = http1_transport_rx_callback, + .transport_close_callback = http1_transport_close_callback, +}; + +static clib_error_t * +http1_init (vlib_main_t *vm) +{ + http_register_engine (&http1_engine, HTTP_VERSION_1); + return 0; +} + +VLIB_INIT_FUNCTION (http1_init) = { + .runs_after = VLIB_INITS ("http_transport_init"), +}; diff --git a/src/plugins/http/http_private.h b/src/plugins/http/http_private.h new file mode 100644 index 00000000000..187d1fe8215 --- /dev/null +++ b/src/plugins/http/http_private.h @@ -0,0 +1,672 @@ +/* SPDX-License-Identifier: Apache-2.0 + * Copyright(c) 2025 Cisco Systems, Inc. + */ + +#ifndef SRC_PLUGINS_HTTP_HTTP_PRIVATE_H_ +#define SRC_PLUGINS_HTTP_HTTP_PRIVATE_H_ + +#include <vppinfra/time_range.h> +#include <vnet/session/application.h> +#include <vnet/session/session.h> +#include <vnet/session/transport.h> +#include <http/http.h> +#include <http/http_buffer.h> + +#define HTTP_FIFO_THRESH (16 << 10) + +typedef u32 http_conn_handle_t; + +typedef struct http_conn_id_ +{ + union + { + session_handle_t app_session_handle; + u32 parent_app_api_ctx; + }; + session_handle_t tc_session_handle; + u32 parent_app_wrk_index; +} http_conn_id_t; + +STATIC_ASSERT (sizeof (http_conn_id_t) <= TRANSPORT_CONN_ID_LEN, + "ctx id must be less than TRANSPORT_CONN_ID_LEN"); + +#define foreach_http_conn_state \ + _ (LISTEN, "LISTEN") \ + _ (CONNECTING, "CONNECTING") \ + _ (ESTABLISHED, "ESTABLISHED") \ + _ (TRANSPORT_CLOSED, "TRANSPORT-CLOSED") \ + _ (APP_CLOSED, "APP-CLOSED") \ + _ (CLOSED, "CLOSED") + +typedef enum http_conn_state_ +{ +#define _(s, str) HTTP_CONN_STATE_##s, + foreach_http_conn_state +#undef _ +} http_conn_state_t; + +#define foreach_http_req_state \ + _ (0, IDLE, "idle") \ + _ (1, WAIT_APP_METHOD, "wait app method") \ + _ (2, WAIT_TRANSPORT_REPLY, "wait transport reply") \ + _ (3, TRANSPORT_IO_MORE_DATA, "transport io more data") \ + _ (4, WAIT_TRANSPORT_METHOD, "wait transport method") \ + _ (5, WAIT_APP_REPLY, "wait app reply") \ + _ (6, APP_IO_MORE_DATA, "app io more data") \ + _ (7, TUNNEL, "tunnel") \ + _ (8, UDP_TUNNEL, "udp tunnel") + +typedef enum http_req_state_ +{ +#define _(n, s, str) HTTP_REQ_STATE_##s = n, + foreach_http_req_state +#undef _ + HTTP_REQ_N_STATES +} http_req_state_t; + +typedef enum http_target_form_ +{ + HTTP_TARGET_ORIGIN_FORM, + HTTP_TARGET_ABSOLUTE_FORM, + HTTP_TARGET_AUTHORITY_FORM, + HTTP_TARGET_ASTERISK_FORM +} http_target_form_t; + +typedef enum http_version_ +{ + HTTP_VERSION_1, + HTTP_VERSION_2, + HTTP_VERSION_3, + HTTP_VERSION_NA = 7, +} http_version_t; + +typedef struct http_req_ +{ + /* in case of multiplexing we have app session for each stream */ + session_handle_t app_session_handle; + u32 as_fifo_offset; /* for peek */ + + http_req_state_t state; /* state-machine state */ + + http_buffer_t tx_buf; /* message body from app to be sent */ + + /* + * for parsing of incoming message from transport + */ + u32 rx_buf_offset; /* current offset during parsing */ + u32 control_data_len; /* start line + headers + empty line */ + + union + { + u64 to_recv; /* remaining bytes of body to receive from transport */ + u64 to_skip; /* remaining bytes of capsule to skip */ + }; + + u8 is_tunnel; + + /* + * parsed metadata for app + */ + union + { + http_status_code_t status_code; + http_req_method_t method; + }; + + http_target_form_t target_form; + u8 *target; + http_url_scheme_t scheme; + u32 target_authority_offset; + u32 target_authority_len; + u32 target_path_offset; + u32 target_path_len; + u32 target_query_offset; + u32 target_query_len; + + u32 headers_offset; + u32 headers_len; + + u32 body_offset; + u64 body_len; + + http_field_line_t *headers; + uword content_len_header_index; + uword connection_header_index; + uword upgrade_header_index; + uword host_header_index; + + http_upgrade_proto_t upgrade_proto; +} http_req_t; + +typedef struct http_tc_ +{ + union + { + transport_connection_t connection; + http_conn_id_t c_http_conn_id; + }; +#define h_tc_session_handle c_http_conn_id.tc_session_handle +#define h_pa_wrk_index c_http_conn_id.parent_app_wrk_index +#define h_pa_session_handle c_http_conn_id.app_session_handle +#define h_pa_app_api_ctx c_http_conn_id.parent_app_api_ctx +#define h_hc_index connection.c_index + + http_version_t version; + http_conn_state_t state; + u32 timer_handle; + u32 timeout; + u8 pending_timer; + u8 *app_name; + u8 *host; + u8 is_server; + http_udp_tunnel_mode_t udp_tunnel_mode; + + http_req_t *req_pool; /* multiplexing => request per stream */ +} http_conn_t; + +typedef struct http_worker_ +{ + http_conn_t *conn_pool; +} http_worker_t; + +typedef struct http_main_ +{ + http_worker_t *wrk; + http_conn_t *listener_pool; + http_conn_t *ho_conn_pool; + u32 app_index; + + u8 **rx_bufs; + u8 **tx_bufs; + u8 **app_header_lists; + + clib_timebase_t timebase; + + http_status_code_t *sc_by_u16; + /* + * Runtime config + */ + u8 is_init; + + /* + * Config + */ + u64 first_seg_size; + u64 add_seg_size; + u32 fifo_size; +} http_main_t; + +typedef struct http_engine_vft_ +{ + void (*app_tx_callback) (http_conn_t *hc, transport_send_params_t *sp); + void (*app_rx_evt_callback) (http_conn_t *hc); + void (*app_close_callback) (http_conn_t *hc); + void (*app_reset_callback) (http_conn_t *hc); + void (*transport_rx_callback) (http_conn_t *hc); + void (*transport_close_callback) (http_conn_t *hc); +} http_engine_vft_t; + +void http_register_engine (const http_engine_vft_t *vft, + http_version_t version); + +/* HTTP state machine result */ +typedef enum http_sm_result_t_ +{ + HTTP_SM_STOP = 0, + HTTP_SM_CONTINUE = 1, + HTTP_SM_ERROR = -1, +} http_sm_result_t; + +typedef http_sm_result_t (*http_sm_handler) (http_conn_t *hc, http_req_t *req, + transport_send_params_t *sp); + +#define expect_char(c) \ + if (*p++ != c) \ + { \ + clib_warning ("unexpected character"); \ + return -1; \ + } + +#define parse_int(val, mul) \ + do \ + { \ + if (!isdigit (*p)) \ + { \ + clib_warning ("expected digit"); \ + return -1; \ + } \ + val += mul * (*p++ - '0'); \ + } \ + while (0) + +#define http_field_line_value_token(_fl, _req, _rx_buf) \ + (const char *) ((_rx_buf) + (_req)->headers_offset + (_fl)->value_offset), \ + (_fl)->value_len + +u8 *format_http_req_state (u8 *s, va_list *va); +u8 *format_http_conn_state (u8 *s, va_list *args); +u8 *format_http_time_now (u8 *s, va_list *args); + +/** + * @brief Find the first occurrence of the string in the vector. + * + * @param vec The vector to be scanned. + * @param offset Search offset in the vector. + * @param num Maximum number of characters to be searched if non-zero. + * @param str The string to be searched. + * + * @return @c -1 if the string is not found within the vector; index otherwise. + */ +int http_v_find_index (u8 *vec, u32 offset, u32 num, char *str); + +/** + * Disconnect HTTP connection. + * + * @param hc HTTP connection to disconnect. + */ +void http_disconnect_transport (http_conn_t *hc); + +/** + * Convert numeric representation of status code to @c http_status_code_t. + * + * @param status_code Status code within the range of 100 to 599, inclusive. + * + * @return Registered status code or in case of unrecognized status code as + * equivalent to the x00 status code of that class. + */ +http_status_code_t http_sc_by_u16 (u16 status_code); + +/** + * Read header list sent by app. + * + * @param hc HTTP connection. + * @param msg HTTP msg sent by app. + * + * @return Pointer to the header list. + * + * @note For immediate processing, not for buffering. + */ +u8 *http_get_app_header_list (http_conn_t *hc, http_msg_t *msg); + +/** + * Get pre-allocated TX buffer/vector. + * + * @param hc HTTP connection. + * + * @return Pointer to the vector. + * + * @note Vector length is reset to zero, use as temporary storage. + */ +u8 *http_get_tx_buf (http_conn_t *hc); + +/** + * Get pre-allocated RX buffer/vector. + * + * @param hc HTTP connection. + * + * @return Pointer to the vector. + * + * @note Vector length is reset to zero, use as temporary storage. + */ +u8 *http_get_rx_buf (http_conn_t *hc); + +/** + * Read request target path sent by app. + * + * @param hc HTTP connection. + * @param msg HTTP msg sent by app. + * + * @return Pointer to the target path. + * + * @note Valid only with request lifetime. + */ +u8 *http_get_app_target (http_req_t *req, http_msg_t *msg); + +/** + * Initialize per-request HTTP TX buffer. + * + * @param req HTTP request. + * @param msg HTTP msg sent by app. + * + * @note Use for streaming of body sent by app. + */ +void http_req_tx_buffer_init (http_req_t *req, http_msg_t *msg); + +/** + * Allocate new request within given HTTP connection. + * + * @param hc HTTP connection. + * + * @return Request index in per-connection pool. + */ +always_inline u32 +http_alloc_req (http_conn_t *hc) +{ + http_req_t *req; + pool_get_zero (hc->req_pool, req); + req->app_session_handle = SESSION_INVALID_HANDLE; + return (req - hc->req_pool); +} + +/** + * Get request in per-connection pool. + * + * @param hc HTTP connection. + * @param req_index Request index. + * + * @return Pointer to the request data. + */ +always_inline http_req_t * +http_get_req (http_conn_t *hc, u32 req_index) +{ + return pool_elt_at_index (hc->req_pool, req_index); +} + +/** + * Get request in per-connection pool if valid. + * + * @param hc HTTP connection. + * @param req_index Request index. + * + * @return Pointer to the request data or @c 0 if not valid. + */ +always_inline http_req_t * +http_get_req_if_valid (http_conn_t *hc, u32 req_index) +{ + if (pool_is_free_index (hc->req_pool, req_index)) + return 0; + return pool_elt_at_index (hc->req_pool, req_index); +} + +/** + * Free request in per-connection pool. + * + * @param hc HTTP connection. + * @param req Pointer to the request. + */ +always_inline void +http_req_free (http_conn_t *hc, http_req_t *req) +{ + vec_free (req->headers); + vec_free (req->target); + http_buffer_free (&req->tx_buf); + if (CLIB_DEBUG) + memset (req, 0xba, sizeof (*req)); + pool_put (hc->req_pool, req); +} + +/** + * Change state of given HTTP request. + * + * @param req HTTP request. + * @param state New state. + */ +always_inline void +http_req_state_change (http_req_t *req, http_req_state_t state) +{ + HTTP_DBG (1, "changing http req state: %U -> %U", format_http_req_state, + req->state, format_http_req_state, state); + ASSERT (req->state != HTTP_REQ_STATE_TUNNEL); + req->state = state; +} + +/** + * Send RX event to the app worker. + * + * @param req HTTP request. + */ +always_inline void +http_app_worker_rx_notify (http_req_t *req) +{ + session_t *as; + app_worker_t *app_wrk; + + as = session_get_from_handle (req->app_session_handle); + app_wrk = app_worker_get_if_valid (as->app_wrk_index); + if (app_wrk) + app_worker_rx_notify (app_wrk, as); +} + +/** + * Get underlying transport protocol of the HTTP connection. + * + * @param hc HTTP connection. + * + * @return Transport protocol, @ref transport_proto_t. + */ +always_inline transport_proto_t +http_get_transport_proto (http_conn_t *hc) +{ + return session_get_transport_proto ( + session_get_from_handle (hc->h_tc_session_handle)); +} + +/** + * Read HTTP msg sent by app. + * + * @param req HTTP request. + * @param msg HTTP msq will be stored here. + */ +always_inline void +http_get_app_msg (http_req_t *req, http_msg_t *msg) +{ + session_t *as; + int rv; + + as = session_get_from_handle (req->app_session_handle); + rv = svm_fifo_dequeue (as->tx_fifo, sizeof (*msg), (u8 *) msg); + ASSERT (rv == sizeof (*msg)); +} + +/* Abstraction of app session fifo operations */ + +always_inline void +http_io_as_want_deq_ntf (http_req_t *req) +{ + session_t *as = session_get_from_handle (req->app_session_handle); + svm_fifo_add_want_deq_ntf (as->rx_fifo, SVM_FIFO_WANT_DEQ_NOTIF); +} + +always_inline u32 +http_io_as_max_write (http_req_t *req) +{ + session_t *as = session_get_from_handle (req->app_session_handle); + return svm_fifo_max_enqueue_prod (as->rx_fifo); +} + +always_inline u32 +http_io_as_max_read (http_req_t *req) +{ + session_t *as = session_get_from_handle (req->app_session_handle); + return svm_fifo_max_dequeue_cons (as->tx_fifo); +} + +always_inline u32 +http_io_as_write_segs (http_req_t *req, const svm_fifo_seg_t segs[], + u32 n_segs) +{ + int n_written; + session_t *as = session_get_from_handle (req->app_session_handle); + n_written = svm_fifo_enqueue_segments (as->rx_fifo, segs, n_segs, 0); + ASSERT (n_written > 0); + return (u32) n_written; +} + +always_inline u32 +http_io_as_read (http_req_t *req, u8 *buf, u32 len, u8 peek) +{ + int n_read; + session_t *as = session_get_from_handle (req->app_session_handle); + + if (peek) + { + n_read = svm_fifo_peek (as->tx_fifo, req->as_fifo_offset, len, buf); + ASSERT (n_read > 0); + req->as_fifo_offset += len; + return (u32) n_read; + } + + n_read = svm_fifo_dequeue (as->tx_fifo, len, buf); + ASSERT (n_read == len); + return (u32) n_read; +} + +always_inline void +http_io_as_read_segs (http_req_t *req, svm_fifo_seg_t *segs, u32 *n_segs, + u32 max_bytes) +{ + int n_read; + session_t *as = session_get_from_handle (req->app_session_handle); + n_read = svm_fifo_segments (as->tx_fifo, 0, segs, n_segs, max_bytes); + ASSERT (n_read > 0); +} + +always_inline void +http_io_as_drain (http_req_t *req, u32 len) +{ + session_t *as = session_get_from_handle (req->app_session_handle); + svm_fifo_dequeue_drop (as->tx_fifo, len); + req->as_fifo_offset = 0; +} + +always_inline void +http_io_as_drain_all (http_req_t *req) +{ + session_t *as = session_get_from_handle (req->app_session_handle); + svm_fifo_dequeue_drop_all (as->tx_fifo); + req->as_fifo_offset = 0; +} + +/* Abstraction of transport session fifo operations */ + +always_inline u32 +http_io_ts_max_read (http_conn_t *hc) +{ + session_t *ts = session_get_from_handle (hc->h_tc_session_handle); + return svm_fifo_max_dequeue_cons (ts->rx_fifo); +} + +always_inline u32 +http_io_ts_max_write (http_conn_t *hc, transport_send_params_t *sp) +{ + session_t *ts = session_get_from_handle (hc->h_tc_session_handle); + return clib_min (svm_fifo_max_enqueue_prod (ts->tx_fifo), + sp->max_burst_size); +} + +always_inline u32 +http_io_ts_read (http_conn_t *hc, u8 *buf, u32 len, u8 peek) +{ + int n_read; + session_t *ts = session_get_from_handle (hc->h_tc_session_handle); + + if (peek) + { + n_read = svm_fifo_peek (ts->rx_fifo, 0, len, buf); + ASSERT (n_read > 0); + return (u32) n_read; + } + + n_read = svm_fifo_dequeue (ts->rx_fifo, len, buf); + ASSERT (n_read == len); + return (u32) n_read; +} + +always_inline void +http_io_ts_read_segs (http_conn_t *hc, svm_fifo_seg_t *segs, u32 *n_segs, + u32 max_bytes) +{ + int n_read; + session_t *ts = session_get_from_handle (hc->h_tc_session_handle); + n_read = svm_fifo_segments (ts->rx_fifo, 0, segs, n_segs, max_bytes); + ASSERT (n_read > 0); +} + +always_inline void +http_io_ts_drain (http_conn_t *hc, u32 len) +{ + session_t *ts = session_get_from_handle (hc->h_tc_session_handle); + svm_fifo_dequeue_drop (ts->rx_fifo, len); +} + +always_inline void +http_io_ts_drain_all (http_conn_t *hc) +{ + session_t *ts = session_get_from_handle (hc->h_tc_session_handle); + svm_fifo_dequeue_drop_all (ts->rx_fifo); +} + +always_inline void +http_io_ts_after_read (http_conn_t *hc, u8 clear_evt) +{ + session_t *ts = session_get_from_handle (hc->h_tc_session_handle); + if (clear_evt) + { + if (svm_fifo_is_empty_cons (ts->rx_fifo)) + svm_fifo_unset_event (ts->rx_fifo); + } + else + { + if (svm_fifo_max_dequeue_cons (ts->rx_fifo)) + session_program_rx_io_evt (hc->h_tc_session_handle); + } +} + +always_inline void +http_io_ts_write (http_conn_t *hc, u8 *data, u32 len, + transport_send_params_t *sp) +{ + int n_written; + session_t *ts = session_get_from_handle (hc->h_tc_session_handle); + + n_written = svm_fifo_enqueue (ts->tx_fifo, len, data); + ASSERT (n_written == len); + if (sp) + { + ASSERT (sp->max_burst_size >= len); + sp->bytes_dequeued += len; + sp->max_burst_size -= len; + } +} + +always_inline u32 +http_io_ts_write_segs (http_conn_t *hc, const svm_fifo_seg_t segs[], + u32 n_segs, transport_send_params_t *sp) +{ + int n_written; + session_t *ts = session_get_from_handle (hc->h_tc_session_handle); + n_written = svm_fifo_enqueue_segments (ts->tx_fifo, segs, n_segs, 0); + ASSERT (n_written > 0); + sp->bytes_dequeued += n_written; + sp->max_burst_size -= n_written; + return (u32) n_written; +} + +always_inline void +http_io_ts_after_write (http_conn_t *hc, transport_send_params_t *sp, u8 flush, + u8 written) +{ + session_t *ts = session_get_from_handle (hc->h_tc_session_handle); + + if (!flush) + { + if (written && svm_fifo_set_event (ts->tx_fifo)) + session_program_tx_io_evt (ts->handle, SESSION_IO_EVT_TX); + + if (sp && (svm_fifo_max_enqueue (ts->tx_fifo) < HTTP_FIFO_THRESH)) + { + /* Deschedule http session and wait for deq notification if + * underlying ts tx fifo almost full */ + svm_fifo_add_want_deq_ntf (ts->tx_fifo, SVM_FIFO_WANT_DEQ_NOTIF); + transport_connection_deschedule (&hc->connection); + sp->flags |= TRANSPORT_SND_F_DESCHED; + } + } + else + { + if (written && svm_fifo_set_event (ts->tx_fifo)) + session_program_tx_io_evt (ts->handle, SESSION_IO_EVT_TX_FLUSH); + } +} + +#endif /* SRC_PLUGINS_HTTP_HTTP_PRIVATE_H_ */ diff --git a/src/plugins/http/http_timer.h b/src/plugins/http/http_timer.h index 43d20d004d8..3bd12f7821d 100644 --- a/src/plugins/http/http_timer.h +++ b/src/plugins/http/http_timer.h @@ -16,7 +16,7 @@ #ifndef SRC_PLUGINS_HTTP_HTTP_TIMER_H_ #define SRC_PLUGINS_HTTP_HTTP_TIMER_H_ -#include <http/http.h> +#include <http/http_private.h> #include <vppinfra/tw_timer_2t_1w_2048sl.h> #define HTTP_CONN_TIMEOUT 60 @@ -45,7 +45,8 @@ http_conn_timer_start (http_conn_t *hc) u32 hs_handle; ASSERT (hc->timer_handle == HTTP_TIMER_HANDLE_INVALID); - hs_handle = hc->c_thread_index << 24 | hc->c_c_index; + ASSERT (hc->h_hc_index <= 0x00FFFFFF); + hs_handle = hc->c_thread_index << 24 | hc->h_hc_index; clib_spinlock_lock (&twc->tw_lock); hc->timer_handle = @@ -79,7 +80,8 @@ http_conn_timer_update (http_conn_t *hc) tw_timer_update_2t_1w_2048sl (&twc->tw, hc->timer_handle, hc->timeout); else { - hs_handle = hc->c_thread_index << 24 | hc->c_c_index; + ASSERT (hc->h_hc_index <= 0x00FFFFFF); + hs_handle = hc->c_thread_index << 24 | hc->h_hc_index; hc->timer_handle = tw_timer_start_2t_1w_2048sl (&twc->tw, hs_handle, 0, hc->timeout); } diff --git a/src/plugins/quic/quic_crypto.c b/src/plugins/quic/quic_crypto.c index 9e2c915daaa..4e11eff2431 100644 --- a/src/plugins/quic/quic_crypto.c +++ b/src/plugins/quic/quic_crypto.c @@ -248,8 +248,7 @@ quic_crypto_decrypt_packet (quic_ctx_t *qctx, quic_rx_packet_ctx_t *pctx) pctx->packet.octets.len - aead_off, pn, pctx->packet.octets.base, aead_off)) == SIZE_MAX) { - fprintf (stderr, "%s: aead decryption failure (pn: %d)\n", __FUNCTION__, - pn); + fprintf (stderr, "%s: aead decryption failure (pn: %d)\n", __func__, pn); return; } @@ -349,8 +348,7 @@ quic_crypto_cipher_setup_crypto (ptls_cipher_context_t *_ctx, int is_enc, } else { - QUIC_DBG (1, "%s, Invalid crypto cipher : ", __FUNCTION__, - _ctx->algo->name); + QUIC_DBG (1, "%s, Invalid crypto cipher : ", __func__, _ctx->algo->name); assert (0); } @@ -405,8 +403,7 @@ quic_crypto_aead_setup_crypto (ptls_aead_context_t *_ctx, int is_enc, } else { - QUIC_DBG (1, "%s, invalied aead cipher %s", __FUNCTION__, - _ctx->algo->name); + QUIC_DBG (1, "%s, invalied aead cipher %s", __func__, _ctx->algo->name); assert (0); } diff --git a/src/plugins/tlspicotls/pico_vpp_crypto.c b/src/plugins/tlspicotls/pico_vpp_crypto.c index 3d28d50b352..e8e4a875e33 100644 --- a/src/plugins/tlspicotls/pico_vpp_crypto.c +++ b/src/plugins/tlspicotls/pico_vpp_crypto.c @@ -107,8 +107,7 @@ ptls_vpp_crypto_cipher_setup_crypto (ptls_cipher_context_t * _ctx, int is_enc, } else { - TLS_DBG (1, "%s, Invalid crypto cipher : ", __FUNCTION__, - _ctx->algo->name); + TLS_DBG (1, "%s, Invalid crypto cipher : ", __func__, _ctx->algo->name); assert (0); } @@ -226,8 +225,7 @@ ptls_vpp_crypto_aead_setup_crypto (ptls_aead_context_t *_ctx, int is_enc, } else { - TLS_DBG (1, "%s, invalied aead cipher %s", __FUNCTION__, - _ctx->algo->name); + TLS_DBG (1, "%s, invalied aead cipher %s", __func__, _ctx->algo->name); return -1; } diff --git a/src/vlib/main.c b/src/vlib/main.c index a2f833711ab..731e788d30d 100644 --- a/src/vlib/main.c +++ b/src/vlib/main.c @@ -972,7 +972,7 @@ dispatch_node (vlib_main_t * vm, { ELOG_TYPE_DECLARE (e) = { - .function = (char *) __FUNCTION__, + .function = (char *) __func__, .format = "%s vector length %d, switching to %s", .format_args = "T4i4t4", .n_enum_strings = 2, diff --git a/src/vlib/threads.c b/src/vlib/threads.c index fa8d949d549..9dce1b8c8bd 100644 --- a/src/vlib/threads.c +++ b/src/vlib/threads.c @@ -1304,7 +1304,7 @@ vlib_worker_thread_initial_barrier_sync_and_release (vlib_main_t * vm) { if ((now = vlib_time_now (vm)) > deadline) { - fformat (stderr, "%s: worker thread deadlock\n", __FUNCTION__); + fformat (stderr, "%s: worker thread deadlock\n", __func__); os_panic (); } CLIB_PAUSE (); @@ -1409,7 +1409,7 @@ vlib_worker_thread_barrier_sync_int (vlib_main_t * vm, const char *func_name) { if ((now = vlib_time_now (vm)) > deadline) { - fformat (stderr, "%s: worker thread deadlock\n", __FUNCTION__); + fformat (stderr, "%s: worker thread deadlock\n", __func__); os_panic (); } } @@ -1485,7 +1485,7 @@ vlib_worker_thread_barrier_release (vlib_main_t * vm) { if ((now = vlib_time_now (vm)) > deadline) { - fformat (stderr, "%s: worker thread deadlock\n", __FUNCTION__); + fformat (stderr, "%s: worker thread deadlock\n", __func__); os_panic (); } } @@ -1502,7 +1502,7 @@ vlib_worker_thread_barrier_release (vlib_main_t * vm) if ((now = vlib_time_now (vm)) > deadline) { fformat (stderr, "%s: worker thread refork deadlock\n", - __FUNCTION__); + __func__); os_panic (); } } diff --git a/src/vlib/threads.h b/src/vlib/threads.h index c671aa78c39..da2c41fec73 100644 --- a/src/vlib/threads.h +++ b/src/vlib/threads.h @@ -166,7 +166,10 @@ u32 vlib_frame_queue_main_init (u32 node_index, u32 frame_queue_nelts); #define BARRIER_SYNC_TIMEOUT (1.0) #endif -#define vlib_worker_thread_barrier_sync(X) {vlib_worker_thread_barrier_sync_int(X, __FUNCTION__);} +#define vlib_worker_thread_barrier_sync(X) \ + { \ + vlib_worker_thread_barrier_sync_int (X, __func__); \ + } void vlib_worker_thread_barrier_sync_int (vlib_main_t * vm, const char *func_name); @@ -195,7 +198,7 @@ vlib_smp_unsafe_warning (void) if (CLIB_DEBUG > 0) { if (vlib_get_thread_index ()) - fformat (stderr, "%s: SMP unsafe warning...\n", __FUNCTION__); + fformat (stderr, "%s: SMP unsafe warning...\n", __func__); } } diff --git a/src/vnet/session/session_lookup.c b/src/vnet/session/session_lookup.c index 28a1feb1ed8..7678b0e0761 100644 --- a/src/vnet/session/session_lookup.c +++ b/src/vnet/session/session_lookup.c @@ -1380,6 +1380,71 @@ session_lookup_connection (u32 fib_index, ip46_address_t * lcl, lcl_port, rmt_port, proto); } +/** + * Lookup exact match 6-tuple amongst established and half-open sessions + * + * Does not look into session rules table and does not try to find a listener. + */ +transport_connection_t * +session_lookup_6tuple (u32 fib_index, ip46_address_t *lcl, ip46_address_t *rmt, + u16 lcl_port, u16 rmt_port, u8 proto, u8 is_ip4) +{ + session_table_t *st; + session_t *s; + int rv; + + if (is_ip4) + { + session_kv4_t kv4; + + st = session_table_get_for_fib_index (FIB_PROTOCOL_IP4, fib_index); + if (PREDICT_FALSE (!st)) + return 0; + + /* + * Lookup session amongst established ones + */ + make_v4_ss_kv (&kv4, &lcl->ip4, &rmt->ip4, lcl_port, rmt_port, proto); + rv = clib_bihash_search_inline_16_8 (&st->v4_session_hash, &kv4); + if (rv == 0) + { + s = session_get_from_handle (kv4.value); + return transport_get_connection (proto, s->connection_index, + s->thread_index); + } + + /* + * Try half-open connections + */ + rv = clib_bihash_search_inline_16_8 (&st->v4_half_open_hash, &kv4); + if (rv == 0) + return transport_get_half_open (proto, kv4.value & 0xFFFFFFFF); + } + else + { + session_kv6_t kv6; + + st = session_table_get_for_fib_index (FIB_PROTOCOL_IP6, fib_index); + if (PREDICT_FALSE (!st)) + return 0; + + make_v6_ss_kv (&kv6, &lcl->ip6, &rmt->ip6, lcl_port, rmt_port, proto); + rv = clib_bihash_search_inline_48_8 (&st->v6_session_hash, &kv6); + if (rv == 0) + { + s = session_get_from_handle (kv6.value); + return transport_get_connection (proto, s->connection_index, + s->thread_index); + } + + /* Try half-open connections */ + rv = clib_bihash_search_inline_48_8 (&st->v6_half_open_hash, &kv6); + if (rv == 0) + return transport_get_half_open (proto, kv6.value & 0xFFFFFFFF); + } + return 0; +} + session_error_t vnet_session_rule_add_del (session_rule_add_del_args_t *args) { diff --git a/src/vnet/session/session_lookup.h b/src/vnet/session/session_lookup.h index 9f56af20a87..8f9ff7ee9bc 100644 --- a/src/vnet/session/session_lookup.h +++ b/src/vnet/session/session_lookup.h @@ -72,6 +72,9 @@ transport_connection_t *session_lookup_connection (u32 fib_index, ip46_address_t * rmt, u16 lcl_port, u16 rmt_port, u8 proto, u8 is_ip4); +transport_connection_t * +session_lookup_6tuple (u32 fib_index, ip46_address_t *lcl, ip46_address_t *rmt, + u16 lcl_port, u16 rmt_port, u8 proto, u8 is_ip4); session_t *session_lookup_listener4 (u32 fib_index, ip4_address_t * lcl, u16 lcl_port, u8 proto, u8 use_wildcard); session_t *session_lookup_listener6 (u32 fib_index, ip6_address_t * lcl, diff --git a/src/vnet/session/transport.c b/src/vnet/session/transport.c index ac9b54f333a..015255ecf08 100644 --- a/src/vnet/session/transport.c +++ b/src/vnet/session/transport.c @@ -661,8 +661,8 @@ transport_alloc_local_port (u8 proto, ip46_address_t *lcl_addr, break; /* IP:port pair already in use, check if 6-tuple available */ - if (session_lookup_connection (rmt->fib_index, lcl_addr, &rmt->ip, port, - rmt->port, proto, rmt->is_ip4)) + if (session_lookup_6tuple (rmt->fib_index, lcl_addr, &rmt->ip, port, + rmt->port, proto, rmt->is_ip4)) continue; /* 6-tuple is available so increment lcl endpoint refcount */ @@ -792,9 +792,9 @@ transport_alloc_local_endpoint (u8 proto, transport_endpoint_cfg_t * rmt_cfg, return 0; /* IP:port pair already in use, check if 6-tuple available */ - if (session_lookup_connection (rmt->fib_index, lcl_addr, &rmt->ip, - rmt_cfg->peer.port, rmt->port, proto, - rmt->is_ip4)) + if (session_lookup_6tuple (rmt->fib_index, lcl_addr, &rmt->ip, + rmt_cfg->peer.port, rmt->port, proto, + rmt->is_ip4)) return SESSION_E_PORTINUSE; /* 6-tuple is available so increment lcl endpoint refcount */ diff --git a/src/vppinfra/clib_error.h b/src/vppinfra/clib_error.h index 45f18eb1fe4..5db1a5e3440 100644 --- a/src/vppinfra/clib_error.h +++ b/src/vppinfra/clib_error.h @@ -23,7 +23,7 @@ typedef struct /* Error message. */ u8 *what; - /* Where error occurred (e.g. __FUNCTION__ __LINE__) */ + /* Where error occurred (e.g. __func__ __LINE__) */ const u8 *where; uword flags; diff --git a/src/vppinfra/elog.h b/src/vppinfra/elog.h index d0825bdd5b2..6a66319148d 100644 --- a/src/vppinfra/elog.h +++ b/src/vppinfra/elog.h @@ -444,21 +444,21 @@ elog_data_inline (elog_main_t * em, elog_event_type_t * type, #define ELOG_TYPE_INIT_FORMAT_AND_FUNCTION(fmt,func) \ { .format = fmt, .function = func, } -#define ELOG_TYPE_INIT(fmt) \ - ELOG_TYPE_INIT_FORMAT_AND_FUNCTION(fmt,(char *) __FUNCTION__) +#define ELOG_TYPE_INIT(fmt) \ + ELOG_TYPE_INIT_FORMAT_AND_FUNCTION (fmt, (char *) __func__) #define ELOG_TYPE_DECLARE_HELPER(f,fmt,func) \ static elog_event_type_t __ELOG_TYPE_VAR(f) = \ ELOG_TYPE_INIT_FORMAT_AND_FUNCTION (fmt, func) -#define ELOG_TYPE_DECLARE_FORMAT_AND_FUNCTION(f,fmt) \ - ELOG_TYPE_DECLARE_HELPER (f, fmt, (char *) __FUNCTION__) +#define ELOG_TYPE_DECLARE_FORMAT_AND_FUNCTION(f, fmt) \ + ELOG_TYPE_DECLARE_HELPER (f, fmt, (char *) __func__) #define ELOG_TYPE_DECLARE_FORMAT(f,fmt) \ ELOG_TYPE_DECLARE_HELPER (f, fmt, 0) -/* Shorthands with and without __FUNCTION__. - D for decimal; X for hex. F for __FUNCTION__. */ +/* Shorthands with and without __func__. + D for decimal; X for hex. F for __func__. */ #define ELOG_TYPE(f,fmt) ELOG_TYPE_DECLARE_FORMAT_AND_FUNCTION(f,fmt) #define ELOG_TYPE_D(f) ELOG_TYPE_DECLARE_FORMAT (f, #f " %d") #define ELOG_TYPE_X(f) ELOG_TYPE_DECLARE_FORMAT (f, #f " 0x%x") diff --git a/src/vppinfra/error_bootstrap.h b/src/vppinfra/error_bootstrap.h index ae23d1bcca8..d3eed1b83ae 100644 --- a/src/vppinfra/error_bootstrap.h +++ b/src/vppinfra/error_bootstrap.h @@ -53,7 +53,7 @@ enum }; /* Current function name. Need (char *) cast to silence gcc4 pointer signedness warning. */ -#define clib_error_function ((char *) __FUNCTION__) +#define clib_error_function ((char *) __func__) #ifndef CLIB_ASSERT_ENABLE #define CLIB_ASSERT_ENABLE (CLIB_DEBUG > 0) diff --git a/src/vppinfra/mem.h b/src/vppinfra/mem.h index 6211bb51f0a..893978081d0 100644 --- a/src/vppinfra/mem.h +++ b/src/vppinfra/mem.h @@ -259,7 +259,7 @@ uword clib_mem_size (void *p); void clib_mem_free_s (void *p); /* Memory allocator which panics when it fails. - Use macro so that clib_panic macro can expand __FUNCTION__ and __LINE__. */ + Use macro so that clib_panic macro can expand __func__ and __LINE__. */ #define clib_mem_alloc_aligned_no_fail(size,align) \ ({ \ uword _clib_mem_alloc_size = (size); \ diff --git a/src/vppinfra/string.c b/src/vppinfra/string.c index ea9480875a5..aedaf428a31 100644 --- a/src/vppinfra/string.c +++ b/src/vppinfra/string.c @@ -94,7 +94,7 @@ clib_memswap (void *_a, void *_b, uword bytes) __clib_export void clib_c11_violation (const char *s) { - _clib_error (CLIB_ERROR_WARNING, (char *) __FUNCTION__, 0, (char *) s); + _clib_error (CLIB_ERROR_WARNING, (char *) __func__, 0, (char *) s); } /** |