http: udp proxy uri template parsing 96/41796/2
authorMatus Fabian <[email protected]>
Wed, 30 Oct 2024 17:04:40 +0000 (18:04 +0100)
committerFlorin Coras <[email protected]>
Wed, 30 Oct 2024 23:20:54 +0000 (23:20 +0000)
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 <[email protected]>
src/plugins/hs_apps/http_cli.c
src/plugins/http/http.h
src/plugins/http/http_plugin.rst
src/plugins/http/test/http_test.c

index 1000a2d..dfa90f9 100644 (file)
@@ -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;
     }
index 13118bf..04c53d1 100644 (file)
@@ -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_ */
 
 /*
index 2771d49..f86c796 100644 (file)
@@ -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;
         }
index c21cf85..d4ac8f4 100644 (file)
@@ -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;