From 0af11f537f52bbb7af274c8cfd2a1c5c8fcfb0b7 Mon Sep 17 00:00:00 2001 From: Matus Fabian Date: Wed, 30 Oct 2024 18:04:40 +0100 Subject: http: udp proxy uri template parsing Parse a URI template that has variables "target_host" and "target_port", where varaibles are at the end of the path: "/{target_host}/{target_port}/". Type: improvement Change-Id: I440b7f4951bffa1fd9971740b9890b221193943b Signed-off-by: Matus Fabian --- src/plugins/hs_apps/http_cli.c | 2 +- src/plugins/http/http.h | 108 ++++++++++++++++++++++++++++++-------- src/plugins/http/http_plugin.rst | 5 +- src/plugins/http/test/http_test.c | 76 +++++++++++++++++++++++++-- 4 files changed, 163 insertions(+), 28 deletions(-) diff --git a/src/plugins/hs_apps/http_cli.c b/src/plugins/hs_apps/http_cli.c index 1000a2ddd18..dfa90f9eced 100644 --- a/src/plugins/hs_apps/http_cli.c +++ b/src/plugins/hs_apps/http_cli.c @@ -406,7 +406,7 @@ hcs_ts_rx_callback (session_t *ts) } if (is_encoded) { - u8 *decoded = http_percent_decode (args.buf); + u8 *decoded = http_percent_decode (args.buf, vec_len (args.buf)); vec_free (args.buf); args.buf = decoded; } diff --git a/src/plugins/http/http.h b/src/plugins/http/http.h index 13118bfde06..04c53d15ecb 100644 --- a/src/plugins/http/http.h +++ b/src/plugins/http/http.h @@ -522,18 +522,19 @@ http_validate_query_syntax (u8 *query, int *is_encoded) * Decode percent-encoded data. * * @param src Data to decode. + * @param len Length of data to decode. * * @return New vector with decoded data. * * The caller is always responsible to free the returned vector. */ always_inline u8 * -http_percent_decode (u8 *src) +http_percent_decode (u8 *src, u32 len) { - int i; + u32 i; u8 *decoded_uri = 0; - for (i = 0; i < vec_len (src); i++) + for (i = 0; i < len; i++) { if (src[i] == '%') { @@ -995,6 +996,31 @@ typedef struct u8 host_is_ip6; } http_url_t; +always_inline int +_parse_port (u8 **pos, u8 *end, u16 *port) +{ + u32 value = 0; + u8 *p = *pos; + + if (!isdigit (*p)) + return -1; + value = *p - '0'; + p++; + + while (p != end) + { + if (!isdigit (*p)) + break; + value = value * 10 + *p - '0'; + if (value > CLIB_U16_MAX) + return -1; + p++; + } + *pos = p; + *port = clib_host_to_net_u16 ((u16) value); + return 0; +} + /** * An "absolute-form" URL parsing. * @@ -1102,27 +1128,12 @@ http_parse_absolute_form (u8 *url, http_url_t *parsed) /* parse port, if any */ if (token_start != end && *token_start == ':') { - u32 port = 0; token_end = ++token_start; - while (token_end != end && *token_end != '/') + if (_parse_port (&token_end, end, &parsed->port)) { - if (isdigit (*token_end)) - { - port = port * 10 + *token_end - '0'; - if (port > 65535) - { - clib_warning ("invalid port number"); - return -1; - } - } - else - { - clib_warning ("expected digit '%u'", *token_end); - return -1; - } - token_end++; + clib_warning ("invalid port"); + return -1; } - parsed->port = clib_host_to_net_u16 ((u16) port); token_start = token_end; } @@ -1140,6 +1151,61 @@ http_parse_absolute_form (u8 *url, http_url_t *parsed) return 0; } +/** + * Parse target host and port of UDP tunnel over HTTP. + * + * @param path Path in format "{target_host}/{target_port}/". + * @param path_len Length of given path. + * @param parsed Parsed target in case of success.. + * + * @return @c 0 on success. + * + * @note Only IPv4 literals and IPv6 literals supported. + */ +always_inline int +http_parse_masque_host_port (u8 *path, u32 path_len, http_uri_t *parsed) +{ + u8 *p, *end, *decoded_host; + u32 host_len; + unformat_input_t input; + + p = path; + end = path + path_len; + clib_memset (parsed, 0, sizeof (*parsed)); + + while (p != end && *p != '/') + p++; + + host_len = p - path; + if (!host_len || (host_len == path_len) || (host_len + 1 == path_len)) + return -1; + decoded_host = http_percent_decode (path, host_len); + unformat_init_vector (&input, decoded_host); + if (unformat (&input, "%U", unformat_ip4_address, &parsed->ip.ip4)) + parsed->is_ip4 = 1; + else if (unformat (&input, "%U", unformat_ip6_address, &parsed->ip.ip6)) + parsed->is_ip4 = 0; + else + { + unformat_free (&input); + clib_warning ("unsupported target_host format"); + return -1; + } + unformat_free (&input); + + p++; + if (_parse_port (&p, end, &parsed->port)) + { + clib_warning ("invalid port"); + return -1; + } + + if (p == end || *p != '/') + return -1; + + return 0; +} + #endif /* SRC_PLUGINS_HTTP_HTTP_H_ */ /* diff --git a/src/plugins/http/http_plugin.rst b/src/plugins/http/http_plugin.rst index 2771d491636..f86c796bd83 100644 --- a/src/plugins/http/http_plugin.rst +++ b/src/plugins/http/http_plugin.rst @@ -16,7 +16,8 @@ Usage The plugin exposes following inline functions: ``http_validate_abs_path_syntax``, ``http_validate_query_syntax``, ``http_percent_decode``, ``http_path_remove_dot_segments``, ``http_parse_headers``, ``http_get_header``, -``http_free_header_table``, ``http_add_header``, ``http_serialize_headers``. +``http_free_header_table``, ``http_add_header``, ``http_serialize_headers``, ``http_parse_authority_form_target``, +``http_serialize_authority_form_target``, ``http_parse_absolute_form``, ``http_parse_masque_host_port``. It relies on the hoststack constructs and uses ``http_msg_data_t`` data structure for passing metadata to/from applications. @@ -82,7 +83,7 @@ Example bellow validates "absolute-path" rule, as described in RFC9110 section 4 } if (is_encoded) { - u8 *decoded = http_percent_decode (target_path); + u8 *decoded = http_percent_decode (target_path, vec_len (target_path)); vec_free (target_path); target_path = decoded; } diff --git a/src/plugins/http/test/http_test.c b/src/plugins/http/test/http_test.c index c21cf85a1bb..d4ac8f46f29 100644 --- a/src/plugins/http/test/http_test.c +++ b/src/plugins/http/test/http_test.c @@ -84,10 +84,6 @@ http_test_absolute_form (vlib_main_t *vm) int rv; url = format (0, "https://example.org/.well-known/masque/udp/1.2.3.4/123/"); - clib_warning ( - "strlen %u vec_len %u", - strlen ("https://example.org/.well-known/masque/udp/1.2.3.4/123/"), - vec_len (url)); rv = http_parse_absolute_form (url, &parsed_url); HTTP_TEST ((rv == 0), "'%v' should be valid", url); HTTP_TEST ((parsed_url.scheme == HTTP_URL_SCHEME_HTTPS), @@ -251,6 +247,74 @@ http_test_absolute_form (vlib_main_t *vm) return 0; } +static int +http_test_parse_masque_host_port (vlib_main_t *vm) +{ + u8 *path = 0; + http_uri_t target; + int rv; + + path = format (0, "10.10.2.45/443/"); + rv = http_parse_masque_host_port (path, vec_len (path), &target); + HTTP_TEST ((rv == 0), "'%v' should be valid", path); + HTTP_TEST ((target.is_ip4 == 1), "is_ip4=%d should be 1", target.is_ip4); + HTTP_TEST ((clib_net_to_host_u16 (target.port) == 443), + "port=%u should be 443", clib_net_to_host_u16 (target.port)); + HTTP_TEST ((target.ip.ip4.data[0] == 10 && target.ip.ip4.data[1] == 10 && + target.ip.ip4.data[2] == 2 && target.ip.ip4.data[3] == 45), + "target.ip=%U should be 10.10.2.45", format_ip4_address, + &target.ip.ip4); + vec_free (path); + + path = format (0, "dead%%3Abeef%%3A%%3A1234/80/"); + rv = http_parse_masque_host_port (path, vec_len (path), &target); + HTTP_TEST ((rv == 0), "'%v' should be valid", path); + HTTP_TEST ((target.is_ip4 == 0), "is_ip4=%d should be 0", target.is_ip4); + HTTP_TEST ((clib_net_to_host_u16 (target.port) == 80), + "port=%u should be 80", clib_net_to_host_u16 (target.port)); + HTTP_TEST ((clib_net_to_host_u16 (target.ip.ip6.as_u16[0]) == 0xdead && + clib_net_to_host_u16 (target.ip.ip6.as_u16[1]) == 0xbeef && + target.ip.ip6.as_u16[2] == 0 && target.ip.ip6.as_u16[3] == 0 && + target.ip.ip6.as_u16[4] == 0 && target.ip.ip6.as_u16[5] == 0 && + target.ip.ip6.as_u16[6] == 0 && + clib_net_to_host_u16 (target.ip.ip6.as_u16[7]) == 0x1234), + "target.ip=%U should be dead:beef::1234", format_ip6_address, + &target.ip.ip6); + vec_free (path); + + path = format (0, "example.com/443/"); + rv = http_parse_masque_host_port (path, vec_len (path), &target); + HTTP_TEST ((rv != 0), "'%v' reg-name not supported", path); + vec_free (path); + + path = format (0, "10.10.2.45/443443/"); + rv = http_parse_masque_host_port (path, vec_len (path), &target); + HTTP_TEST ((rv != 0), "'%v' should be invalid", path); + vec_free (path); + + path = format (0, "/443/"); + rv = http_parse_masque_host_port (path, vec_len (path), &target); + HTTP_TEST ((rv != 0), "'%v' should be invalid", path); + vec_free (path); + + path = format (0, "10.10.2.45/"); + rv = http_parse_masque_host_port (path, vec_len (path), &target); + HTTP_TEST ((rv != 0), "'%v' should be invalid", path); + vec_free (path); + + path = format (0, "10.10.2.45"); + rv = http_parse_masque_host_port (path, vec_len (path), &target); + HTTP_TEST ((rv != 0), "'%v' should be invalid", path); + vec_free (path); + + path = format (0, "10.10.2.45/443"); + rv = http_parse_masque_host_port (path, vec_len (path), &target); + HTTP_TEST ((rv != 0), "'%v' should be invalid", path); + vec_free (path); + + return 0; +} + static clib_error_t * test_http_command_fn (vlib_main_t *vm, unformat_input_t *input, vlib_cli_command_t *cmd) @@ -262,12 +326,16 @@ test_http_command_fn (vlib_main_t *vm, unformat_input_t *input, res = http_test_authority_form (vm); else if (unformat (input, "absolute-form")) 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, "all")) { if ((res = http_test_authority_form (vm))) goto done; if ((res = http_test_absolute_form (vm))) goto done; + if ((res = http_test_parse_masque_host_port (vm))) + goto done; } else break; -- cgit 1.2.3-korg