http_static: introduce max-body-size parameter 75/42175/15
authorSemir Sionek <[email protected]>
Fri, 17 Jan 2025 15:06:23 +0000 (10:06 -0500)
committerFlorin Coras <[email protected]>
Thu, 23 Jan 2025 18:30:28 +0000 (18:30 +0000)
Introduce the max-body-size parameter to put a limit on how big of a
POST request can the static server take (and how big of a memory
allocation that causes).

Type: improvement
Change-Id: I93cdeaf38dabe2850665e92bedbaa0545c375214
Signed-off-by: Semir Sionek <[email protected]>
extras/hs-test/http_test.go
src/plugins/http_static/http_static.api
src/plugins/http_static/http_static.c
src/plugins/http_static/http_static.h
src/plugins/http_static/http_static_test.c
src/plugins/http_static/static_server.c

index 4cbcdeb..6893455 100644 (file)
@@ -36,7 +36,7 @@ func init() {
                HttpClientErrRespTest, HttpClientPostFormTest, HttpClientGet128kbResponseTest, HttpClientGetResponseBodyTest,
                HttpClientGetNoResponseBodyTest, HttpClientPostFileTest, HttpClientPostFilePtrTest, HttpUnitTest,
                HttpRequestLineTest, HttpClientGetTimeout, HttpStaticFileHandlerWrkTest, HttpStaticUrlHandlerWrkTest, HttpConnTimeoutTest,
-               HttpClientGetRepeat, HttpClientPostRepeat, HttpIgnoreH2UpgradeTest, HttpInvalidAuthorityFormUriTest)
+               HttpClientGetRepeat, HttpClientPostRepeat, HttpIgnoreH2UpgradeTest, HttpInvalidAuthorityFormUriTest, HttpHeaderErrorConnectionDropTest)
        RegisterNoTopoSoloTests(HttpStaticPromTest, HttpGetTpsTest, HttpGetTpsInterruptModeTest, PromConcurrentConnectionsTest,
                PromMemLeakTest, HttpClientPostMemLeakTest, HttpInvalidClientRequestMemLeakTest, HttpPostTpsTest, HttpPostTpsInterruptModeTest,
                PromConsecutiveConnectionsTest, HttpGetTpsTlsTest, HttpPostTpsTlsTest)
