http: HTTP Datagrams and the Capsule Protocol
Type: improvement Change-Id: I45de69172eec54578ceebe87f655701830af1a3d Signed-off-by: Matus Fabian <matfabia@cisco.com>
This commit is contained in:

committed by
Florin Coras

parent
9765e27635
commit
2ec8a9abc2
@ -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_ */
|
||||
|
||||
/*
|
||||
|
@ -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;
|
||||
|
Reference in New Issue
Block a user