From: Matus Fabian Date: Thu, 22 May 2025 17:45:08 +0000 (+0000) Subject: http: http/2 CONTINUATION frame support X-Git-Url: https://gerrit.fd.io/r/gitweb?a=commitdiff_plain;h=refs%2Fchanges%2F07%2F43007%2F15;p=vpp.git http: http/2 CONTINUATION frame support We can now receive and send headers split into multiple frames. Type: improvement Change-Id: I3a2a21c67dbc7bbd0bc19b8c6a0ff1516bcfc6fd Signed-off-by: Matus Fabian --- diff --git a/extras/hs-test/http2_test.go b/extras/hs-test/http2_test.go index dc2d210000d..0d170eb121b 100644 --- a/extras/hs-test/http2_test.go +++ b/extras/hs-test/http2_test.go @@ -2,6 +2,7 @@ package main import ( "fmt" + "strconv" "strings" "time" @@ -9,7 +10,7 @@ import ( ) func init() { - RegisterH2Tests(Http2TcpGetTest, Http2TcpPostTest, Http2MultiplexingTest, Http2TlsTest) + RegisterH2Tests(Http2TcpGetTest, Http2TcpPostTest, Http2MultiplexingTest, Http2TlsTest, Http2ContinuationTxTest) RegisterH2SoloTests(Http2MultiplexingMTTest) } @@ -106,3 +107,16 @@ func Http2TlsTest(s *H2Suite) { s.AssertContains(log, "ALPN: server accepted h2") s.AssertContains(writeOut, "version") } + +func Http2ContinuationTxTest(s *H2Suite) { + vpp := s.Containers.Vpp.VppInstance + serverAddress := s.VppAddr() + ":" + s.Ports.Port1 + vpp.Vppctl("http tps uri tcp://" + serverAddress + " no-zc") + args := fmt.Sprintf("-w %%{size_header} --max-time 10 --noproxy '*' --http2-prior-knowledge http://%s/test_file_64?test_header=32k", serverAddress) + writeOut, log := s.RunCurlContainer(s.Containers.Curl, args) + s.AssertContains(log, "HTTP/2 200") + s.AssertContains(log, "[64 bytes data]") + sizeHeader, err := strconv.Atoi(strings.ReplaceAll(writeOut, "\x00", "")) + s.AssertNil(err, fmt.Sprint(err)) + s.AssertGreaterThan(sizeHeader, 32768) +} diff --git a/extras/hs-test/http_test.go b/extras/hs-test/http_test.go index 953898d509e..19f54d17000 100644 --- a/extras/hs-test/http_test.go +++ b/extras/hs-test/http_test.go @@ -1192,11 +1192,11 @@ func HttpCliBadRequestTest(s *NoTopoSuite) { func HttpStaticHttp1OnlyTest(s *NoTopoSuite) { vpp := s.Containers.Vpp.VppInstance - serverAddress := s.VppAddr() - s.Log(vpp.Vppctl("http static server uri tls://" + serverAddress + "/80 url-handlers http1-only debug")) + serverAddress := s.VppAddr() + ":" + s.Ports.Http + s.Log(vpp.Vppctl("http static server uri tls://" + serverAddress + " url-handlers http1-only debug")) client := NewHttpClient(defaultHttpTimeout, true) - req, err := http.NewRequest("GET", "https://"+serverAddress+":80/version.json", nil) + req, err := http.NewRequest("GET", "https://"+serverAddress+"/version.json", nil) s.AssertNil(err, fmt.Sprint(err)) resp, err := client.Do(req) s.AssertNil(err, fmt.Sprint(err)) diff --git a/extras/hs-test/infra/suite_h2.go b/extras/hs-test/infra/suite_h2.go index 36fc29493ca..b00708645d6 100644 --- a/extras/hs-test/infra/suite_h2.go +++ b/extras/hs-test/infra/suite_h2.go @@ -88,7 +88,6 @@ func (s *H2Suite) VppAddr() string { return s.Interfaces.Tap.Peer.Ip4AddressString() } -// Marked as pending since http plugin is not build with http/2 enabled by default var _ = Describe("Http2Suite", Ordered, ContinueOnFailure, func() { var s H2Suite BeforeAll(func() { @@ -118,7 +117,6 @@ var _ = Describe("Http2Suite", Ordered, ContinueOnFailure, func() { } }) -// Marked as pending since http plugin is not build with http/2 enabled by default var _ = Describe("Http2SoloSuite", Ordered, ContinueOnFailure, Serial, func() { var s H2Suite BeforeAll(func() { @@ -230,26 +228,21 @@ var http2Tests = []h2specTest{ {desc: "http2/5.1/1"}, {desc: "http2/5.1/2"}, {desc: "http2/5.1/3"}, - // TODO: CONTINUATION - // {desc: "http2/5.1/4"}, + {desc: "http2/5.1/4"}, {desc: "http2/5.1/5"}, {desc: "http2/5.1/6"}, - // TODO: CONTINUATION - // {desc: "http2/5.1/7"}, + {desc: "http2/5.1/7"}, {desc: "http2/5.1/8"}, {desc: "http2/5.1/9"}, - // TODO: CONTINUATION - // {desc: "http2/5.1/10"}, + {desc: "http2/5.1/10"}, {desc: "http2/5.1/11"}, {desc: "http2/5.1/12"}, - // TODO: CONTINUATION - // {desc: "http2/5.1/13"}, + {desc: "http2/5.1/13"}, // http2/5.3.1/* PRIORITY is deprecated {desc: "http2/5.4.1/1"}, {desc: "http2/5.4.1/2"}, {desc: "http2/5.5/1"}, - // TODO: CONTINUATION - // {desc: "http2/5.5/2"}, + {desc: "http2/5.5/2"}, {desc: "http2/6.1/1"}, {desc: "http2/6.1/2"}, {desc: "http2/6.1/3"}, @@ -287,13 +280,12 @@ var http2Tests = []h2specTest{ // TODO: message framing without content length using END_STREAM flag // {desc: "http2/6.9/2"}, {desc: "http2/6.9/3"}, - // TODO: CONTINUATION - // {desc: "http2/6.10/1"}, - // {desc: "http2/6.10/2"}, - // {desc: "http2/6.10/3"}, - // {desc: "http2/6.10/4"}, - // {desc: "http2/6.10/5"}, - // {desc: "http2/6.10/6"}, + {desc: "http2/6.10/1"}, + {desc: "http2/6.10/2"}, + {desc: "http2/6.10/3"}, + {desc: "http2/6.10/4"}, + {desc: "http2/6.10/5"}, + {desc: "http2/6.10/6"}, {desc: "http2/7/1"}, // TODO: message framing without content length using END_STREAM flag // {desc: "http2/7/2"}, @@ -363,14 +355,14 @@ var _ = Describe("H2SpecSuite", Ordered, ContinueOnFailure, func() { s.Log(testName + ": BEGIN") vpp := s.Containers.Vpp.VppInstance serverAddress := s.VppAddr() - s.Log(vpp.Vppctl("http static server uri tls://" + serverAddress + "/" + s.Ports.Port1 + " url-handlers debug 2")) + s.Log(vpp.Vppctl("http static server uri tls://" + serverAddress + "/" + s.Ports.Port1 + " url-handlers debug 2 fifo-size 16k")) s.Log(vpp.Vppctl("test-url-handler enable")) conf := &config.Config{ Host: serverAddress, Port: s.Ports.Port1AsInt, Path: "/test1", Timeout: time.Second * 5, - MaxHeaderLen: 1024, + MaxHeaderLen: 4096, TLS: true, Insecure: true, Sections: []string{test.desc}, diff --git a/src/plugins/hs_apps/http_tps.c b/src/plugins/hs_apps/http_tps.c index 486d4a525e3..617ced57ac3 100644 --- a/src/plugins/hs_apps/http_tps.c +++ b/src/plugins/hs_apps/http_tps.c @@ -73,6 +73,7 @@ typedef struct hs_main_ u8 no_zc; u8 *default_uri; u32 seed; + u8 *test_header_value; } hts_main_t; static hts_main_t hts_main; @@ -370,8 +371,10 @@ hts_ts_rx_callback (session_t *ts) { hts_main_t *htm = &hts_main; hts_session_t *hs; - u8 *target = 0; + u8 *target = 0, *query = 0; http_msg_t msg; + unformat_input_t input; + u64 test_header_len; int rv; hs = hts_session_get (ts->thread_index, ts->opaque); @@ -415,6 +418,35 @@ hts_ts_rx_callback (session_t *ts) msg.method_type == HTTP_REQ_GET ? "GET" : "POST", target); + if (msg.data.target_query_len != 0) + { + vec_validate (query, msg.data.target_query_len - 1); + rv = svm_fifo_peek (ts->rx_fifo, msg.data.target_query_offset, + msg.data.target_query_len, query); + ASSERT (rv == msg.data.target_query_len); + if (htm->debug_level) + clib_warning ("query: %v", query); + unformat_init_vector (&input, query); + if (unformat (&input, "test_header=%U", unformat_memory_size, + &test_header_len)) + { + if (test_header_len > vec_len (htm->test_header_value)) + { + test_header_len = vec_len (htm->test_header_value); + clib_warning ("test_header_len too big, truncated to %U", + format_memory_size, test_header_len); + } + vec_resize (hs->resp_headers_buf, + sizeof (http_app_header_t) + test_header_len); + hs->resp_headers.len = vec_len (hs->resp_headers_buf); + hs->resp_headers.buf = hs->resp_headers_buf; + http_add_custom_header ( + &hs->resp_headers, http_token_lit ("x-test"), + (const char *) htm->test_header_value, test_header_len); + } + vec_free (query); + } + if (msg.method_type == HTTP_REQ_GET) { if (try_test_file (hs, target)) @@ -766,6 +798,8 @@ hts_create (vlib_main_t *vm) if (htm->no_zc) vec_validate (htm->test_data, (64 << 10) - 1); + vec_validate_init_empty (htm->test_header_value, htm->fifo_size - 1024, 'x'); + if (hts_attach (htm)) { clib_warning ("failed to attach server"); diff --git a/src/plugins/http/http.c b/src/plugins/http/http.c index 2c923c69eed..951bf3ad96e 100644 --- a/src/plugins/http/http.c +++ b/src/plugins/http/http.c @@ -317,6 +317,7 @@ http_get_app_header_list (http_req_t *req, http_msg_t *msg) else { app_headers = hm->app_header_lists[as->thread_index]; + vec_validate (app_headers, msg->data.headers_len - 1); rv = svm_fifo_dequeue (as->tx_fifo, msg->data.headers_len, app_headers); ASSERT (rv == msg->data.headers_len); } @@ -837,7 +838,7 @@ http_transport_enable (vlib_main_t *vm, u8 is_en) vec_validate (hm->tx_bufs[i], HTTP_UDP_PAYLOAD_MAX_LEN + HTTP_UDP_PROXY_DATAGRAM_CAPSULE_OVERHEAD); - vec_validate (hm->app_header_lists[i], 32 << 10); + vec_validate (hm->app_header_lists[i], 64 << 10); } clib_timebase_init (&hm->timebase, 0 /* GMT */, CLIB_TIMEBASE_DAYLIGHT_NONE, diff --git a/src/plugins/http/http2/frame.c b/src/plugins/http/http2/frame.c index 580ffff22c5..07821de3be7 100644 --- a/src/plugins/http/http2/frame.c +++ b/src/plugins/http/http2/frame.c @@ -204,15 +204,13 @@ http2_frame_write_rst_stream (http2_error_t error_code, u32 stream_id, clib_memcpy_fast (p, &value, RST_STREAM_LENGTH); } -#define GOAWAY_MIN_SIZE 8 - __clib_export http2_error_t http2_frame_read_goaway (u32 *error_code, u32 *last_stream_id, u8 *payload, u32 payload_len) { u32 *value; - if (payload_len < GOAWAY_MIN_SIZE) + if (payload_len < HTTP2_GOAWAY_MIN_SIZE) return HTTP2_ERROR_FRAME_SIZE_ERROR; value = (u32 *) payload; @@ -222,7 +220,6 @@ http2_frame_read_goaway (u32 *error_code, u32 *last_stream_id, u8 *payload, value = (u32 *) payload; *error_code = clib_net_to_host_u32 (*value); - /* TODO: Additional Debug Data */ return HTTP2_ERROR_NO_ERROR; } @@ -236,11 +233,11 @@ http2_frame_write_goaway (http2_error_t error_code, u32 last_stream_id, ASSERT (last_stream_id <= 0x7FFFFFFF); http2_frame_header_t fh = { .type = HTTP2_FRAME_TYPE_GOAWAY, - .length = GOAWAY_MIN_SIZE }; + .length = HTTP2_GOAWAY_MIN_SIZE }; p = http2_frame_header_alloc (dst); http2_frame_header_write (&fh, p); - vec_add2 (*dst, p, GOAWAY_MIN_SIZE); + vec_add2 (*dst, p, HTTP2_GOAWAY_MIN_SIZE); value = clib_host_to_net_u32 (last_stream_id); clib_memcpy_fast (p, &value, 4); p += 4; @@ -308,6 +305,19 @@ http2_frame_write_headers_header (u32 headers_len, u32 stream_id, u8 flags, http2_frame_header_write (&fh, dst); } +void +http2_frame_write_continuation_header (u32 headers_len, u32 stream_id, + u8 flags, u8 *dst) +{ + ASSERT (stream_id > 0 && stream_id <= 0x7FFFFFFF); + + http2_frame_header_t fh = { .type = HTTP2_FRAME_TYPE_CONTINUATION, + .length = headers_len, + .flags = flags, + .stream_id = stream_id }; + http2_frame_header_write (&fh, dst); +} + __clib_export http2_error_t http2_frame_read_data (u8 **data, u32 *data_len, u8 *payload, u32 payload_len, u8 flags) diff --git a/src/plugins/http/http2/frame.h b/src/plugins/http/http2/frame.h index 53a37c1aa0a..e19cfa7a698 100644 --- a/src/plugins/http/http2/frame.h +++ b/src/plugins/http/http2/frame.h @@ -11,6 +11,7 @@ #define HTTP2_FRAME_HEADER_SIZE 9 #define HTTP2_PING_PAYLOAD_LEN 8 +#define HTTP2_GOAWAY_MIN_SIZE 8 #define foreach_http2_frame_type \ _ (0x00, DATA, "DATA") \ @@ -161,14 +162,14 @@ void http2_frame_write_rst_stream (http2_error_t error_code, u32 stream_id, /** * Parse GOAWAY frame payload * - * @param last_stream_id Parsed last stream ID * @param error_code Parsed error code + * @param last_stream_id Parsed last stream ID * @param payload Payload to parse * @param payload_len Payload length * * @return @c HTTP2_ERROR_NO_ERROR on success, error otherwise */ -http2_error_t http2_frame_read_goaway (u32 *last_stream_id, u32 *error_code, +http2_error_t http2_frame_read_goaway (u32 *error_code, u32 *last_stream_id, u8 *payload, u32 payload_len); /** @@ -218,6 +219,19 @@ http2_error_t http2_frame_read_headers (u8 **headers, u32 *headers_len, void http2_frame_write_headers_header (u32 headers_len, u32 stream_id, u8 flags, u8 *dst); +/** + * Write CONTINUATION frame header + * + * @param headers_len Header block fragment length + * @param stream_id Stream ID, except 0 + * @param flags Frame header flags + * @param dst Pointer where frame header will be written + * + * @note Use @c http2_frame_header_alloc before + */ +void http2_frame_write_continuation_header (u32 headers_len, u32 stream_id, + u8 flags, u8 *dst); + /** * Parse DATA frame payload * diff --git a/src/plugins/http/http2/http2.c b/src/plugins/http/http2/http2.c index 38a3cca4f82..8d470835547 100644 --- a/src/plugins/http/http2/http2.c +++ b/src/plugins/http/http2/http2.c @@ -56,10 +56,13 @@ typedef struct http2_req_ u8 *payload; u32 payload_len; clib_llist_anchor_t sched_list; + void (*dispatch_headers_cb) (struct http2_req_ *req, http_conn_t *hc, + u8 *n_emissions, clib_llist_index_t *next_ri); } http2_req_t; #define foreach_http2_conn_flags \ _ (EXPECT_PREFACE, "expect-preface") \ + _ (EXPECT_CONTINUATION, "expect-continuation") \ _ (PREFACE_VERIFIED, "preface-verified") \ _ (TS_DESCHED, "ts-descheduled") @@ -92,6 +95,9 @@ typedef struct http2_conn_ctx_ clib_llist_index_t old_tx_streams; /* data */ http2_conn_settings_t settings; clib_llist_anchor_t sched_list; + u8 *unparsed_headers; /* temporary storing rx fragmented headers */ + u8 *unsent_headers; /* temporary storing tx fragmented headers */ + u32 unsent_headers_offset; } http2_conn_ctx_t; typedef struct http2_worker_ctx_ @@ -99,6 +105,7 @@ typedef struct http2_worker_ctx_ http2_conn_ctx_t *conn_pool; http2_req_t *req_pool; clib_llist_index_t sched_head; + u8 *header_list; /* buffer for headers decompression */ } http2_worker_ctx_t; typedef struct http2_main_ @@ -111,6 +118,7 @@ typedef struct http2_main_ typedef enum { HTTP2_SCHED_WEIGHT_DATA_PTR = 1, + HTTP2_SCHED_WEIGHT_HEADERS_CONTINUATION = 1, HTTP2_SCHED_WEIGHT_DATA_INLINE = 2, HTTP2_SCHED_WEIGHT_HEADERS_PTR = 3, HTTP2_SCHED_WEIGHT_HEADERS_INLINE = 4, @@ -449,17 +457,77 @@ http2_send_server_preface (http_conn_t *hc) /* stream TX scheduler */ /***********************/ +static void +http2_sched_dispatch_continuation (http2_req_t *req, http_conn_t *hc, + u8 *n_emissions, + clib_llist_index_t *next_ri) +{ + u8 fh[HTTP2_FRAME_HEADER_SIZE]; + u8 flags = 0; + u32 n_written, stream_id, max_write, headers_len, headers_left; + http2_conn_ctx_t *h2c; + http2_worker_ctx_t *wrk = http2_get_worker (hc->c_thread_index); + + *n_emissions += HTTP2_SCHED_WEIGHT_HEADERS_CONTINUATION; + + h2c = http2_conn_ctx_get_w_thread (hc); + + max_write = http_io_ts_max_write (hc, 0); + max_write -= HTTP2_FRAME_HEADER_SIZE; + max_write = clib_min (max_write, h2c->peer_settings.max_frame_size); + + stream_id = req->stream_id; + + ASSERT (vec_len (h2c->unsent_headers) > h2c->unsent_headers_offset); + headers_left = vec_len (h2c->unsent_headers) - h2c->unsent_headers_offset; + headers_len = clib_min (max_write, headers_left); + flags |= (headers_len == headers_left) ? HTTP2_FRAME_FLAG_END_HEADERS : 0; + http2_frame_write_continuation_header (headers_len, stream_id, flags, fh); + svm_fifo_seg_t segs[2] = { + { fh, HTTP2_FRAME_HEADER_SIZE }, + { h2c->unsent_headers + h2c->unsent_headers_offset, headers_len } + }; + n_written = http_io_ts_write_segs (hc, segs, 2, 0); + ASSERT (n_written == (HTTP2_FRAME_HEADER_SIZE + headers_len)); + http_io_ts_after_write (hc, 0); + + if (headers_len == headers_left) + { + HTTP_DBG (1, "sent last headers fragment"); + vec_free (h2c->unsent_headers); + *next_ri = clib_llist_next_index (req, sched_list); + clib_llist_remove (wrk->req_pool, sched_list, req); + flags |= HTTP2_FRAME_FLAG_END_HEADERS; + if (http_buffer_bytes_left (&req->base.tx_buf)) + { + /* start sending the actual data */ + HTTP_DBG (1, "adding to data queue req_index %x", + ((http_req_handle_t) req->base.hr_req_handle).req_index); + http2_req_schedule_data_tx (hc, req); + } + else + http2_stream_close (req, hc); + } + else + { + HTTP_DBG (1, "need another headers fragment"); + *next_ri = clib_llist_entry_index (wrk->req_pool, req); + h2c->unsent_headers_offset += headers_len; + } +} + static void http2_sched_dispatch_headers (http2_req_t *req, http_conn_t *hc, - u8 *n_emissions) + u8 *n_emissions, clib_llist_index_t *next_ri) { http_msg_t msg; u8 *response, *date, *app_headers = 0; u8 fh[HTTP2_FRAME_HEADER_SIZE]; hpack_response_control_data_t control_data; - u8 flags = HTTP2_FRAME_FLAG_END_HEADERS; - u32 n_written, stream_id, n_deq; + u8 flags = 0; + u32 n_written, stream_id, n_deq, max_write, headers_len, headers_left; http2_conn_ctx_t *h2c; + http2_worker_ctx_t *wrk = http2_get_worker (hc->c_thread_index); http_get_app_msg (&req->base, &msg); ASSERT (msg.type == HTTP_MSG_REPLY); @@ -488,38 +556,62 @@ http2_sched_dispatch_headers (http2_req_t *req, http_conn_t *hc, hpack_serialize_response (app_headers, msg.data.headers_len, &control_data, &response); vec_free (date); + headers_len = vec_len (response); h2c = http2_conn_ctx_get_w_thread (hc); - if (vec_len (response) > h2c->peer_settings.max_frame_size) - { - /* TODO: CONTINUATION (headers fragmentation) */ - clib_warning ("resp headers greater than SETTINGS_MAX_FRAME_SIZE"); - http2_stream_error (hc, req, HTTP2_ERROR_INTERNAL_ERROR, 0); - return; - } + + max_write = http_io_ts_max_write (hc, 0); + max_write -= HTTP2_FRAME_HEADER_SIZE; + max_write = clib_min (max_write, h2c->peer_settings.max_frame_size); stream_id = req->stream_id; + + /* END_STREAM flag need to be set in HEADERS frame */ if (msg.data.body_len) { - /* start sending the actual data */ http_req_tx_buffer_init (&req->base, &msg); - HTTP_DBG (1, "adding to data queue req_index %x", - ((http_req_handle_t) req->base.hr_req_handle).req_index); - http2_req_schedule_data_tx (hc, req); http_io_as_dequeue_notify (&req->base, n_deq); } else + flags |= HTTP2_FRAME_FLAG_END_STREAM; + + if (headers_len <= max_write) { - /* no response body, we are done */ - flags |= HTTP2_FRAME_FLAG_END_STREAM; - http2_stream_close (req, hc); + *next_ri = clib_llist_next_index (req, sched_list); + clib_llist_remove (wrk->req_pool, sched_list, req); + flags |= HTTP2_FRAME_FLAG_END_HEADERS; + if (msg.data.body_len) + { + /* start sending the actual data */ + HTTP_DBG (1, "adding to data queue req_index %x", + ((http_req_handle_t) req->base.hr_req_handle).req_index); + http2_req_schedule_data_tx (hc, req); + } + else + http2_stream_close (req, hc); } - - http2_frame_write_headers_header (vec_len (response), stream_id, flags, fh); + else + { + /* we need to send CONTINUATION frame as next */ + HTTP_DBG (1, "response headers need to be fragmented"); + *next_ri = clib_llist_entry_index (wrk->req_pool, req); + headers_len = max_write; + headers_left = vec_len (response) - headers_len; + req->dispatch_headers_cb = http2_sched_dispatch_continuation; + /* move unsend portion of headers to connection ctx */ + ASSERT (h2c->unsent_headers == 0); + vec_validate (h2c->unsent_headers, headers_left - 1); + clib_memcpy_fast (h2c->unsent_headers, response + headers_len, + headers_left); + h2c->unsent_headers_offset = 0; + *n_emissions += HTTP2_SCHED_WEIGHT_HEADERS_CONTINUATION; + } + + http2_frame_write_headers_header (headers_len, stream_id, flags, fh); svm_fifo_seg_t segs[2] = { { fh, HTTP2_FRAME_HEADER_SIZE }, - { response, vec_len (response) } }; + { response, headers_len } }; n_written = http_io_ts_write_segs (hc, segs, 2, 0); - ASSERT (n_written == (HTTP2_FRAME_HEADER_SIZE + vec_len (response))); + ASSERT (n_written == (HTTP2_FRAME_HEADER_SIZE + headers_len)); http_io_ts_after_write (hc, 0); } @@ -641,11 +733,9 @@ http2_update_time_callback (f64 now, u8 thread_index) n_emissions < HTTP2_SCHED_MAX_EMISSIONS) { req = clib_llist_elt (wrk->req_pool, ri); - ri = clib_llist_next_index (req, sched_list); HTTP_DBG (1, "sending headers req_index %x", ((http_req_handle_t) req->base.hr_req_handle).req_index); - clib_llist_remove (wrk->req_pool, sched_list, req); - http2_sched_dispatch_headers (req, hc, &n_emissions); + req->dispatch_headers_cb (req, hc, &n_emissions, &ri); } /* handle old responses (data frames), if we had any prior to processing @@ -699,24 +789,26 @@ http2_req_state_wait_transport_method (http_conn_t *hc, http2_req_t *req, { http2_conn_ctx_t *h2c; hpack_request_control_data_t control_data; - u8 *buf = 0; http_msg_t msg; int rv; http_req_state_t new_state = HTTP_REQ_STATE_WAIT_APP_REPLY; + http2_worker_ctx_t *wrk = http2_get_worker (hc->c_thread_index); h2c = http2_conn_ctx_get_w_thread (hc); - /* TODO: configurable buf size with bigger default value */ - vec_validate_init_empty (buf, 1023, 0); - *error = hpack_parse_request (req->payload, req->payload_len, buf, 1023, - &control_data, &req->base.headers, - &h2c->decoder_dynamic_table); + *error = + hpack_parse_request (req->payload, req->payload_len, wrk->header_list, + vec_len (wrk->header_list), &control_data, + &req->base.headers, &h2c->decoder_dynamic_table); if (*error != HTTP2_ERROR_NO_ERROR) { HTTP_DBG (1, "hpack_parse_request failed"); return HTTP_SM_ERROR; } + HTTP_DBG (1, "decompressed headers size %u", control_data.headers_len); + HTTP_DBG (1, "dynamic table size %u", h2c->decoder_dynamic_table.used); + if (!(control_data.parsed_bitmap & HPACK_PSEUDO_HEADER_METHOD_PARSED)) { HTTP_DBG (1, ":method pseudo-header missing in request"); @@ -759,13 +851,13 @@ http2_req_state_wait_transport_method (http_conn_t *hc, http2_req_t *req, } req->base.control_data_len = control_data.control_data_len; - req->base.headers_offset = control_data.headers - buf; + req->base.headers_offset = control_data.headers - wrk->header_list; req->base.headers_len = control_data.headers_len; if (control_data.content_len_header_index != ~0) { req->base.content_len_header_index = control_data.content_len_header_index; - rv = http_parse_content_length (&req->base, buf); + rv = http_parse_content_length (&req->base, wrk->header_list); if (rv) { http2_stream_error (hc, req, HTTP2_ERROR_PROTOCOL_ERROR, sp); @@ -784,20 +876,20 @@ http2_req_state_wait_transport_method (http_conn_t *hc, http2_req_t *req, req->base.to_recv = req->base.body_len; req->base.target_path_len = control_data.path_len; - req->base.target_path_offset = control_data.path - buf; + req->base.target_path_offset = control_data.path - wrk->header_list; /* drop leading slash */ req->base.target_path_offset++; req->base.target_path_len--; req->base.target_query_offset = 0; req->base.target_query_len = 0; - http_identify_optional_query (&req->base, buf); + http_identify_optional_query (&req->base, wrk->header_list); msg.type = HTTP_MSG_REQUEST; msg.method_type = control_data.method; msg.data.type = HTTP_MSG_DATA_INLINE; msg.data.len = req->base.connection_header_index; msg.data.scheme = control_data.scheme; - msg.data.target_authority_offset = control_data.authority - buf; + msg.data.target_authority_offset = control_data.authority - wrk->header_list; msg.data.target_authority_len = control_data.authority_len; msg.data.target_path_offset = req->base.target_path_offset; msg.data.target_path_len = req->base.target_path_len; @@ -811,8 +903,10 @@ http2_req_state_wait_transport_method (http_conn_t *hc, http2_req_t *req, msg.data.body_len = req->base.body_len; svm_fifo_seg_t segs[2] = { { (u8 *) &msg, sizeof (msg) }, - { buf, req->base.control_data_len } }; - HTTP_DBG (3, "%U", format_http_bytes, buf, req->base.control_data_len); + { wrk->header_list, + req->base.control_data_len } }; + HTTP_DBG (3, "%U", format_http_bytes, wrk->header_list, + req->base.control_data_len); http_io_as_write_segs (&req->base, segs, 2); http_req_state_change (&req->base, new_state); http_app_worker_rx_notify (&req->base); @@ -982,16 +1076,12 @@ static http2_error_t http2_handle_headers_frame (http_conn_t *hc, http2_frame_header_t *fh) { http2_req_t *req; - u8 *rx_buf; + u8 *rx_buf, *headers_start; + u32 headers_len; + uword n_del, n_dec; http2_error_t rv; http2_conn_ctx_t *h2c; - if (!(fh->flags & HTTP2_FRAME_FLAG_END_HEADERS)) - { - /* TODO: fragmented headers */ - return HTTP2_ERROR_INTERNAL_ERROR; - } - if (hc->flags & HTTP_CONN_F_IS_SERVER) { h2c = http2_conn_ctx_get_w_thread (hc); @@ -1018,6 +1108,7 @@ http2_handle_headers_frame (http_conn_t *hc, http2_frame_header_t *fh) return HTTP2_ERROR_NO_ERROR; } req = http2_conn_alloc_req (hc, fh->stream_id); + req->dispatch_headers_cb = http2_sched_dispatch_headers; http_conn_accept_request (hc, &req->base); http_req_state_change (&req->base, HTTP_REQ_STATE_WAIT_TRANSPORT_METHOD); req->stream_state = HTTP2_STREAM_STATE_OPEN; @@ -1031,6 +1122,31 @@ http2_handle_headers_frame (http_conn_t *hc, http2_frame_header_t *fh) } if (fh->flags & HTTP2_FRAME_FLAG_END_STREAM) req->stream_state = HTTP2_STREAM_STATE_HALF_CLOSED; + + if (!(fh->flags & HTTP2_FRAME_FLAG_END_HEADERS)) + { + HTTP_DBG (1, "fragmented headers stream id %u", fh->stream_id); + h2c->flags |= HTTP2_CONN_F_EXPECT_CONTINUATION; + vec_validate (h2c->unparsed_headers, fh->length - 1); + http_io_ts_read (hc, h2c->unparsed_headers, fh->length, 0); + rv = http2_frame_read_headers (&headers_start, &headers_len, + h2c->unparsed_headers, fh->length, + fh->flags); + if (rv != HTTP2_ERROR_NO_ERROR) + return rv; + + /* in case frame has padding */ + if (PREDICT_FALSE (headers_start != h2c->unparsed_headers)) + { + n_dec = fh->length - headers_len; + n_del = headers_start - h2c->unparsed_headers; + n_dec -= n_del; + vec_delete (h2c->unparsed_headers, n_del, 0); + vec_dec_len (h2c->unparsed_headers, n_dec); + } + + return HTTP2_ERROR_NO_ERROR; + } } else { @@ -1051,6 +1167,53 @@ http2_handle_headers_frame (http_conn_t *hc, http2_frame_header_t *fh) return http2_req_run_state_machine (hc, req, 0, 0); } +static http2_error_t +http2_handle_continuation_frame (http_conn_t *hc, http2_frame_header_t *fh) +{ + http2_req_t *req; + http2_conn_ctx_t *h2c; + u8 *p; + http2_error_t rv = HTTP2_ERROR_NO_ERROR; + + if (hc->flags & HTTP_CONN_F_IS_SERVER) + { + h2c = http2_conn_ctx_get_w_thread (hc); + + if (!(h2c->flags & HTTP2_CONN_F_EXPECT_CONTINUATION)) + { + HTTP_DBG (1, "unexpected CONTINUATION frame"); + return HTTP2_ERROR_PROTOCOL_ERROR; + } + + if (fh->stream_id != h2c->last_opened_stream_id) + { + HTTP_DBG (1, "invalid stream id %u", fh->stream_id); + return HTTP2_ERROR_PROTOCOL_ERROR; + } + + vec_add2 (h2c->unparsed_headers, p, fh->length); + http_io_ts_read (hc, p, fh->length, 0); + + if (fh->flags & HTTP2_FRAME_FLAG_END_HEADERS) + { + req = http2_conn_get_req (hc, fh->stream_id); + h2c->flags &= ~HTTP2_CONN_F_EXPECT_CONTINUATION; + req->payload = h2c->unparsed_headers; + req->payload_len = vec_len (h2c->unparsed_headers); + HTTP_DBG (1, "run state machine"); + rv = http2_req_run_state_machine (hc, req, 0, 0); + vec_free (h2c->unparsed_headers); + } + } + else + { + /* TODO: client */ + return HTTP2_ERROR_INTERNAL_ERROR; + } + + return rv; +} + static http2_error_t http2_handle_data_frame (http_conn_t *hc, http2_frame_header_t *fh) { @@ -1325,7 +1488,8 @@ http2_handle_goaway_frame (http_conn_t *hc, http2_frame_header_t *fh) if (rv != HTTP2_ERROR_NO_ERROR) return rv; - HTTP_DBG (1, "received GOAWAY %U", format_http2_error, error_code); + HTTP_DBG (1, "received GOAWAY %U, last stream id %u", format_http2_error, + error_code, last_stream_id); if (error_code == HTTP2_ERROR_NO_ERROR) { @@ -1333,6 +1497,10 @@ http2_handle_goaway_frame (http_conn_t *hc, http2_frame_header_t *fh) } else { + if (fh->length > HTTP2_GOAWAY_MIN_SIZE) + clib_warning ("additional debug data: %U", format_http_bytes, + rx_buf + HTTP2_GOAWAY_MIN_SIZE, + fh->length - HTTP2_GOAWAY_MIN_SIZE); /* connection error */ h2c = http2_conn_ctx_get_w_thread (hc); hash_foreach (stream_id, req_index, h2c->req_by_stream_id, ({ @@ -1669,7 +1837,16 @@ http2_transport_rx_callback (http_conn_t *hc) http_io_ts_drain (hc, HTTP2_FRAME_HEADER_SIZE); to_deq -= fh.length; - HTTP_DBG (1, "frame type 0x%02x", fh.type); + HTTP_DBG (1, "frame type 0x%02x len %u", fh.type, fh.length); + + if ((h2c->flags & HTTP2_CONN_F_EXPECT_CONTINUATION) && + fh.type != HTTP2_FRAME_TYPE_CONTINUATION) + { + HTTP_DBG (1, "expected CONTINUATION frame"); + http2_connection_error (hc, HTTP2_ERROR_PROTOCOL_ERROR, 0); + return; + } + switch (fh.type) { case HTTP2_FRAME_TYPE_HEADERS: @@ -1694,8 +1871,7 @@ http2_transport_rx_callback (http_conn_t *hc) rv = http2_handle_ping_frame (hc, &fh); break; case HTTP2_FRAME_TYPE_CONTINUATION: - /* TODO */ - rv = HTTP2_ERROR_INTERNAL_ERROR; + rv = http2_handle_continuation_frame (hc, &fh); break; case HTTP2_FRAME_TYPE_PUSH_PROMISE: rv = http2_handle_push_promise (hc, &fh); @@ -1867,6 +2043,7 @@ http2_enable_callback (void) { wrk = &h2m->wrk_ctx[i]; wrk->sched_head = clib_llist_make_head (wrk->conn_pool, sched_list); + vec_validate (wrk->header_list, h2m->settings.max_header_list_size - 1); } } @@ -1957,6 +2134,7 @@ http2_init (vlib_main_t *vm) clib_warning ("http/2 enabled"); h2m->settings = http2_default_conn_settings; h2m->settings.max_concurrent_streams = 100; /* by default unlimited */ + h2m->settings.max_header_list_size = 1 << 14; /* by default unlimited */ http_register_engine (&http2_engine, HTTP_VERSION_2); return 0;