@@ -1240,7 +1240,7 @@ func HttpInvalidContentLengthTest(s *NoTopoSuite) {
 func HttpContentLengthTest(s *NoTopoSuite) {
        vpp := s.Containers.Vpp.VppInstance
        serverAddress := s.VppAddr()
-       s.Log(vpp.Vppctl("http static server uri tcp://" + serverAddress + "/80 url-handlers debug"))
+       s.Log(vpp.Vppctl("http static server uri tcp://" + serverAddress + "/80 url-handlers debug max-body-size 12"))
        ifName := s.VppIfName()
 
        resp, err := TcpSendReceive(serverAddress+":80",
@@ -1259,6 +1259,25 @@ func HttpContentLengthTest(s *NoTopoSuite) {
        validatePostInterfaceStats(s, resp)
 }
 
+func HttpHeaderErrorConnectionDropTest(s *NoTopoSuite) {
+       vpp := s.Containers.Vpp.VppInstance
+       serverAddress := s.VppAddr()
+       s.Log(vpp.Vppctl("http static server uri tcp://" + serverAddress + "/80 url-handlers debug max-body-size 12"))
+       request := "POST /interface_stats.json HTTP/1.1\r\nContent-Length: 18234234\r\n\r\n" + s.VppIfName()
+       conn, err := net.DialTimeout("tcp", serverAddress+":80", time.Second*30)
+       s.AssertNil(err, fmt.Sprint(err))
+       err = conn.SetDeadline(time.Now().Add(time.Second * 10))
+       s.AssertNil(err, fmt.Sprint(err))
+       _, err = conn.Write([]byte(request))
+       s.AssertNil(err, fmt.Sprint(err))
+       reply := make([]byte, 1024)
+       _, err = conn.Read(reply)
+       s.AssertNil(err, fmt.Sprint(err))
+       s.AssertContains(string(reply), "HTTP/1.1 413 Content Too Large")
+       check := make([]byte, 1)
+       _, err = conn.Read(check)
+       s.AssertEqual(err, io.EOF)
+}
 func HttpMethodNotImplementedTest(s *NoTopoSuite) {
        vpp := s.Containers.Vpp.VppInstance
        serverAddress := s.VppAddr()
index 60c0369..bd0cebc 100644 (file)
@@ -3,7 +3,7 @@
     This file defines static http server control-plane API messages
 */
 
-option version = "2.3.0";
+option version = "2.4.0";
 
 /** \brief Configure and enable the static http server
     @param client_index - opaque cookie to identify the sender
@@ -74,3 +74,39 @@ autoreply define http_static_enable_v3 {
     /* The bind URI */
     string uri[256];
 };
+
+/** \brief Configure and enable the static http server
+    @param client_index - opaque cookie to identify the sender
+    @param context - sender context, to match reply w/ request
+    @param fifo_size - size (in bytes) of the session FIFOs
+    @param cache_size_limit - size (in bytes) of the in-memory file data cache
+    @param max_age - how long a response is considered fresh (in seconds)
+    @param max_body_size - maximum size of a request body (in bytes)
+    @param keepalive_timeout - timeout during which client connection will stay open (in seconds)
+    @param prealloc_fifos - number of preallocated fifos (usually 0)
+    @param private_segment_size - fifo segment size (usually 0)
+    @param www_root - html root path
+    @param uri - bind URI, defaults to "tcp://0.0.0.0/80"
+*/
+
+autoreply define http_static_enable_v4 {
+    /* Client identifier, set from api_main.my_client_index */
+    u32 client_index;
+
+    /* Arbitrary context, so client can match reply to request */
+    u32 context;
+    /* Typical options */
+    u32 fifo_size;
+    u32 cache_size_limit;
+    u32 max_age [default=600];
+    u32 keepalive_timeout [default=60];
+    u64 max_body_size [default=8000];
+    /* Unusual options */
+    u32 prealloc_fifos;
+    u32 private_segment_size;
+
+    /* Root of the html path */
+    string www_root[256];
+    /* The bind URI */
+    string uri[256];
+};
index 464fd27..7a12f37 100644 (file)
@@ -67,7 +67,7 @@ hss_register_url_handler (hss_url_handler_fn fp, const char *url,
 static int
 hss_enable_api (u32 fifo_size, u32 cache_limit, u32 prealloc_fifos,
                u32 private_segment_size, u8 *www_root, u8 *uri, u32 max_age,
-               u32 keepalive_timeout)
+               u32 keepalive_timeout, u64 max_body_size)
 {
   hss_main_t *hsm = &hss_main;
   int rv;
@@ -79,6 +79,7 @@ hss_enable_api (u32 fifo_size, u32 cache_limit, u32 prealloc_fifos,
   hsm->www_root = format (0, "%s%c", www_root, 0);
   hsm->uri = format (0, "%s%c", uri, 0);
   hsm->max_age = max_age;
+  hsm->max_body_size = max_body_size;
   hsm->keepalive_timeout = keepalive_timeout;
 
   if (vec_len (hsm->www_root) < 2)
@@ -119,7 +120,8 @@ vl_api_http_static_enable_v2_t_handler (vl_api_http_static_enable_v2_t *mp)
   rv = hss_enable_api (ntohl (mp->fifo_size), ntohl (mp->cache_size_limit),
                       ntohl (mp->prealloc_fifos),
                       ntohl (mp->private_segment_size), mp->www_root, mp->uri,
-                      ntohl (mp->max_age), HSS_DEFAULT_KEEPALIVE_TIMEOUT);
+                      ntohl (mp->max_age), HSS_DEFAULT_KEEPALIVE_TIMEOUT,
+                      HSS_DEFAULT_MAX_BODY_SIZE);
 
   REPLY_MACRO (VL_API_HTTP_STATIC_ENABLE_V2_REPLY);
 }
@@ -138,11 +140,32 @@ vl_api_http_static_enable_v3_t_handler (vl_api_http_static_enable_v3_t *mp)
   rv = hss_enable_api (ntohl (mp->fifo_size), ntohl (mp->cache_size_limit),
                       ntohl (mp->prealloc_fifos),
                       ntohl (mp->private_segment_size), mp->www_root, mp->uri,
-                      ntohl (mp->max_age), ntohl (mp->keepalive_timeout));
+                      ntohl (mp->max_age), ntohl (mp->keepalive_timeout),
+                      HSS_DEFAULT_MAX_BODY_SIZE);
 
   REPLY_MACRO (VL_API_HTTP_STATIC_ENABLE_V3_REPLY);
 }
 
+/* API message handler */
+static void
+vl_api_http_static_enable_v4_t_handler (vl_api_http_static_enable_v4_t *mp)
+{
+  vl_api_http_static_enable_v4_reply_t *rmp;
+  hss_main_t *hsm = &hss_main;
+  int rv;
+
+  mp->uri[ARRAY_LEN (mp->uri) - 1] = 0;
+  mp->www_root[ARRAY_LEN (mp->www_root) - 1] = 0;
+
+  rv = hss_enable_api (ntohl (mp->fifo_size), ntohl (mp->cache_size_limit),
+                      ntohl (mp->prealloc_fifos),
+                      ntohl (mp->private_segment_size), mp->www_root, mp->uri,
+                      ntohl (mp->max_age), ntohl (mp->keepalive_timeout),
+                      ntohl (mp->max_body_size));
+
+  REPLY_MACRO (VL_API_HTTP_STATIC_ENABLE_V4_REPLY);
+}
+
 #include <http_static/http_static.api.c>
 static clib_error_t *
 hss_api_init (vlib_main_t *vm)
index 88b0002..e158a32 100644 (file)
@@ -24,6 +24,7 @@
 #include <http_static/http_cache.h>
 
 #define HSS_DEFAULT_MAX_AGE 600
+#define HSS_DEFAULT_MAX_BODY_SIZE     8192
 #define HSS_DEFAULT_KEEPALIVE_TIMEOUT 60
 
 /** @file http_static.h
@@ -161,6 +162,8 @@ typedef struct
   u64 cache_size;
   /** How long a response is considered fresh (in seconds) */
   u32 max_age;
+  /** Maximum size of a request body (in bytes) **/
+  u64 max_body_size;
   /** Formatted max_age: "max-age=xyz" */
   u8 *max_age_formatted;
   /** Timeout during which client connection will stay open */
index edb016f..5648789 100644 (file)
@@ -214,6 +214,104 @@ api_http_static_enable_v3 (vat_main_t *vam)
   mp->private_segment_size = ntohl (private_segment_size);
   mp->max_age = ntohl (max_age);
   mp->keepalive_timeout = ntohl (keepalive_timeout);
+  /* send it... */
+  S (mp);
+
+  /* Wait for a reply... */
+  W (ret);
+  return ret;
+}
+
+static int
+api_http_static_enable_v4 (vat_main_t *vam)
+{
+  unformat_input_t *line_input = vam->input;
+  vl_api_http_static_enable_v4_t *mp;
+  u64 tmp;
+  u8 *www_root = 0;
+  u8 *uri = 0;
+  u32 prealloc_fifos = 0;
+  u32 private_segment_size = 0;
+  u32 fifo_size = 8 << 10;
+  u32 cache_size_limit = 1 << 20;
+  u32 max_age = HSS_DEFAULT_MAX_AGE;
+  u32 keepalive_timeout = HSS_DEFAULT_KEEPALIVE_TIMEOUT;
+  u64 max_body_size = HSS_DEFAULT_MAX_BODY_SIZE;
+  int ret;
+
+  /* Parse args required to build the message */
+  while (unformat_check_input (line_input) != UNFORMAT_END_OF_INPUT)
+    {
+      if (unformat (line_input, "www-root %s", &www_root))
+       ;
+      else if (unformat (line_input, "prealloc-fifos %d", &prealloc_fifos))
+       ;
+      else if (unformat (line_input, "private-segment-size %U",
+                        unformat_memory_size, &tmp))
+       {
+         if (tmp >= 0x100000000ULL)
+           {
+             errmsg ("private segment size %llu, too large", tmp);
+             return -99;
+           }
+         private_segment_size = (u32) tmp;
+       }
+      else if (unformat (line_input, "fifo-size %U", unformat_memory_size,
+                        &tmp))
+       {
+         if (tmp >= 0x100000000ULL)
+           {
+             errmsg ("fifo-size %llu, too large", tmp);
+             return -99;
+           }
+         fifo_size = (u32) tmp;
+       }
+      else if (unformat (line_input, "cache-size %U", unformat_memory_size,
+                        &tmp))
+       {
+         if (tmp < (128ULL << 10))
+           {
+             errmsg ("cache-size must be at least 128kb");
+             return -99;
+           }
+         cache_size_limit = (u32) tmp;
+       }
+      else if (unformat (line_input, "max-age %d", &max_age))
+       ;
+      else if (unformat (line_input, "keepalive-timeout %d",
+                        &keepalive_timeout))
+       ;
+      else if (unformat (line_input, "uri %s", &uri))
+       ;
+      else if (unformat (line_input, "max-body-size %llu", &max_body_size))
+       ;
+      else
+       {
+         errmsg ("unknown input `%U'", format_unformat_error, line_input);
+         return -99;
+       }
+    }
+
+  if (www_root == 0)
+    {
+      errmsg ("Must specify www-root");
+      return -99;
+    }
+
+  if (uri == 0)
+    uri = format (0, "tcp://0.0.0.0/80%c", 0);
+
+  /* Construct the API message */
+  M (HTTP_STATIC_ENABLE_V4, mp);
+  strncpy_s ((char *) mp->www_root, 256, (const char *) www_root, 256);
+  strncpy_s ((char *) mp->uri, 256, (const char *) uri, 256);
+  mp->fifo_size = ntohl (fifo_size);
+  mp->cache_size_limit = ntohl (cache_size_limit);
+  mp->prealloc_fifos = ntohl (prealloc_fifos);
+  mp->private_segment_size = ntohl (private_segment_size);
+  mp->max_age = ntohl (max_age);
+  mp->keepalive_timeout = ntohl (keepalive_timeout);
+  mp->max_body_size = ntohl (max_body_size);
 
   /* send it... */
   S (mp);
index 7c8c65b..0744168 100644 (file)
@@ -500,6 +500,7 @@ handle_request (hss_session_t *hs, http_req_method_t rt, u8 *target_path,
 static int
 hss_ts_rx_callback (session_t *ts)
 {
+  hss_main_t *hsm = &hss_main;
   hss_session_t *hs;
   u8 *target_path = 0, *target_query = 0, *data = 0;
   http_msg_t msg;
@@ -522,7 +523,7 @@ hss_ts_rx_callback (session_t *ts)
       http_add_header (&hs->resp_headers, HTTP_HEADER_ALLOW,
                       http_token_lit ("GET, POST"));
       start_send_data (hs, HTTP_STATUS_METHOD_NOT_ALLOWED);
-      goto done;
+      goto err_done;
     }
 
   /* Read target path */
@@ -535,7 +536,7 @@ hss_ts_rx_callback (session_t *ts)
       if (http_validate_abs_path_syntax (target_path, 0))
        {
          start_send_data (hs, HTTP_STATUS_BAD_REQUEST);
-         goto done;
+         goto err_done;
        }
       /* Target path must be a proper C-string in addition to a vector */
       vec_add1 (target_path, 0);
@@ -551,13 +552,24 @@ hss_ts_rx_callback (session_t *ts)
       if (http_validate_query_syntax (target_query, 0))
        {
          start_send_data (hs, HTTP_STATUS_BAD_REQUEST);
-         goto done;
+         goto err_done;
        }
     }
 
   /* Read request body for POST requests */
   if (msg.data.body_len && msg.method_type == HTTP_REQ_POST)
     {
+      if (msg.data.body_len > hsm->max_body_size)
+       {
+         start_send_data (hs, HTTP_STATUS_CONTENT_TOO_LARGE);
+         goto err_done;
+       }
+      if (svm_fifo_max_dequeue (ts->rx_fifo) - msg.data.body_offset <
+         msg.data.body_len)
+       {
+         start_send_data (hs, HTTP_STATUS_INTERNAL_ERROR);
+         goto err_done;
+       }
       vec_validate (data, msg.data.body_len - 1);
       rv = svm_fifo_peek (ts->rx_fifo, msg.data.body_offset, msg.data.body_len,
                          data);
@@ -566,7 +578,10 @@ hss_ts_rx_callback (session_t *ts)
 
   /* Find and send data */
   handle_request (hs, msg.method_type, target_path, target_query, data);
+  goto done;
 
+err_done:
+  hss_session_disconnect_transport (hs);
 done:
   vec_free (target_path);
   vec_free (target_query);
@@ -879,6 +894,7 @@ hss_create_command_fn (vlib_main_t *vm, unformat_input_t *input,
   hsm->fifo_size = 0;
   hsm->cache_size = 10 << 20;
   hsm->max_age = HSS_DEFAULT_MAX_AGE;
+  hsm->max_body_size = HSS_DEFAULT_MAX_BODY_SIZE;
   hsm->keepalive_timeout = HSS_DEFAULT_KEEPALIVE_TIMEOUT;
 
   /* Get a line of input. */
@@ -916,6 +932,9 @@ hss_create_command_fn (vlib_main_t *vm, unformat_input_t *input,
        hsm->enable_url_handlers = 1;
       else if (unformat (line_input, "max-age %d", &hsm->max_age))
        ;
+      else if (unformat (line_input, "max-body-size %U", unformat_memory_size,
+                        &hsm->max_body_size))
+       ;
       else
        {
          error = clib_error_return (0, "unknown input `%U'",
@@ -979,7 +998,7 @@ VLIB_CLI_COMMAND (hss_create_command, static) = {
     "http static server www-root <path> [prealloc-fifos <nn>]\n"
     "[private-segment-size <nnMG>] [fifo-size <nbytes>] [max-age <nseconds>]\n"
     "[uri <uri>] [ptr-thresh <nn>] [url-handlers] [debug [nn]]\n"
-    "[keepalive-timeout <nn>]\n",
+    "[keepalive-timeout <nn>] [max-body-size <nn>]\n",
   .function = hss_create_command_fn,
 };