diff options
author | 2025-02-18 04:29:10 -0500 | |
---|---|---|
committer | 2025-02-25 17:47:07 +0000 | |
commit | 58b6c4e6bdfba7c2a652e121c1c4e907df685780 (patch) | |
tree | 92636fd3b69277589ed1ebbb5621114cb22cbf1f | |
parent | 3979037173d4054bdff3be236cd33514c809ff85 (diff) |
http: hpack headers decoding
1) parsing of binary format (RFC7541 section 6)
2) simple dynamic table implementation
3) parsing of request header block
Type: feature
Change-Id: If43e175a0643f9731c15efc412a82345d9e33cee
Signed-off-by: Matus Fabian <matfabia@cisco.com>
-rw-r--r-- | src/plugins/http/http.h | 3 | ||||
-rw-r--r-- | src/plugins/http/http2/hpack.c | 618 | ||||
-rw-r--r-- | src/plugins/http/http2/hpack.h | 85 | ||||
-rw-r--r-- | src/plugins/http/http2/http2.h | 58 | ||||
-rw-r--r-- | src/plugins/http/test/http_test.c | 248 |
5 files changed, 989 insertions, 23 deletions
diff --git a/src/plugins/http/http.h b/src/plugins/http/http.h index 178bdd14881..0245c6e696b 100644 --- a/src/plugins/http/http.h +++ b/src/plugins/http/http.h @@ -56,6 +56,7 @@ typedef enum http_req_method_ HTTP_REQ_GET = 0, HTTP_REQ_POST, HTTP_REQ_CONNECT, + HTTP_REQ_UNKNOWN, /* for internal use */ } http_req_method_t; typedef enum http_msg_type_ @@ -278,6 +279,7 @@ typedef enum http_status_code_ _ (PROXY_STATUS, "Proxy-Status") \ _ (RANGE, "Range") \ _ (REFERER, "Referer") \ + _ (REFRESH, "Refresh") \ _ (REPR_DIGEST, "Repr-Digest") \ _ (SET_COOKIE, "Set-Cookie") \ _ (SIGNATURE, "Signature") \ @@ -337,6 +339,7 @@ typedef enum http_url_scheme_ { HTTP_URL_SCHEME_HTTP, HTTP_URL_SCHEME_HTTPS, + HTTP_URL_SCHEME_UNKNOWN, /* for internal use */ } http_url_scheme_t; typedef struct http_msg_data_ diff --git a/src/plugins/http/http2/hpack.c b/src/plugins/http/http2/hpack.c index f279bd9f1b8..917f32e79a0 100644 --- a/src/plugins/http/http2/hpack.c +++ b/src/plugins/http/http2/hpack.c @@ -3,9 +3,89 @@ */ #include <vppinfra/error.h> +#include <vppinfra/ring.h> +#include <http/http.h> #include <http/http2/hpack.h> #include <http/http2/huffman_table.h> +#define HPACK_STATIC_TABLE_SIZE 61 + +typedef struct +{ + char *name; + uword name_len; + char *value; + uword value_len; +} hpack_static_table_entry_t; + +#define name_val_token_lit(name, value) \ + (name), sizeof (name) - 1, (value), sizeof (value) - 1 + +static hpack_static_table_entry_t + hpack_static_table[HPACK_STATIC_TABLE_SIZE] = { + { name_val_token_lit (":authority", "") }, + { name_val_token_lit (":method", "GET") }, + { name_val_token_lit (":method", "POST") }, + { name_val_token_lit (":path", "/") }, + { name_val_token_lit (":path", "/index.html") }, + { name_val_token_lit (":scheme", "http") }, + { name_val_token_lit (":scheme", "https") }, + { name_val_token_lit (":status", "200") }, + { name_val_token_lit (":status", "204") }, + { name_val_token_lit (":status", "206") }, + { name_val_token_lit (":status", "304") }, + { name_val_token_lit (":status", "400") }, + { name_val_token_lit (":status", "404") }, + { name_val_token_lit (":status", "500") }, + { name_val_token_lit ("accept-charset", "") }, + { name_val_token_lit ("accept-encoding", "gzip, deflate") }, + { name_val_token_lit ("accept-language", "") }, + { name_val_token_lit ("accept-ranges", "") }, + { name_val_token_lit ("accept", "") }, + { name_val_token_lit ("access-control-allow-origin", "") }, + { name_val_token_lit ("age", "") }, + { name_val_token_lit ("allow", "") }, + { name_val_token_lit ("authorization", "") }, + { name_val_token_lit ("cache-control", "") }, + { name_val_token_lit ("content-disposition", "") }, + { name_val_token_lit ("content-encoding", "") }, + { name_val_token_lit ("content-language", "") }, + { name_val_token_lit ("content-length", "") }, + { name_val_token_lit ("content-location", "") }, + { name_val_token_lit ("content-range", "") }, + { name_val_token_lit ("content-type", "") }, + { name_val_token_lit ("cookie", "") }, + { name_val_token_lit ("date", "") }, + { name_val_token_lit ("etag", "") }, + { name_val_token_lit ("etag", "") }, + { name_val_token_lit ("expires", "") }, + { name_val_token_lit ("from", "") }, + { name_val_token_lit ("host", "") }, + { name_val_token_lit ("if-match", "") }, + { name_val_token_lit ("if-modified-since", "") }, + { name_val_token_lit ("if-none-match", "") }, + { name_val_token_lit ("if-range", "") }, + { name_val_token_lit ("if-unmodified-since", "") }, + { name_val_token_lit ("last-modified", "") }, + { name_val_token_lit ("link", "") }, + { name_val_token_lit ("location", "") }, + { name_val_token_lit ("max-forwards", "") }, + { name_val_token_lit ("proxy-authenticate", "") }, + { name_val_token_lit ("proxy-authorization", "") }, + { name_val_token_lit ("range", "") }, + { name_val_token_lit ("referer", "") }, + { name_val_token_lit ("refresh", "") }, + { name_val_token_lit ("retry-after", "") }, + { name_val_token_lit ("server", "") }, + { name_val_token_lit ("set-cookie", "") }, + { name_val_token_lit ("strict-transport-security", "") }, + { name_val_token_lit ("transfer-encoding", "") }, + { name_val_token_lit ("user-agent", "") }, + { name_val_token_lit ("vary", "") }, + { name_val_token_lit ("via", "") }, + { name_val_token_lit ("www-authenticate", "") }, + }; + __clib_export uword hpack_decode_int (u8 **src, u8 *end, u8 prefix_len) { @@ -48,7 +128,7 @@ hpack_decode_int (u8 **src, u8 *end, u8 prefix_len) return HPACK_INVALID_INT; } -int +http2_error_t hpack_decode_huffman (u8 **src, u8 *end, u8 **buf, uword *buf_len) { u64 accumulator = 0; @@ -61,7 +141,7 @@ hpack_decode_huffman (u8 **src, u8 *end, u8 **buf, uword *buf_len) { /* out of space? */ if (*buf_len == 0) - return -1; + return HTTP2_ERROR_INTERNAL_ERROR; /* refill */ while (p < end && accumulator_len <= 56) { @@ -117,7 +197,7 @@ hpack_decode_huffman (u8 **src, u8 *end, u8 **buf, uword *buf_len) /* out of space? */ if (*buf_len == 0) - return -1; + return HTTP2_ERROR_INTERNAL_ERROR; /* if bogus EOF check bellow will fail */ code = &huff_code_table_fast[(u8) (accumulator @@ -133,14 +213,14 @@ hpack_decode_huffman (u8 **src, u8 *end, u8 **buf, uword *buf_len) /* we must end with EOF here */ if (((1 << accumulator_len) - 1) != (accumulator & ((1 << accumulator_len) - 1))) - return -1; + return HTTP2_ERROR_COMPRESSION_ERROR; break; } } - return 0; + return HTTP2_ERROR_NO_ERROR; } -__clib_export int +__clib_export http2_error_t hpack_decode_string (u8 **src, u8 *end, u8 **buf, uword *buf_len) { u8 *p, is_huffman; @@ -155,11 +235,11 @@ hpack_decode_string (u8 **src, u8 *end, u8 **buf, uword *buf_len) /* length is integer with 7 bit prefix */ len = hpack_decode_int (&p, end, 7); if (PREDICT_FALSE (len == HPACK_INVALID_INT)) - return -1; + return HTTP2_ERROR_COMPRESSION_ERROR; /* do we have everything? */ if (len > (end - p)) - return -1; + return HTTP2_ERROR_COMPRESSION_ERROR; if (is_huffman) { @@ -170,13 +250,13 @@ hpack_decode_string (u8 **src, u8 *end, u8 **buf, uword *buf_len) { /* enough space? */ if (len > *buf_len) - return -1; + return HTTP2_ERROR_INTERNAL_ERROR; clib_memcpy (*buf, p, len); *buf_len -= len; *buf += len; *src = (p + len); - return 0; + return HTTP2_ERROR_NO_ERROR; } } @@ -295,3 +375,521 @@ hpack_encode_string (u8 *dst, const u8 *value, uword value_len) return dst; } + +__clib_export void +hpack_dynamic_table_init (hpack_dynamic_table_t *table, u32 max_size) +{ + table->max_size = max_size; + table->size = max_size; + table->used = 0; + clib_ring_new (table->entries, + max_size / HPACK_DYNAMIC_TABLE_ENTRY_OVERHEAD); +} + +__clib_export void +hpack_dynamic_table_free (hpack_dynamic_table_t *table) +{ + hpack_dynamic_table_entry_t *e; + + while ((e = clib_ring_deq (table->entries)) != 0) + vec_free (e->buf); + + clib_ring_free (table->entries); +} + +#define hpack_dynamic_table_entry_value_base(e) \ + ((char *) ((e)->buf + (e)->name_len)) +#define hpack_dynamic_table_entry_value_len(e) \ + (vec_len ((e)->buf) - (e)->name_len) + +always_inline hpack_dynamic_table_entry_t * +hpack_dynamic_table_get (hpack_dynamic_table_t *table, uword index) +{ + if (index > clib_ring_n_enq (table->entries)) + return 0; + + hpack_dynamic_table_entry_t *first = clib_ring_get_first (table->entries); + u32 first_index = first - table->entries; + u32 entry_index = + (first_index + (clib_ring_n_enq (table->entries) - 1 - (u32) index)) % + vec_len (table->entries); + return table->entries + entry_index; +} + +__clib_export u8 * +format_hpack_dynamic_table (u8 *s, va_list *args) +{ + hpack_dynamic_table_t *table = va_arg (*args, hpack_dynamic_table_t *); + u32 i; + hpack_dynamic_table_entry_t *e; + + s = format (s, "HPACK dynamic table:\n"); + for (i = 0; i < clib_ring_n_enq (table->entries); i++) + { + e = hpack_dynamic_table_get (table, i); + s = format (s, "\t[%u] %U: %U\n", i, format_http_bytes, e->buf, + e->name_len, format_http_bytes, + hpack_dynamic_table_entry_value_base (e), + hpack_dynamic_table_entry_value_len (e)); + } + return s; +} + +static inline void +hpack_dynamic_table_evict_one (hpack_dynamic_table_t *table) +{ + u32 entry_size; + hpack_dynamic_table_entry_t *e; + + e = clib_ring_deq (table->entries); + ASSERT (e); + HTTP_DBG (2, "%U: %U", format_http_bytes, e->buf, e->name_len, + format_http_bytes, hpack_dynamic_table_entry_value_base (e), + hpack_dynamic_table_entry_value_len (e)); + entry_size = vec_len (e->buf) + HPACK_DYNAMIC_TABLE_ENTRY_OVERHEAD; + table->used -= entry_size; + vec_reset_length (e->buf); +} + +static void +hpack_dynamic_table_add (hpack_dynamic_table_t *table, http_token_t *name, + http_token_t *value) +{ + u32 entry_size; + hpack_dynamic_table_entry_t *e; + + entry_size = name->len + value->len + HPACK_DYNAMIC_TABLE_ENTRY_OVERHEAD; + + /* make space or evict all */ + while (clib_ring_n_enq (table->entries) && + (table->used + entry_size > table->size)) + hpack_dynamic_table_evict_one (table); + + /* attempt to add entry larger than the maximum size is not error */ + if (entry_size > table->size) + return; + + e = clib_ring_enq (table->entries); + ASSERT (e); + vec_validate (e->buf, name->len + value->len - 1); + clib_memcpy (e->buf, name->base, name->len); + clib_memcpy (e->buf + name->len, value->base, value->len); + e->name_len = name->len; + table->used += entry_size; + + HTTP_DBG (2, "%U: %U", format_http_bytes, e->buf, e->name_len, + format_http_bytes, hpack_dynamic_table_entry_value_base (e), + hpack_dynamic_table_entry_value_len (e)); +} + +static http2_error_t +hpack_get_table_entry (uword index, http_token_t *name, http_token_t *value, + u8 value_is_indexed, hpack_dynamic_table_t *dt) +{ + if (index <= HPACK_STATIC_TABLE_SIZE) + { + hpack_static_table_entry_t *e = &hpack_static_table[index - 1]; + name->base = e->name; + name->len = e->name_len; + if (value_is_indexed) + { + if (PREDICT_FALSE (e->value_len == 0)) + { + HTTP_DBG (1, "static table entry [%llu] without value", index); + return HTTP2_ERROR_COMPRESSION_ERROR; + } + value->base = e->value; + value->len = e->value_len; + } + HTTP_DBG (2, "[%llu] %U: %U", index, format_http_bytes, e->name, + e->name_len, format_http_bytes, e->value, e->value_len); + return HTTP2_ERROR_NO_ERROR; + } + else + { + hpack_dynamic_table_entry_t *e = + hpack_dynamic_table_get (dt, index - HPACK_STATIC_TABLE_SIZE - 1); + if (PREDICT_FALSE (!e)) + { + HTTP_DBG (1, "index %llu not in dynamic table", index); + return HTTP2_ERROR_COMPRESSION_ERROR; + } + name->base = (char *) e->buf; + name->len = e->name_len; + value->base = hpack_dynamic_table_entry_value_base (e); + value->len = hpack_dynamic_table_entry_value_len (e); + HTTP_DBG (2, "[%llu] %U: %U", index, format_http_bytes, name->base, + name->len, format_http_bytes, value->base, value->len); + return HTTP2_ERROR_NO_ERROR; + } +} + +__clib_export http2_error_t +hpack_decode_header (u8 **src, u8 *end, u8 **buf, uword *buf_len, + u32 *name_len, u32 *value_len, hpack_dynamic_table_t *dt) +{ + u8 *p; + u8 value_is_indexed = 0, add_new_entry = 0; + uword old_len, new_max, index = 0; + http_token_t name, value; + http2_error_t rv; + + ASSERT (*src < end); + p = *src; + + /* dynamic table size update */ + while ((*p & 0xE0) == 0x20) + { + new_max = hpack_decode_int (&p, end, 5); + if (p == end || new_max > (uword) dt->max_size) + { + HTTP_DBG (1, "invalid dynamic table size update"); + return HTTP2_ERROR_COMPRESSION_ERROR; + } + while (clib_ring_n_enq (dt->entries) && new_max > dt->used) + hpack_dynamic_table_evict_one (dt); + dt->size = (u32) new_max; + } + + if (*p & 0x80) /* indexed header field */ + { + index = hpack_decode_int (&p, end, 7); + /* index value of 0 is not used */ + if (index == 0 || index == HPACK_INVALID_INT) + { + HTTP_DBG (1, "invalid index"); + return HTTP2_ERROR_COMPRESSION_ERROR; + } + value_is_indexed = 1; + } + else if (*p > 0x40) /* incremental indexing - indexed name */ + { + index = hpack_decode_int (&p, end, 6); + /* index value of 0 is not used */ + if (index == 0 || index == HPACK_INVALID_INT) + { + HTTP_DBG (1, "invalid index"); + return HTTP2_ERROR_COMPRESSION_ERROR; + } + add_new_entry = 1; + } + else if (*p == 0x40) /* incremental indexing - new name */ + { + add_new_entry = 1; + p++; + } + else /* without indexing / never indexed */ + { + if ((*p & 0x0F) == 0) /* new name */ + p++; + else /* indexed name */ + { + index = hpack_decode_int (&p, end, 4); + /* index value of 0 is not used */ + if (index == 0 || index == HPACK_INVALID_INT) + { + HTTP_DBG (1, "invalid index"); + return HTTP2_ERROR_COMPRESSION_ERROR; + } + } + } + + if (index) + { + rv = hpack_get_table_entry (index, &name, &value, value_is_indexed, dt); + if (rv != HTTP2_ERROR_NO_ERROR) + { + HTTP_DBG (1, "entry index %llu error", index); + return rv; + } + if (name.len > *buf_len) + { + HTTP_DBG (1, "not enough space"); + return HTTP2_ERROR_INTERNAL_ERROR; + } + clib_memcpy (*buf, name.base, name.len); + *buf_len -= name.len; + *buf += name.len; + *name_len = name.len; + if (value_is_indexed) + { + if (value.len > *buf_len) + { + HTTP_DBG (1, "not enough space"); + return HTTP2_ERROR_INTERNAL_ERROR; + } + clib_memcpy (*buf, value.base, value.len); + *buf_len -= value.len; + *buf += value.len; + *value_len = value.len; + } + } + else + { + old_len = *buf_len; + name.base = (char *) *buf; + rv = hpack_decode_string (&p, end, buf, buf_len); + if (rv != HTTP2_ERROR_NO_ERROR) + { + HTTP_DBG (1, "invalid header name"); + return rv; + } + *name_len = old_len - *buf_len; + name.len = *name_len; + } + + if (!value_is_indexed) + { + old_len = *buf_len; + value.base = (char *) *buf; + rv = hpack_decode_string (&p, end, buf, buf_len); + if (rv != HTTP2_ERROR_NO_ERROR) + { + HTTP_DBG (1, "invalid header value"); + return rv; + } + *value_len = old_len - *buf_len; + value.len = *value_len; + } + + if (add_new_entry) + hpack_dynamic_table_add (dt, &name, &value); + + *src = p; + return HTTP2_ERROR_NO_ERROR; +} + +static inline u8 +hpack_header_name_is_valid (u8 *name, u32 name_len) +{ + u32 i; + static uword tchar[4] = { + /* !#$%'*+-.0123456789 */ + 0x03ff6cba00000000, + /* ^_`abcdefghijklmnopqrstuvwxyz|~ */ + 0x57ffffffc0000000, + 0x0000000000000000, + 0x0000000000000000, + }; + for (i = 0; i < name_len; i++) + { + if (!clib_bitmap_get_no_check (tchar, name[i])) + return 0; + } + return 1; +} + +static inline u8 +hpack_header_value_is_valid (u8 *value, u32 value_len) +{ + u32 i; + /* VCHAR / SP / HTAB / %x80-FF */ + static uword tchar[4] = { + 0xffffffff00000200, + 0x7fffffffffffffff, + 0xffffffffffffffff, + 0xffffffffffffffff, + }; + + /* must not start or end with SP or HTAB */ + if ((value[0] == 0x20 || value[0] == 0x09 || value[value_len - 1] == 0x20 || + value[value_len - 1] == 0x09)) + return 0; + + for (i = 0; i < value_len; i++) + { + if (!clib_bitmap_get_no_check (tchar, value[i])) + return 0; + } + return 1; +} + +static inline http_req_method_t +hpack_parse_method (u8 *value, u32 value_len) +{ + switch (value_len) + { + case 3: + if (!memcmp (value, "GET", 3)) + return HTTP_REQ_GET; + break; + case 4: + if (!memcmp (value, "POST", 4)) + return HTTP_REQ_POST; + break; + case 7: + if (!memcmp (value, "CONNECT", 7)) + return HTTP_REQ_CONNECT; + break; + default: + break; + } + /* HPACK should return only connection errors, this one is stream error */ + return HTTP_REQ_UNKNOWN; +} + +static inline http_url_scheme_t +hpack_parse_scheme (u8 *value, u32 value_len) +{ + switch (value_len) + { + case 4: + if (!memcmp (value, "http", 4)) + return HTTP_URL_SCHEME_HTTP; + break; + case 5: + if (!memcmp (value, "https", 5)) + return HTTP_URL_SCHEME_HTTPS; + break; + default: + break; + } + /* HPACK should return only connection errors, this one is stream error */ + return HTTP_URL_SCHEME_UNKNOWN; +} + +static http2_error_t +hpack_parse_req_pseudo_header (u8 *name, u32 name_len, u8 *value, + u32 value_len, + hpack_request_control_data_t *control_data) +{ + HTTP_DBG (2, "%U: %U", format_http_bytes, name, name_len, format_http_bytes, + value, value_len); + switch (name_len) + { + case 5: + if (!memcmp (name + 1, "path", 4)) + { + if (control_data->parsed_bitmap & HPACK_PSEUDO_HEADER_PATH_PARSED) + return HTTP2_ERROR_PROTOCOL_ERROR; + control_data->parsed_bitmap |= HPACK_PSEUDO_HEADER_PATH_PARSED; + control_data->path = value; + control_data->path_len = value_len; + break; + } + return HTTP2_ERROR_PROTOCOL_ERROR; + case 7: + switch (name[1]) + { + case 'm': + if (!memcmp (name + 2, "ethod", 5)) + { + if (control_data->parsed_bitmap & + HPACK_PSEUDO_HEADER_METHOD_PARSED) + return HTTP2_ERROR_PROTOCOL_ERROR; + control_data->parsed_bitmap |= HPACK_PSEUDO_HEADER_METHOD_PARSED; + control_data->method = hpack_parse_method (value, value_len); + break; + } + return HTTP2_ERROR_PROTOCOL_ERROR; + case 's': + if (!memcmp (name + 2, "cheme", 5)) + { + if (control_data->parsed_bitmap & + HPACK_PSEUDO_HEADER_SCHEME_PARSED) + return HTTP2_ERROR_PROTOCOL_ERROR; + control_data->parsed_bitmap |= HPACK_PSEUDO_HEADER_SCHEME_PARSED; + control_data->scheme = hpack_parse_scheme (value, value_len); + break; + } + return HTTP2_ERROR_PROTOCOL_ERROR; + default: + return HTTP2_ERROR_PROTOCOL_ERROR; + } + break; + case 10: + if (!memcmp (name + 1, "authority", 9)) + { + if (control_data->parsed_bitmap & + HPACK_PSEUDO_HEADER_AUTHORITY_PARSED) + return HTTP2_ERROR_PROTOCOL_ERROR; + control_data->parsed_bitmap |= HPACK_PSEUDO_HEADER_AUTHORITY_PARSED; + control_data->authority = value; + control_data->authority_len = value_len; + break; + } + return HTTP2_ERROR_PROTOCOL_ERROR; + default: + return HTTP2_ERROR_PROTOCOL_ERROR; + } + + return HTTP2_ERROR_NO_ERROR; +} + +__clib_export http2_error_t +hpack_parse_request (u8 *src, u32 src_len, u8 *dst, u32 dst_len, + hpack_request_control_data_t *control_data, + http_field_line_t **headers, + hpack_dynamic_table_t *dynamic_table) +{ + u8 *p, *end, *b, *name, *value; + u8 regular_header_parsed = 0; + u32 name_len, value_len; + uword b_left; + http_field_line_t *header; + http2_error_t rv; + + p = src; + end = src + src_len; + b = dst; + b_left = dst_len; + control_data->parsed_bitmap = 0; + control_data->headers_len = 0; + + while (p != end) + { + name = b; + rv = hpack_decode_header (&p, end, &b, &b_left, &name_len, &value_len, + dynamic_table); + if (rv != HTTP2_ERROR_NO_ERROR) + { + HTTP_DBG (1, "hpack_decode_header: %U", format_http2_error, rv); + return rv; + } + value = name + name_len; + + /* pseudo header */ + if (name[0] == ':') + { + /* all pseudo-headers must be before regular headers */ + if (regular_header_parsed) + { + HTTP_DBG (1, "pseudo-headers after regular header"); + return HTTP2_ERROR_PROTOCOL_ERROR; + } + rv = hpack_parse_req_pseudo_header (name, name_len, value, value_len, + control_data); + if (rv != HTTP2_ERROR_NO_ERROR) + { + HTTP_DBG (1, "hpack_parse_req_pseudo_header: %U", + format_http2_error, rv); + return rv; + } + continue; + } + else + { + if (!hpack_header_name_is_valid (name, name_len)) + return HTTP2_ERROR_PROTOCOL_ERROR; + if (!regular_header_parsed) + { + regular_header_parsed = 1; + control_data->headers = name; + } + } + if (!hpack_header_value_is_valid (value, value_len)) + return HTTP2_ERROR_PROTOCOL_ERROR; + vec_add2 (*headers, header, 1); + HTTP_DBG (2, "%U: %U", format_http_bytes, name, name_len, + format_http_bytes, value, value_len); + header->name_offset = name - control_data->headers; + header->name_len = name_len; + header->value_offset = value - control_data->headers; + header->value_len = value_len; + control_data->headers_len += name_len; + control_data->headers_len += value_len; + } + + HTTP_DBG (2, "%U", format_hpack_dynamic_table, dynamic_table); + return HTTP2_ERROR_NO_ERROR; +}
\ No newline at end of file diff --git a/src/plugins/http/http2/hpack.h b/src/plugins/http/http2/hpack.h index 6bd6a3c25f1..9dda1d6d49e 100644 --- a/src/plugins/http/http2/hpack.h +++ b/src/plugins/http/http2/hpack.h @@ -6,6 +6,7 @@ #define SRC_PLUGINS_HTTP_HPACK_H_ #include <vppinfra/types.h> +#include <http/http2/http2.h> #define HPACK_INVALID_INT CLIB_UWORD_MAX #if uword_bits == 64 @@ -14,6 +15,47 @@ #define HPACK_ENCODED_INT_MAX_LEN 6 #endif +#define HPACK_DEFAULT_HEADER_TABLE_SIZE 4096 +#define HPACK_DYNAMIC_TABLE_ENTRY_OVERHEAD 32 + +typedef struct +{ + u8 *buf; + uword name_len; +} hpack_dynamic_table_entry_t; + +typedef struct +{ + /* SETTINGS_HEADER_TABLE_SIZE */ + u32 max_size; + /* dynamic table size update */ + u32 size; + /* current usage (each entry = 32 + name len + value len) */ + u32 used; + /* ring buffer */ + hpack_dynamic_table_entry_t *entries; +} hpack_dynamic_table_t; + +enum +{ +#define _(bit, name, str) HPACK_PSEUDO_HEADER_##name##_PARSED = (1 << bit), + foreach_http2_pseudo_header +#undef _ +}; + +typedef struct +{ + http_req_method_t method; + http_url_scheme_t scheme; + u8 *authority; + u32 authority_len; + u8 *path; + u32 path_len; + u8 *headers; + u32 headers_len; + u16 parsed_bitmap; +} hpack_request_control_data_t; + /** * Decode unsigned variable-length integer (RFC7541 section 5.1) * @@ -47,9 +89,13 @@ u8 *hpack_encode_int (u8 *dst, uword value, u8 prefix_len); * advanced by number of written bytes * @param buf_len Length the buffer, will be decreased * - * @return @c 0 on success. + * @return @c HTTP2_ERROR_NO_ERROR on success + * + * @note Caller is responsible to check if there is somthing left in source + * buffer first */ -int hpack_decode_huffman (u8 **src, u8 *end, u8 **buf, uword *buf_len); +http2_error_t hpack_decode_huffman (u8 **src, u8 *end, u8 **buf, + uword *buf_len); /** * Encode given string in Huffman codes. @@ -73,4 +119,39 @@ u8 *hpack_encode_huffman (u8 *dst, const u8 *value, uword value_len); */ uword hpack_huffman_encoded_len (const u8 *value, uword value_len); +/** + * Initialize HPACK dynamic table + * + * @param table Dynamic table to initialize + * @param max_size Maximum table size (SETTINGS_HEADER_TABLE_SIZE) + */ +void hpack_dynamic_table_init (hpack_dynamic_table_t *table, u32 max_size); + +/** + * Free HPACK dynamic table + * + * @param table Dynamic table to free + */ +void hpack_dynamic_table_free (hpack_dynamic_table_t *table); + +u8 *format_hpack_dynamic_table (u8 *s, va_list *args); + +/** + * Request parser + * + * @param src Header block to parse + * @param src_len Length of header block + * @param dst Buffer where headers will be decoded + * @param dst_len Length of buffer for decoded headers + * @param control_data Preparsed pseudo-headers + * @param headers List of regular headers + * @param dynamic_table Decoder dynamic table + * + * @return @c HTTP2_ERROR_NO_ERROR on success, connection error otherwise + */ +http2_error_t hpack_parse_request (u8 *src, u32 src_len, u8 *dst, u32 dst_len, + hpack_request_control_data_t *control_data, + http_field_line_t **headers, + hpack_dynamic_table_t *dynamic_table); + #endif /* SRC_PLUGINS_HTTP_HPACK_H_ */ diff --git a/src/plugins/http/http2/http2.h b/src/plugins/http/http2/http2.h new file mode 100644 index 00000000000..82403a46fc3 --- /dev/null +++ b/src/plugins/http/http2/http2.h @@ -0,0 +1,58 @@ +/* SPDX-License-Identifier: Apache-2.0 + * Copyright(c) 2025 Cisco Systems, Inc. + */ + +#ifndef SRC_PLUGINS_HTTP_HTTP2_H_ +#define SRC_PLUGINS_HTTP_HTTP2_H_ + +/* RFC9113 section 7 */ +#define foreach_http2_error \ + _ (NO_ERROR, "NO_ERROR") \ + _ (PROTOCOL_ERROR, "PROTOCOL_ERROR") \ + _ (INTERNAL_ERROR, "INTERNAL_ERROR") \ + _ (FLOW_CONTROL_ERROR, "FLOW_CONTROL_ERROR") \ + _ (SETTINGS_TIMEOUT, "SETTINGS_TIMEOUT") \ + _ (STREAM_CLOSED, "STREAM_CLOSED") \ + _ (FRAME_SIZE_ERROR, "FRAME_SIZE_ERROR") \ + _ (REFUSED_STREAM, "REFUSED_STREAM") \ + _ (CANCEL, "CANCEL") \ + _ (COMPRESSION_ERROR, "COMPRESSION_ERROR") \ + _ (CONNECT_ERROR, "CONNECT_ERROR") \ + _ (ENHANCE_YOUR_CALM, "ENHANCE_YOUR_CALM") \ + _ (INADEQUATE_SECURITY, "INADEQUATE_SECURITY") \ + _ (HTTP_1_1_REQUIRED, "HTTP_1_1_REQUIRED") + +typedef enum http2_error_ +{ +#define _(s, str) HTTP2_ERROR_##s, + foreach_http2_error +#undef _ +} http2_error_t; + +static inline u8 * +format_http2_error (u8 *s, va_list *va) +{ + http2_error_t e = va_arg (*va, http2_error_t); + u8 *t = 0; + + switch (e) + { +#define _(s, str) \ + case HTTP2_ERROR_##s: \ + t = (u8 *) str; \ + break; + foreach_http2_error +#undef _ + default : return format (s, "BUG: unknown"); + } + return format (s, "%s", t); +} + +#define foreach_http2_pseudo_header \ + _ (0, METHOD, "method") \ + _ (1, SCHEME, "scheme") \ + _ (2, AUTHORITY, "authority") \ + _ (3, PATH, "path") \ + _ (4, STATUS, "status") + +#endif /* SRC_PLUGINS_HTTP_HTTP2_H_ */ diff --git a/src/plugins/http/test/http_test.c b/src/plugins/http/test/http_test.c index 04db580cb59..54d5a07855d 100644 --- a/src/plugins/http/test/http_test.c +++ b/src/plugins/http/test/http_test.c @@ -535,6 +535,106 @@ http_test_http_header_table (vlib_main_t *vm) } static int +http_test_parse_request (const char *first_req, uword first_req_len, + const char *second_req, uword second_req_len, + const char *third_req, uword third_req_len, + hpack_dynamic_table_t *dynamic_table) +{ + http2_error_t rv; + u8 *buf = 0; + hpack_request_control_data_t control_data; + http_field_line_t *headers = 0; + u16 parsed_bitmap = 0; + + static http2_error_t (*_hpack_parse_request) ( + u8 * src, u32 src_len, u8 * dst, u32 dst_len, + hpack_request_control_data_t * control_data, http_field_line_t * *headers, + hpack_dynamic_table_t * dynamic_table); + + _hpack_parse_request = + vlib_get_plugin_symbol ("http_plugin.so", "hpack_parse_request"); + + parsed_bitmap = + HPACK_PSEUDO_HEADER_METHOD_PARSED | HPACK_PSEUDO_HEADER_SCHEME_PARSED | + HPACK_PSEUDO_HEADER_PATH_PARSED | HPACK_PSEUDO_HEADER_AUTHORITY_PARSED; + + /* first request */ + vec_validate_init_empty (buf, 254, 0); + memset (&control_data, 0, sizeof (control_data)); + rv = _hpack_parse_request ((u8 *) first_req, (u32) first_req_len, buf, 254, + &control_data, &headers, dynamic_table); + if (rv != HTTP2_ERROR_NO_ERROR || + control_data.parsed_bitmap != parsed_bitmap || + control_data.method != HTTP_REQ_GET || + control_data.scheme != HTTP_URL_SCHEME_HTTP || + control_data.path_len != 1 || control_data.authority_len != 15 || + dynamic_table->used != 57 || vec_len (headers) != 0) + return 1; + if (memcmp (control_data.path, "/", 1)) + return 1; + if (memcmp (control_data.authority, "www.example.com", 15)) + return 1; + vec_free (headers); + vec_free (buf); + + /* second request */ + vec_validate_init_empty (buf, 254, 0); + memset (&control_data, 0, sizeof (control_data)); + rv = _hpack_parse_request ((u8 *) second_req, (u32) second_req_len, buf, 254, + &control_data, &headers, dynamic_table); + if (rv != HTTP2_ERROR_NO_ERROR || + control_data.parsed_bitmap != parsed_bitmap || + control_data.method != HTTP_REQ_GET || + control_data.scheme != HTTP_URL_SCHEME_HTTP || + control_data.path_len != 1 || control_data.authority_len != 15 || + dynamic_table->used != 110 || vec_len (headers) != 1 || + control_data.headers_len != 21) + return 2; + if (memcmp (control_data.path, "/", 1)) + return 2; + if (memcmp (control_data.authority, "www.example.com", 15)) + return 2; + if (headers[0].name_len != 13 || headers[0].value_len != 8) + return 2; + if (memcmp (control_data.headers + headers[0].name_offset, "cache-control", + 13)) + return 2; + if (memcmp (control_data.headers + headers[0].value_offset, "no-cache", 8)) + return 2; + vec_free (headers); + vec_free (buf); + + /* third request */ + vec_validate_init_empty (buf, 254, 0); + memset (&control_data, 0, sizeof (control_data)); + rv = _hpack_parse_request ((u8 *) third_req, (u32) third_req_len, buf, 254, + &control_data, &headers, dynamic_table); + if (rv != HTTP2_ERROR_NO_ERROR || + control_data.parsed_bitmap != parsed_bitmap || + control_data.method != HTTP_REQ_GET || + control_data.scheme != HTTP_URL_SCHEME_HTTPS || + control_data.path_len != 11 || control_data.authority_len != 15 || + dynamic_table->used != 164 || vec_len (headers) != 1 || + control_data.headers_len != 22) + return 3; + if (memcmp (control_data.path, "/index.html", 11)) + return 3; + if (memcmp (control_data.authority, "www.example.com", 15)) + return 3; + if (headers[0].name_len != 10 || headers[0].value_len != 12) + return 3; + if (memcmp (control_data.headers + headers[0].name_offset, "custom-key", 10)) + return 3; + if (memcmp (control_data.headers + headers[0].value_offset, "custom-value", + 12)) + return 3; + vec_free (headers); + vec_free (buf); + + return 0; +} + +static int http_test_hpack (vlib_main_t *vm) { vlib_cli_output (vm, "hpack_decode_int"); @@ -617,14 +717,14 @@ http_test_hpack (vlib_main_t *vm) vlib_cli_output (vm, "hpack_decode_string"); - static int (*_hpack_decode_string) (u8 * *src, u8 * end, u8 * *buf, - uword * buf_len); + static http2_error_t (*_hpack_decode_string) (u8 * *src, u8 * end, u8 * *buf, + uword * buf_len); _hpack_decode_string = vlib_get_plugin_symbol ("http_plugin.so", "hpack_decode_string"); u8 *bp; uword blen, len; - int rv; + http2_error_t rv; #define TEST(i, e) \ vec_validate (input, sizeof (i) - 2); \ @@ -636,7 +736,8 @@ http_test_hpack (vlib_main_t *vm) rv = _hpack_decode_string (&pos, vec_end (input), &bp, &blen); \ len = vec_len (buf) - blen; \ HTTP_TEST ((len == strlen (e) && !memcmp (buf, e, len) && \ - pos == vec_end (input) && bp == buf + len && rv == 0), \ + pos == vec_end (input) && bp == buf + len && \ + rv == HTTP2_ERROR_NO_ERROR), \ "%U is decoded as %U", format_hex_bytes, input, vec_len (input), \ format_http_bytes, buf, len); \ vec_free (input); \ @@ -667,7 +768,7 @@ http_test_hpack (vlib_main_t *vm) "\x61\xF9\x61\x61\x61\x61\x61\x30\x30\x61\x61\x61\x61\x61\x61\x61") #undef TEST -#define N_TEST(i) \ +#define N_TEST(i, e) \ vec_validate (input, sizeof (i) - 2); \ memcpy (input, i, sizeof (i) - 1); \ pos = input; \ @@ -675,21 +776,23 @@ http_test_hpack (vlib_main_t *vm) bp = buf; \ blen = vec_len (buf); \ rv = _hpack_decode_string (&pos, vec_end (input), &bp, &blen); \ - HTTP_TEST ((rv != 0), "%U should be invalid", format_hex_bytes, input, \ - vec_len (input)); \ + HTTP_TEST ((rv == e), "%U should be invalid (%U)", format_hex_bytes, input, \ + vec_len (input), format_http2_error, rv); \ vec_free (input); \ vec_free (buf); /* incomplete */ - N_TEST ("\x07priv"); + N_TEST ("\x87", HTTP2_ERROR_COMPRESSION_ERROR); + N_TEST ("\x07priv", HTTP2_ERROR_COMPRESSION_ERROR); /* invalid length */ - N_TEST ("\x7Fprivate"); + N_TEST ("\x7Fprivate", HTTP2_ERROR_COMPRESSION_ERROR); /* invalid EOF */ - N_TEST ("\x81\x8C"); + N_TEST ("\x81\x8C", HTTP2_ERROR_COMPRESSION_ERROR); /* not enough space for decoding */ N_TEST ( "\x96\xD0\x7A\xBE\x94\x10\x54\xD4\x44\xA8\x20\x05\x95\x04\x0B\x81\x66" - "\xE0\x82\xA6\x2D\x1B\xFF"); + "\xE0\x82\xA6\x2D\x1B\xFF", + HTTP2_ERROR_INTERNAL_ERROR); #undef N_TEST vlib_cli_output (vm, "hpack_encode_string"); @@ -728,6 +831,129 @@ http_test_hpack (vlib_main_t *vm) TEST ("[XZ]", "\x4[XZ]"); #undef TEST + vlib_cli_output (vm, "hpack_decode_header"); + + static http2_error_t (*_hpack_decode_header) ( + u8 * *src, u8 * end, u8 * *buf, uword * buf_len, u32 * name_len, + u32 * value_len, hpack_dynamic_table_t * dt); + + _hpack_decode_header = + vlib_get_plugin_symbol ("http_plugin.so", "hpack_decode_header"); + + static void (*_hpack_dynamic_table_init) (hpack_dynamic_table_t * table, + u32 max_size); + + _hpack_dynamic_table_init = + vlib_get_plugin_symbol ("http_plugin.so", "hpack_dynamic_table_init"); + + static void (*_hpack_dynamic_table_free) (hpack_dynamic_table_t * table); + + _hpack_dynamic_table_free = + vlib_get_plugin_symbol ("http_plugin.so", "hpack_dynamic_table_free"); + + u32 name_len, value_len; + hpack_dynamic_table_t table; + + _hpack_dynamic_table_init (&table, 128); + +#define TEST(i, e_name, e_value, dt_size) \ + vec_validate (input, sizeof (i) - 2); \ + memcpy (input, i, sizeof (i) - 1); \ + pos = input; \ + vec_validate_init_empty (buf, 63, 0); \ + bp = buf; \ + blen = vec_len (buf); \ + rv = _hpack_decode_header (&pos, vec_end (input), &bp, &blen, &name_len, \ + &value_len, &table); \ + len = vec_len (buf) - blen; \ + HTTP_TEST ((rv == HTTP2_ERROR_NO_ERROR && table.used == dt_size && \ + name_len == strlen (e_name) && value_len == strlen (e_value) && \ + !memcmp (buf, e_name, name_len) && \ + !memcmp (buf + name_len, e_value, value_len) && \ + vec_len (buf) == (blen + name_len + value_len) && \ + pos == vec_end (input) && bp == buf + name_len + value_len), \ + "%U is decoded as '%U: %U'", format_hex_bytes, input, \ + vec_len (input), format_http_bytes, buf, name_len, \ + format_http_bytes, buf + name_len, value_len); \ + vec_free (input); \ + vec_free (buf); + + /* C.2.1. Literal Header Field with Indexing */ + TEST ("\x40\x0A\x63\x75\x73\x74\x6F\x6D\x2D\x6B\x65\x79\x0D\x63\x75\x73\x74" + "\x6F\x6D\x2D\x68\x65\x61\x64\x65\x72", + "custom-key", "custom-header", 55); + /* C.2.2. Literal Header Field without Indexing */ + TEST ("\x04\x0C\x2F\x73\x61\x6D\x70\x6C\x65\x2F\x70\x61\x74\x68", ":path", + "/sample/path", 55); + /* C.2.3. Literal Header Field Never Indexed */ + TEST ("\x10\x08\x70\x61\x73\x73\x77\x6F\x72\x64\x06\x73\x65\x63\x72\x65\x74", + "password", "secret", 55); + /* C.2.4. Indexed Header Field */ + TEST ("\x82", ":method", "GET", 55); + TEST ("\xBE", "custom-key", "custom-header", 55); + /* Literal Header Field with Indexing - enough space in dynamic table */ + TEST ("\x41\x0F\x77\x77\x77\x2E\x65\x78\x61\x6D\x70\x6C\x65\x2E\x63\x6F\x6D", + ":authority", "www.example.com", 112); + /* verification */ + TEST ("\xBE", ":authority", "www.example.com", 112); + TEST ("\xBF", "custom-key", "custom-header", 112); + /* Literal Header Field with Indexing - eviction */ + TEST ("\x58\x08\x6E\x6F\x2D\x63\x61\x63\x68\x65", "cache-control", + "no-cache", 110); + /* verification */ + TEST ("\xBE", "cache-control", "no-cache", 110); + TEST ("\xBF", ":authority", "www.example.com", 110); + /* Literal Header Field with Indexing - eviction */ + TEST ("\x40\x0A\x63\x75\x73\x74\x6F\x6D\x2D\x6B\x65\x79\x0D\x63\x75\x73\x74" + "\x6F\x6D\x2D\x68\x65\x61\x64\x65\x72", + "custom-key", "custom-header", 108); + /* verification */ + TEST ("\xBE", "custom-key", "custom-header", 108); + TEST ("\xBF", "cache-control", "no-cache", 108); + /* Literal Header Field with Indexing - eviction */ + TEST ("\x41\x0F\x77\x77\x77\x2E\x65\x78\x61\x6D\x70\x6C\x65\x2E\x63\x6F\x6D", + ":authority", "www.example.com", 112); + /* verification */ + TEST ("\xBE", ":authority", "www.example.com", 112); + TEST ("\xBF", "custom-key", "custom-header", 112); + /* Literal Header Field with Indexing - eviction with reference */ + TEST ("\x7F\x00\x0C\x63\x75\x73\x74\x6F\x6D\x2D\x76\x61\x6C\x75\x65", + "custom-key", "custom-value", 111); + /* verification */ + TEST ("\xBE", "custom-key", "custom-value", 111); + TEST ("\xBF", ":authority", "www.example.com", 111); +#undef TEST + + _hpack_dynamic_table_free (&table); + + vlib_cli_output (vm, "hpack_parse_request"); + + int result; + /* C.3. Request Examples without Huffman Coding */ + _hpack_dynamic_table_init (&table, HPACK_DEFAULT_HEADER_TABLE_SIZE); + result = http_test_parse_request ( + http_token_lit ("\x82\x86\x84\x41\x0F\x77\x77\x77\x2E\x65\x78\x61" + "\x6D\x70\x6C\x65\x2E\x63\x6F\x6D"), + http_token_lit ( + "\x82\x86\x84\xBE\x58\x08\x6E\x6F\x2D\x63\x61\x63\x68\x65"), + http_token_lit ( + "\x82\x87\x85\xBF\x40\x0A\x63\x75\x73\x74\x6F\x6D\x2D\x6B" + "\x65\x79\x0C\x63\x75\x73\x74\x6F\x6D\x2D\x76\x61\x6C\x75\x65"), + &table); + _hpack_dynamic_table_free (&table); + HTTP_TEST ((result == 0), "request without Huffman Coding (result=%d)", + result); + /* C.4. Request Examples with Huffman Coding */ + _hpack_dynamic_table_init (&table, HPACK_DEFAULT_HEADER_TABLE_SIZE); + result = http_test_parse_request ( + http_token_lit ( + "\x82\x86\x84\x41\x8C\xF1\xE3\xC2\xE5\xF2\x3A\x6B\xA0\xAB\x90\xF4\xFF"), + http_token_lit ("\x82\x86\x84\xBE\x58\x86\xA8\xEB\x10\x64\x9C\xBF"), + http_token_lit ("\x82\x87\x85\xBF\x40\x88\x25\xA8\x49\xE9\x5B\xA9\x7D\x7F" + "\x89\x25\xA8\x49\xE9\x5B\xB8\xE8\xB4\xBF"), + &table); + _hpack_dynamic_table_free (&table); + HTTP_TEST ((result == 0), "request with Huffman Coding (result=%d)", result); return 0; } |