From 2ec8a9abc2b853bb7b0ce2649403a25fb29881ae Mon Sep 17 00:00:00 2001 From: Matus Fabian Date: Tue, 12 Nov 2024 15:15:26 +0100 Subject: http: HTTP Datagrams and the Capsule Protocol Type: improvement Change-Id: I45de69172eec54578ceebe87f655701830af1a3d Signed-off-by: Matus Fabian --- src/plugins/http/http.h | 200 ++++++++++++++++++++++++++++++++++++++ src/plugins/http/test/http_test.c | 65 +++++++++++++ 2 files changed, 265 insertions(+) (limited to 'src/plugins') diff --git a/src/plugins/http/http.h b/src/plugins/http/http.h index b293c125465..19488fb7ff4 100644 --- a/src/plugins/http/http.h +++ b/src/plugins/http/http.h @@ -1219,6 +1219,206 @@ http_parse_masque_host_port (u8 *path, u32 path_len, http_uri_t *parsed) return 0; } +#define HTTP_INVALID_VARINT ((u64) ~0) +#define HTTP_UDP_PROXY_DATAGRAM_CAPSULE_OVERHEAD 5 +#define HTTP_UDP_PAYLOAD_MAX_LEN 65527 + +#define foreach_http_capsule_type _ (0, DATAGRAM) + +typedef enum http_capsule_type_ +{ +#define _(n, s) HTTP_CAPSULE_TYPE_##s = n, + foreach_http_capsule_type +#undef _ +} __clib_packed http_capsule_type_t; + +/* variable-length integer (RFC9000 section 16) */ +always_inline u64 +_http_decode_varint (u8 **pos, u8 *end) +{ + u8 first_byte, bytes_left, *p; + u64 value; + + p = *pos; + + ASSERT (p < end); + + first_byte = *p; + p++; + + if (first_byte <= 0x3F) + { + *pos = p; + return first_byte; + } + + /* remove length bits, encoded in the first two bits of the first byte */ + value = first_byte & 0x3F; + bytes_left = (1 << (first_byte >> 6)) - 1; + + if (PREDICT_FALSE ((end - p) < bytes_left)) + return HTTP_INVALID_VARINT; + + do + { + value = (value << 8) | *p; + p++; + } + while (--bytes_left); + + *pos = p; + return value; +} + +always_inline u8 * +_http_encode_varint (u8 *dst, u64 value) +{ + ASSERT (value <= 0x3FFFFFFFFFFFFFFF); + if (value <= 0x3f) + { + *dst++ = (u8) value; + return dst; + } + else if (value <= 0x3FFF) + { + *dst++ = (0b01 << 6) | (u8) (value >> 8); + *dst++ = (u8) value; + return dst; + } + else if (value <= 0x3FFFFFFF) + { + *dst++ = (0b10 << 6) | (u8) (value >> 24); + *dst++ = (u8) (value >> 16); + *dst++ = (u8) (value >> 8); + *dst++ = (u8) value; + return dst; + } + else + { + *dst++ = (0b11 << 6) | (u8) (value >> 56); + *dst++ = (u8) (value >> 48); + *dst++ = (u8) (value >> 40); + *dst++ = (u8) (value >> 32); + *dst++ = (u8) (value >> 24); + *dst++ = (u8) (value >> 16); + *dst++ = (u8) (value >> 8); + *dst++ = (u8) value; + return dst; + } +} + +always_inline int +_http_parse_capsule (u8 *data, u64 len, u64 *type, u8 *value_offset, + u64 *value_len) +{ + u64 capsule_type, capsule_value_len; + u8 *p = data; + u8 *end = data + len; + + capsule_type = _http_decode_varint (&p, end); + if (capsule_type == HTTP_INVALID_VARINT) + { + clib_warning ("failed to parse capsule type"); + return -1; + } + + capsule_value_len = _http_decode_varint (&p, end); + if (capsule_value_len == HTTP_INVALID_VARINT) + { + clib_warning ("failed to parse capsule length"); + return -1; + } + + *type = capsule_type; + *value_offset = p - data; + *value_len = capsule_value_len; + return 0; +} + +/** + * Decapsulate UDP payload from datagram capsule. + * + * @param data Input buffer. + * @param len Length of given buffer. + * @param payload_offset Offset of the UDP proxying payload (ignore if capsule + * should be skipped). + * @param payload_len Length of the UDP proxying payload (or number of bytes + * to skip). + * + * @return @c -1 if capsule datagram is invalid (session need to be aborted) + * @return @c 0 if capsule contains UDP payload + * @return @c 1 if capsule should be skipped + */ +always_inline int +http_decap_udp_payload_datagram (u8 *data, u64 len, u8 *payload_offset, + u64 *payload_len) +{ + int rv; + u8 *p = data; + u8 *end = data + len; + u64 capsule_type, value_len, context_id; + u8 value_offset; + + rv = _http_parse_capsule (p, len, &capsule_type, &value_offset, &value_len); + if (rv) + return rv; + + /* skip unknown capsule type or empty capsule */ + if ((capsule_type != HTTP_CAPSULE_TYPE_DATAGRAM) || (value_len == 0)) + { + *payload_len = value_len + value_offset; + return 1; + } + + p += value_offset; + + /* context ID field should be zero (RFC9298 section 4) */ + context_id = _http_decode_varint (&p, end); + if (context_id != 0) + { + *payload_len = value_len + value_offset; + return 1; + } + + *payload_offset = p - data; + *payload_len = value_len - 1; + + /* payload longer than 65527 is considered as error (RFC9298 section 5) */ + if (*payload_len > HTTP_UDP_PAYLOAD_MAX_LEN) + { + clib_warning ("UDP payload length too long"); + return -1; + } + + return 0; +} + +/** + * Encapsulate UDP payload to datagram capsule. + * + * @param buf Capsule buffer under construction. + * @param payload_len Length of the UDP proxying payload. + * + * @return Pointer to the UDP payload in capsule buffer. + * + * @note Capsule buffer need extra @c HTTP_UDP_PROXY_DATAGRAM_CAPSULE_OVERHEAD + * bytes to be allocated. + */ +always_inline u8 * +http_encap_udp_payload_datagram (u8 *buf, u64 payload_len) +{ + /* capsule type */ + *buf++ = HTTP_CAPSULE_TYPE_DATAGRAM; + + /* capsule length */ + buf = _http_encode_varint (buf, payload_len + 1); + + /* context ID */ + *buf++ = 0; + + return buf; +} + #endif /* SRC_PLUGINS_HTTP_HTTP_H_ */ /* diff --git a/src/plugins/http/test/http_test.c b/src/plugins/http/test/http_test.c index d4ac8f46f29..a96d5f9d9f7 100644 --- a/src/plugins/http/test/http_test.c +++ b/src/plugins/http/test/http_test.c @@ -315,6 +315,67 @@ http_test_parse_masque_host_port (vlib_main_t *vm) return 0; } +static int +http_test_udp_payload_datagram (vlib_main_t *vm) +{ + int rv; + u8 payload_offset; + u64 payload_len; + + /* Type = 0x00, Len = 15293, Context ID = 0x00 */ + u8 valid_input[] = { 0x00, 0x7B, 0xBD, 0x00, 0x12, 0x34, 0x56 }; + rv = http_decap_udp_payload_datagram (valid_input, sizeof (valid_input), + &payload_offset, &payload_len); + HTTP_TEST ((rv == 0), "'%U' should be valid", format_hex_bytes, valid_input, + sizeof (valid_input)); + HTTP_TEST ((payload_len == 15292), "payload_len=%llu should be 15292", + payload_len); + HTTP_TEST ((payload_offset == 4), "payload_offset=%u should be 4", + payload_offset); + + u8 invalid_input[] = { 0x00, 0x7B }; + rv = http_decap_udp_payload_datagram (invalid_input, sizeof (invalid_input), + &payload_offset, &payload_len); + HTTP_TEST ((rv == -1), "'%U' should be invalid", format_hex_bytes, + invalid_input, sizeof (invalid_input)); + + /* Type = 0x00, Len = 494878333, Context ID = 0x00 */ + u8 long_payload_input[] = { 0x00, 0x9D, 0x7F, 0x3E, 0x7D, 0x00, 0x12 }; + rv = http_decap_udp_payload_datagram (long_payload_input, + sizeof (long_payload_input), + &payload_offset, &payload_len); + HTTP_TEST ( + (rv == -1), "'%U' should be invalid (payload exceeded maximum value)", + format_hex_bytes, long_payload_input, sizeof (long_payload_input)); + + /* Type = 0x01, Len = 37, Context ID = 0x00 */ + u8 unknown_type_input[] = { 0x01, 0x25, 0x00, 0x12, 0x34, 0x56, 0x78 }; + rv = http_decap_udp_payload_datagram (unknown_type_input, + sizeof (unknown_type_input), + &payload_offset, &payload_len); + HTTP_TEST ((rv == 1), "'%U' should be skipped (unknown capsule type)", + format_hex_bytes, unknown_type_input, + sizeof (unknown_type_input)); + HTTP_TEST ((payload_len == 39), "payload_len=%llu should be 39", + payload_len); + + u8 *buffer = 0, *ret; + vec_validate (buffer, HTTP_UDP_PROXY_DATAGRAM_CAPSULE_OVERHEAD + 2); + ret = http_encap_udp_payload_datagram (buffer, 15292); + payload_offset = ret - buffer; + HTTP_TEST ((payload_offset == 4), "payload_offset=%u should be 4", + payload_offset); + HTTP_TEST ((buffer[0] == HTTP_CAPSULE_TYPE_DATAGRAM), + "capsule_type=%u should be %u", buffer[0], + HTTP_CAPSULE_TYPE_DATAGRAM); + HTTP_TEST ((buffer[1] == 0x7B && buffer[2] == 0xBD), + "capsule_len=0x%x%x should be 0x7bbd", buffer[1], buffer[2]); + HTTP_TEST ((buffer[3] == 0), "context_id=%u should be 0", buffer[3]); + vec_free (buffer); + + return 0; +} + static clib_error_t * test_http_command_fn (vlib_main_t *vm, unformat_input_t *input, vlib_cli_command_t *cmd) @@ -328,6 +389,8 @@ test_http_command_fn (vlib_main_t *vm, unformat_input_t *input, res = http_test_absolute_form (vm); else if (unformat (input, "parse-masque-host-port")) res = http_test_parse_masque_host_port (vm); + else if (unformat (input, "udp-payload-datagram")) + res = http_test_udp_payload_datagram (vm); else if (unformat (input, "all")) { if ((res = http_test_authority_form (vm))) @@ -336,6 +399,8 @@ test_http_command_fn (vlib_main_t *vm, unformat_input_t *input, goto done; if ((res = http_test_parse_masque_host_port (vm))) goto done; + if ((res = http_test_udp_payload_datagram (vm))) + goto done; } else break; -- cgit 1.2.3-korg