This is an automated email from the ASF dual-hosted git repository.

cmcfarlen pushed a commit to branch 10.0.x
in repository https://gitbox.apache.org/repos/asf/trafficserver.git

commit 62355fb177d18ea566c5a30df775b41d9e4c14b5
Author: Masakazu Kitajo <[email protected]>
AuthorDate: Wed Jul 17 14:04:34 2024 -0600

    Add HTTP/2 Empty CONTINUATION & DATA frame counter (#11345)
    
    * HTTP/2 Empty CONTINUATION & DATA frame counter
    
    * Allow using 0 as a valid limit value and use negative numbers as unlimited
    
    * Don't return early if CONTINUATION frame is empty
    
    * Fix typos
    
    * Add documentation about proxy.config.http2.max_empty_frames_per_minute
    
    * Add documentation about http2_max_empty_frames_per_minute in sni.yaml
    
    * Fix errors from rebasing
    
    * Documentation
    
    * Improve wording
    
    ---------
    
    Co-authored-by: Masaori Koshiba <[email protected]>
    (cherry picked from commit 21581ecbf1cc441cfb6fab4f935b326107963ced)
---
 doc/admin-guide/files/records.yaml.en.rst  | 19 +++++++++
 doc/admin-guide/files/sni.yaml.en.rst      |  4 ++
 include/iocore/net/TLSSNISupport.h         | 10 ++---
 include/proxy/http2/HTTP2.h                | 16 ++++----
 include/proxy/http2/Http2ConnectionState.h | 14 ++++---
 src/proxy/http2/HTTP2.cc                   | 32 ++++++++-------
 src/proxy/http2/Http2ConnectionState.cc    | 66 +++++++++++++++++++++++-------
 src/records/RecordsConfig.cc               | 14 ++++---
 8 files changed, 124 insertions(+), 51 deletions(-)

diff --git a/doc/admin-guide/files/records.yaml.en.rst 
b/doc/admin-guide/files/records.yaml.en.rst
index 198b014e4f..19d6dbd144 100644
--- a/doc/admin-guide/files/records.yaml.en.rst
+++ b/doc/admin-guide/files/records.yaml.en.rst
@@ -4546,6 +4546,7 @@ HTTP/2 Configuration
    Specifies how many settings in an HTTP/2 SETTINGS frame |TS| accepts.
    Clients exceeded this limit will be immediately disconnected with an error
    code of ENHANCE_YOUR_CALM.
+   Any negative value configures no limit to the number of settings received.
 
 .. ts:cv:: CONFIG proxy.config.http2.max_settings_per_minute INT 14
    :reloadable:
@@ -4553,6 +4554,7 @@ HTTP/2 Configuration
    Specifies how many settings in HTTP/2 SETTINGS frames |TS| accept for a 
minute.
    Clients exceeded this limit will be immediately disconnected with an error
    code of ENHANCE_YOUR_CALM.
+   Any negative value configures no limit to the number of settings received.
 
 .. ts:cv:: CONFIG proxy.config.http2.max_settings_frames_per_minute INT 14
    :reloadable:
@@ -4560,6 +4562,7 @@ HTTP/2 Configuration
    Specifies how many SETTINGS frames |TS| receives for a minute at maximum.
    Clients exceeded this limit will be immediately disconnected with an error
    code of ENHANCE_YOUR_CALM.
+   Any negative value configures no limit to the number of SETTINGS frames 
received.
 
 .. ts:cv:: CONFIG proxy.config.http2.max_ping_frames_per_minute INT 60
    :reloadable:
@@ -4567,6 +4570,7 @@ HTTP/2 Configuration
    Specifies how many number of PING frames |TS| receives for a minute at 
maximum.
    Clients exceeded this limit will be immediately disconnected with an error
    code of ENHANCE_YOUR_CALM.
+   Any negative value configures no limit to the number of PING frames 
received.
 
 .. ts:cv:: CONFIG proxy.config.http2.max_priority_frames_per_minute INT 120
    :reloadable:
@@ -4576,6 +4580,7 @@ HTTP/2 Configuration
    code of ENHANCE_YOUR_CALM. If this is set to 0, the limit logic is disabled.
    This limit only will be enforced if 
:ts:cv:`proxy.config.http2.stream_priority_enabled`
    is set to 1.
+   Any negative value configures no limit to the number of PRIORITY frames 
received.
 
 .. ts:cv:: CONFIG proxy.config.http2.max_rst_stream_frames_per_minute INT 200
    :reloadable:
@@ -4583,6 +4588,7 @@ HTTP/2 Configuration
    Specifies how many RST_STREAM frames |TS| receives per minute at maximum.
    Clients exceeding this limit will be immediately disconnected with an error
    code of ENHANCE_YOUR_CALM.
+   Any negative value configures no limit to the number of RST_STREAM frames 
received.
 
 .. ts:cv:: CONFIG proxy.config.http2.max_continuation_frames_per_minute INT 120
    :reloadable:
@@ -4590,6 +4596,19 @@ HTTP/2 Configuration
    Specifies how many CONTINUATION frames |TS| receives per minute at maximum.
    Clients exceeding this limit will be immediately disconnected with an error
    code of ENHANCE_YOUR_CALM.
+   Any negative value configures no limit to the number of CONTINUATION frames 
received.
+
+.. ts:cv:: CONFIG proxy.config.http2.max_empty_frames_per_minute INT 0
+   :reloadable:
+
+   Specifies the maximum number of empty frames |TS| will receive per minute 
before it will start closing connections.
+   In this context, an "empty frame" means either a DATA frame that does not 
carry a payload
+   nor an END_STREAM flag, or a CONTINUATION frame that does not carry payload
+   nor an END_HEADERS flag.
+   Clients exceeding this limit will be immediately disconnected with an error
+   code of ENHANCE_YOUR_CALM.
+   Any negative value configures no limit to the number of empty frames 
received.
+   ``0`` is the default configuration, meaning that no empty frames are 
allowed.
 
 .. ts:cv:: CONFIG proxy.config.http2.min_avg_window_update FLOAT 2560.0
    :reloadable:
diff --git a/doc/admin-guide/files/sni.yaml.en.rst 
b/doc/admin-guide/files/sni.yaml.en.rst
index 7be40de811..191fefb010 100644
--- a/doc/admin-guide/files/sni.yaml.en.rst
+++ b/doc/admin-guide/files/sni.yaml.en.rst
@@ -228,6 +228,10 @@ http2_max_continuation_frames_per_minute Inbound   
Specifies how many CONTINUATI
                                                    By default this is 
:ts:cv:`proxy.config.http2.max_continuation_frames_per_minute`.
                                                    NOTE: Connection coalescing 
may prevent this from taking effect.
 
+http2_max_empty_frames_per_minute        Inbound   Specifies how many empty 
frames |TS| receives per minute at maximum.
+                                                   By default this is 
:ts:cv:`proxy.config.http2.max_empty_frames_per_minute`.
+                                                   NOTE: Connection coalescing 
may prevent this from taking effect.
+
 quic                                     Inbound   Indicates whether QUIC 
connections should be accepted. The valid values are :code:`on` or
                                                    :code:`off`. Note that this 
is a more specific setting to configure QUIC availability per server
                                                    name. More broadly, you 
will also need to configure :ts:cv:`proxy.config.http.server_ports` to
diff --git a/include/iocore/net/TLSSNISupport.h 
b/include/iocore/net/TLSSNISupport.h
index 060b60c826..1e6685276f 100644
--- a/include/iocore/net/TLSSNISupport.h
+++ b/include/iocore/net/TLSSNISupport.h
@@ -61,11 +61,11 @@ public:
     std::optional<uint32_t>         http2_buffer_water_mark;
     std::optional<uint32_t>         server_max_early_data;
     std::optional<uint32_t>         http2_initial_window_size_in;
-    std::optional<uint32_t>         http2_max_settings_frames_per_minute;
-    std::optional<uint32_t>         http2_max_ping_frames_per_minute;
-    std::optional<uint32_t>         http2_max_priority_frames_per_minute;
-    std::optional<uint32_t>         http2_max_rst_stream_frames_per_minute;
-    std::optional<uint32_t>         http2_max_continuation_frames_per_minute;
+    std::optional<int32_t>          http2_max_settings_frames_per_minute;
+    std::optional<int32_t>          http2_max_ping_frames_per_minute;
+    std::optional<int32_t>          http2_max_priority_frames_per_minute;
+    std::optional<int32_t>          http2_max_rst_stream_frames_per_minute;
+    std::optional<int32_t>          http2_max_continuation_frames_per_minute;
     std::optional<std::string_view> outbound_sni_policy;
   } hints_from_sni;
 
diff --git a/include/proxy/http2/HTTP2.h b/include/proxy/http2/HTTP2.h
index 11c60ec35f..9f3c62b97e 100644
--- a/include/proxy/http2/HTTP2.h
+++ b/include/proxy/http2/HTTP2.h
@@ -104,6 +104,7 @@ struct Http2StatsBlock {
   Metrics::Counter::AtomicType *max_priority_frames_per_minute_exceeded;
   Metrics::Counter::AtomicType *max_rst_stream_frames_per_minute_exceeded;
   Metrics::Counter::AtomicType *max_continuation_frames_per_minute_exceeded;
+  Metrics::Counter::AtomicType *max_empty_frames_per_minute_exceeded;
   Metrics::Counter::AtomicType *insufficient_avg_window_update;
   Metrics::Counter::AtomicType *max_concurrent_streams_exceeded_in;
   Metrics::Counter::AtomicType *max_concurrent_streams_exceeded_out;
@@ -423,13 +424,14 @@ public:
 
   static float    stream_error_rate_threshold;
   static uint32_t stream_error_sampling_threshold;
-  static uint32_t max_settings_per_frame;
-  static uint32_t max_settings_per_minute;
-  static uint32_t max_settings_frames_per_minute;
-  static uint32_t max_ping_frames_per_minute;
-  static uint32_t max_priority_frames_per_minute;
-  static uint32_t max_rst_stream_frames_per_minute;
-  static uint32_t max_continuation_frames_per_minute;
+  static int32_t  max_settings_per_frame;
+  static int32_t  max_settings_per_minute;
+  static int32_t  max_settings_frames_per_minute;
+  static int32_t  max_ping_frames_per_minute;
+  static int32_t  max_priority_frames_per_minute;
+  static int32_t  max_rst_stream_frames_per_minute;
+  static int32_t  max_continuation_frames_per_minute;
+  static int32_t  max_empty_frames_per_minute;
   static float    min_avg_window_update;
   static uint32_t con_slow_log_threshold;
   static uint32_t stream_slow_log_threshold;
diff --git a/include/proxy/http2/Http2ConnectionState.h 
b/include/proxy/http2/Http2ConnectionState.h
index 0681fddeed..289918754c 100644
--- a/include/proxy/http2/Http2ConnectionState.h
+++ b/include/proxy/http2/Http2ConnectionState.h
@@ -201,6 +201,8 @@ public:
   uint32_t get_received_rst_stream_frame_count();
   void     increment_received_continuation_frame_count();
   uint32_t get_received_continuation_frame_count();
+  void     increment_received_empty_frame_count();
+  uint32_t get_received_empty_frame_count();
 
   ssize_t        get_peer_rwnd() const;
   Http2ErrorCode increment_peer_rwnd(size_t amount);
@@ -336,6 +338,7 @@ private:
   FrequencyCounter _received_priority_frame_counter;
   FrequencyCounter _received_rst_stream_frame_counter;
   FrequencyCounter _received_continuation_frame_counter;
+  FrequencyCounter _received_empty_frame_counter;
 
   /** Records the various settings for each SETTINGS frame that we've sent.
    *
@@ -405,11 +408,12 @@ private:
   Event             *_data_event         = nullptr;
   Event             *retransmit_event    = nullptr;
 
-  uint32_t configured_max_settings_frames_per_minute     = 0;
-  uint32_t configured_max_ping_frames_per_minute         = 0;
-  uint32_t configured_max_priority_frames_per_minute     = 0;
-  uint32_t configured_max_rst_stream_frames_per_minute   = 0;
-  uint32_t configured_max_continuation_frames_per_minute = 0;
+  int32_t configured_max_settings_frames_per_minute     = 0;
+  int32_t configured_max_ping_frames_per_minute         = 0;
+  int32_t configured_max_priority_frames_per_minute     = 0;
+  int32_t configured_max_rst_stream_frames_per_minute   = 0;
+  int32_t configured_max_continuation_frames_per_minute = 0;
+  int32_t configured_max_empty_frames_per_minute        = 0;
 };
 
 ///////////////////////////////////////////////
diff --git a/src/proxy/http2/HTTP2.cc b/src/proxy/http2/HTTP2.cc
index a28284588d..8323b05352 100644
--- a/src/proxy/http2/HTTP2.cc
+++ b/src/proxy/http2/HTTP2.cc
@@ -486,13 +486,14 @@ uint32_t               Http2::no_activity_timeout_out    
= 120;
 
 float    Http2::stream_error_rate_threshold        = 0.1;
 uint32_t Http2::stream_error_sampling_threshold    = 10;
-uint32_t Http2::max_settings_per_frame             = 7;
-uint32_t Http2::max_settings_per_minute            = 14;
-uint32_t Http2::max_settings_frames_per_minute     = 14;
-uint32_t Http2::max_ping_frames_per_minute         = 60;
-uint32_t Http2::max_priority_frames_per_minute     = 120;
-uint32_t Http2::max_rst_stream_frames_per_minute   = 200;
-uint32_t Http2::max_continuation_frames_per_minute = 120;
+int32_t  Http2::max_settings_per_frame             = 7;
+int32_t  Http2::max_settings_per_minute            = 14;
+int32_t  Http2::max_settings_frames_per_minute     = 14;
+int32_t  Http2::max_ping_frames_per_minute         = 60;
+int32_t  Http2::max_priority_frames_per_minute     = 120;
+int32_t  Http2::max_rst_stream_frames_per_minute   = 200;
+int32_t  Http2::max_continuation_frames_per_minute = 120;
+int32_t  Http2::max_empty_frames_per_minute        = 0;
 float    Http2::min_avg_window_update              = 2560.0;
 uint32_t Http2::con_slow_log_threshold             = 0;
 uint32_t Http2::stream_slow_log_threshold          = 0;
@@ -544,13 +545,14 @@ Http2::init()
   REC_EstablishStaticConfigInt32U(zombie_timeout_in, 
"proxy.config.http2.zombie_debug_timeout_in");
   REC_EstablishStaticConfigFloat(stream_error_rate_threshold, 
"proxy.config.http2.stream_error_rate_threshold");
   REC_EstablishStaticConfigInt32U(stream_error_sampling_threshold, 
"proxy.config.http2.stream_error_sampling_threshold");
-  REC_EstablishStaticConfigInt32U(max_settings_per_frame, 
"proxy.config.http2.max_settings_per_frame");
-  REC_EstablishStaticConfigInt32U(max_settings_per_minute, 
"proxy.config.http2.max_settings_per_minute");
-  REC_EstablishStaticConfigInt32U(max_settings_frames_per_minute, 
"proxy.config.http2.max_settings_frames_per_minute");
-  REC_EstablishStaticConfigInt32U(max_ping_frames_per_minute, 
"proxy.config.http2.max_ping_frames_per_minute");
-  REC_EstablishStaticConfigInt32U(max_priority_frames_per_minute, 
"proxy.config.http2.max_priority_frames_per_minute");
-  REC_EstablishStaticConfigInt32U(max_rst_stream_frames_per_minute, 
"proxy.config.http2.max_rst_stream_frames_per_minute");
-  REC_EstablishStaticConfigInt32U(max_continuation_frames_per_minute, 
"proxy.config.http2.max_continuation_frames_per_minute");
+  REC_EstablishStaticConfigInt32(max_settings_per_frame, 
"proxy.config.http2.max_settings_per_frame");
+  REC_EstablishStaticConfigInt32(max_settings_per_minute, 
"proxy.config.http2.max_settings_per_minute");
+  REC_EstablishStaticConfigInt32(max_settings_frames_per_minute, 
"proxy.config.http2.max_settings_frames_per_minute");
+  REC_EstablishStaticConfigInt32(max_ping_frames_per_minute, 
"proxy.config.http2.max_ping_frames_per_minute");
+  REC_EstablishStaticConfigInt32(max_priority_frames_per_minute, 
"proxy.config.http2.max_priority_frames_per_minute");
+  REC_EstablishStaticConfigInt32(max_rst_stream_frames_per_minute, 
"proxy.config.http2.max_rst_stream_frames_per_minute");
+  REC_EstablishStaticConfigInt32(max_continuation_frames_per_minute, 
"proxy.config.http2.max_continuation_frames_per_minute");
+  REC_EstablishStaticConfigInt32(max_empty_frames_per_minute, 
"proxy.config.http2.max_empty_frames_per_minute");
   REC_EstablishStaticConfigFloat(min_avg_window_update, 
"proxy.config.http2.min_avg_window_update");
   REC_EstablishStaticConfigInt32U(con_slow_log_threshold, 
"proxy.config.http2.connection.slow.log.threshold");
   REC_EstablishStaticConfigInt32U(stream_slow_log_threshold, 
"proxy.config.http2.stream.slow.log.threshold");
@@ -607,6 +609,8 @@ Http2::init()
     
Metrics::Counter::createPtr("proxy.process.http2.max_rst_stream_frames_per_minute_exceeded");
   http2_rsb.max_continuation_frames_per_minute_exceeded =
     
Metrics::Counter::createPtr("proxy.process.http2.max_continuation_frames_per_minute_exceeded");
+  http2_rsb.max_empty_frames_per_minute_exceeded =
+    
Metrics::Counter::createPtr("proxy.process.http2.max_empty_frames_per_minute_exceeded");
   http2_rsb.insufficient_avg_window_update = 
Metrics::Counter::createPtr("proxy.process.http2.insufficient_avg_window_update");
   http2_rsb.max_concurrent_streams_exceeded_in =
     
Metrics::Counter::createPtr("proxy.process.http2.max_concurrent_streams_exceeded_in");
diff --git a/src/proxy/http2/Http2ConnectionState.cc 
b/src/proxy/http2/Http2ConnectionState.cc
index d4e1ac6f2d..affbf0b813 100644
--- a/src/proxy/http2/Http2ConnectionState.cc
+++ b/src/proxy/http2/Http2ConnectionState.cc
@@ -184,8 +184,19 @@ Http2ConnectionState::rcv_data_frame(const Http2Frame 
&frame)
     stream->set_trailing_header_is_possible();
   }
 
-  // If payload length is 0 without END_STREAM flag, do nothing
-  if (payload_length == 0 && !stream->receive_end_stream) {
+  // If payload length is 0 without END_STREAM flag, just count it
+  const uint32_t unpadded_length = payload_length - pad_length;
+  if (unpadded_length == 0 && !stream->receive_end_stream) {
+    this->increment_received_empty_frame_count();
+    if (configured_max_empty_frames_per_minute >= 0 &&
+        this->get_received_empty_frame_count() > 
static_cast<uint32_t>(configured_max_empty_frames_per_minute)) {
+      
Metrics::Counter::increment(http2_rsb.max_empty_frames_per_minute_exceeded);
+      Http2StreamDebug(this->session, id, "Observed too many empty DATA 
frames: %u within the last minute",
+                       this->get_received_empty_frame_count());
+      return Http2Error(Http2ErrorClass::HTTP2_ERROR_CLASS_CONNECTION, 
Http2ErrorCode::HTTP2_ERROR_ENHANCE_YOUR_CALM,
+                        "recv data too frequent empty frame");
+    }
+
     return Http2Error(Http2ErrorClass::HTTP2_ERROR_CLASS_NONE);
   }
 
@@ -211,8 +222,7 @@ Http2ConnectionState::rcv_data_frame(const Http2Frame 
&frame)
                      this->get_local_rwnd(), session_window, 
stream->get_local_rwnd(), stream_window);
   }
 
-  const uint32_t unpadded_length = payload_length - pad_length;
-  MIOBuffer     *writer          = stream->read_vio_writer();
+  MIOBuffer *writer = stream->read_vio_writer();
   if (writer == nullptr) {
     return Http2Error(Http2ErrorClass::HTTP2_ERROR_CLASS_STREAM, 
Http2ErrorCode::HTTP2_ERROR_INTERNAL_ERROR, "no writer");
   }
@@ -573,8 +583,8 @@ Http2ConnectionState::rcv_priority_frame(const Http2Frame 
&frame)
   // Update PRIORITY frame count per minute
   this->increment_received_priority_frame_count();
   // Close this connection if its priority frame count received exceeds a limit
-  if (configured_max_priority_frames_per_minute != 0 &&
-      this->get_received_priority_frame_count() > 
configured_max_priority_frames_per_minute) {
+  if (configured_max_priority_frames_per_minute >= 0 &&
+      this->get_received_priority_frame_count() > 
static_cast<uint32_t>(configured_max_priority_frames_per_minute)) {
     
Metrics::Counter::increment(http2_rsb.max_priority_frames_per_minute_exceeded);
     Http2StreamDebug(this->session, stream_id, "Observed too frequent priority 
changes: %u priority changes within a last minute",
                      this->get_received_priority_frame_count());
@@ -648,8 +658,8 @@ Http2ConnectionState::rcv_rst_stream_frame(const Http2Frame 
&frame)
   // Update RST_STREAM frame count per minute
   this->increment_received_rst_stream_frame_count();
   // Close this connection if its RST_STREAM frame count exceeds a limit
-  if (configured_max_rst_stream_frames_per_minute != 0 &&
-      this->get_received_rst_stream_frame_count() > 
configured_max_rst_stream_frames_per_minute) {
+  if (configured_max_rst_stream_frames_per_minute >= 0 &&
+      this->get_received_rst_stream_frame_count() > 
static_cast<uint32_t>(configured_max_rst_stream_frames_per_minute)) {
     
Metrics::Counter::increment(http2_rsb.max_rst_stream_frames_per_minute_exceeded);
     Http2StreamDebug(this->session, stream_id, "Observed too frequent 
RST_STREAM frames: %u frames within a last minute",
                      this->get_received_rst_stream_frame_count());
@@ -697,8 +707,8 @@ Http2ConnectionState::rcv_settings_frame(const Http2Frame 
&frame)
   // Update SETTINGS frame count per minute
   this->increment_received_settings_frame_count();
   // Close this connection if its SETTINGS frame count exceeds a limit
-  if (configured_max_settings_frames_per_minute != 0 &&
-      this->get_received_settings_frame_count() > 
configured_max_settings_frames_per_minute) {
+  if (configured_max_settings_frames_per_minute >= 0 &&
+      this->get_received_settings_frame_count() > 
static_cast<uint32_t>(configured_max_settings_frames_per_minute)) {
     
Metrics::Counter::increment(http2_rsb.max_settings_frames_per_minute_exceeded);
     Http2StreamDebug(this->session, stream_id, "Observed too frequent SETTINGS 
frames: %u frames within a last minute",
                      this->get_received_settings_frame_count());
@@ -738,7 +748,7 @@ Http2ConnectionState::rcv_settings_frame(const Http2Frame 
&frame)
 
   uint32_t n_settings = 0;
   while (nbytes < frame.header().length) {
-    if (n_settings >= Http2::max_settings_per_frame) {
+    if (Http2::max_settings_per_frame >= 0 && n_settings >= 
static_cast<uint32_t>(Http2::max_settings_per_frame)) {
       Metrics::Counter::increment(http2_rsb.max_settings_per_frame_exceeded);
       Http2StreamDebug(this->session, stream_id, "Observed too many settings 
in a frame");
       return Http2Error(Http2ErrorClass::HTTP2_ERROR_CLASS_CONNECTION, 
Http2ErrorCode::HTTP2_ERROR_ENHANCE_YOUR_CALM,
@@ -779,7 +789,8 @@ Http2ConnectionState::rcv_settings_frame(const Http2Frame 
&frame)
   // Update settings count per minute
   this->increment_received_settings_count(n_settings);
   // Close this connection if its settings count received exceeds a limit
-  if (this->get_received_settings_count() > Http2::max_settings_per_minute) {
+  if (Http2::max_settings_per_frame >= 0 &&
+      this->get_received_settings_count() > 
static_cast<uint32_t>(Http2::max_settings_per_minute)) {
     Metrics::Counter::increment(http2_rsb.max_settings_per_minute_exceeded);
     Http2StreamDebug(this->session, stream_id, "Observed too frequent setting 
changes: %u settings within a last minute",
                      this->get_received_settings_count());
@@ -833,7 +844,8 @@ Http2ConnectionState::rcv_ping_frame(const Http2Frame 
&frame)
   // Update PING frame count per minute
   this->increment_received_ping_frame_count();
   // Close this connection if its ping count received exceeds a limit
-  if (configured_max_ping_frames_per_minute != 0 && 
this->get_received_ping_frame_count() > configured_max_ping_frames_per_minute) {
+  if (configured_max_ping_frames_per_minute >= 0 &&
+      this->get_received_ping_frame_count() > 
static_cast<uint32_t>(configured_max_ping_frames_per_minute)) {
     Metrics::Counter::increment(http2_rsb.max_ping_frames_per_minute_exceeded);
     Http2StreamDebug(this->session, stream_id, "Observed too frequent PING 
frames: %u PING frames within a last minute",
                      this->get_received_ping_frame_count());
@@ -1002,6 +1014,18 @@ Http2ConnectionState::rcv_continuation_frame(const 
Http2Frame &frame)
                       "continuation bad client id");
   }
 
+  if (payload_length == 0 && (frame.header().flags & 
HTTP2_FLAGS_HEADERS_END_HEADERS) == 0x0) {
+    this->increment_received_empty_frame_count();
+    if (configured_max_empty_frames_per_minute >= 0 &&
+        this->get_received_empty_frame_count() > 
static_cast<uint32_t>(configured_max_empty_frames_per_minute)) {
+      
Metrics::Counter::increment(http2_rsb.max_empty_frames_per_minute_exceeded);
+      Http2StreamDebug(this->session, stream_id, "Observed too many empty 
CONTINUATION frames: %u within the last minute",
+                       this->get_received_empty_frame_count());
+      return Http2Error(Http2ErrorClass::HTTP2_ERROR_CLASS_CONNECTION, 
Http2ErrorCode::HTTP2_ERROR_ENHANCE_YOUR_CALM,
+                        "recv continuation too frequent empty frame");
+    }
+  }
+
   // Find opened stream
   // CONTINUATION frames MUST be associated with a stream.  If a
   // CONTINUATION frame is received whose stream identifier field is 0x0,
@@ -1033,7 +1057,7 @@ Http2ConnectionState::rcv_continuation_frame(const 
Http2Frame &frame)
   this->increment_received_continuation_frame_count();
   // Close this connection if its CONTINUATION frame count exceeds a limit.
   if (configured_max_continuation_frames_per_minute != 0 &&
-      this->get_received_continuation_frame_count() > 
configured_max_continuation_frames_per_minute) {
+      this->get_received_continuation_frame_count() > 
static_cast<uint32_t>(configured_max_continuation_frames_per_minute)) {
     
Metrics::Counter::increment(http2_rsb.max_continuation_frames_per_minute_exceeded);
     Http2StreamDebug(this->session, stream_id, "Observed too frequent 
CONTINUATION frames: %u frames within a last minute",
                      this->get_received_continuation_frame_count());
@@ -1287,6 +1311,8 @@ Http2ConnectionState::init(Http2CommonSession *ssn)
   configured_max_priority_frames_per_minute     = 
Http2::max_priority_frames_per_minute;
   configured_max_rst_stream_frames_per_minute   = 
Http2::max_rst_stream_frames_per_minute;
   configured_max_continuation_frames_per_minute = 
Http2::max_continuation_frames_per_minute;
+  configured_max_empty_frames_per_minute        = 
Http2::max_empty_frames_per_minute;
+
   if (auto snis = session->get_netvc()->get_service<TLSSNISupport>(); snis) {
     if (snis->hints_from_sni.http2_max_settings_frames_per_minute.has_value()) 
{
       configured_max_settings_frames_per_minute = 
snis->hints_from_sni.http2_max_settings_frames_per_minute.value();
@@ -2798,6 +2824,18 @@ 
Http2ConnectionState::get_received_continuation_frame_count()
   return this->_received_continuation_frame_counter.get_count();
 }
 
+void
+Http2ConnectionState::increment_received_empty_frame_count()
+{
+  this->_received_empty_frame_counter.increment();
+}
+
+uint32_t
+Http2ConnectionState::get_received_empty_frame_count()
+{
+  return this->_received_empty_frame_counter.get_count();
+}
+
 // Return min_concurrent_streams_in when current client streams number is 
larger than max_active_streams_in.
 // Main purpose of this is preventing DDoS Attacks.
 unsigned
diff --git a/src/records/RecordsConfig.cc b/src/records/RecordsConfig.cc
index 4955ee7de3..899a6d3c76 100644
--- a/src/records/RecordsConfig.cc
+++ b/src/records/RecordsConfig.cc
@@ -1304,17 +1304,19 @@ static const RecordElement RecordsConfig[] =
   ,
   {RECT_CONFIG, "proxy.config.http2.stream_error_sampling_threshold", 
RECD_INT, "10", RECU_DYNAMIC, RR_NULL, RECC_STR, "^[0-9]+$", RECA_NULL}
   ,
-  {RECT_CONFIG, "proxy.config.http2.max_settings_per_frame", RECD_INT, "7", 
RECU_DYNAMIC, RR_NULL, RECC_STR, "^[0-9]+$", RECA_NULL}
+  {RECT_CONFIG, "proxy.config.http2.max_settings_per_frame", RECD_INT, "7", 
RECU_DYNAMIC, RR_NULL, RECC_STR, "^-?[0-9]+$", RECA_NULL}
   ,
-  {RECT_CONFIG, "proxy.config.http2.max_settings_per_minute", RECD_INT, "14", 
RECU_DYNAMIC, RR_NULL, RECC_STR, "^[0-9]+$", RECA_NULL}
+  {RECT_CONFIG, "proxy.config.http2.max_settings_per_minute", RECD_INT, "14", 
RECU_DYNAMIC, RR_NULL, RECC_STR, "^-?[0-9]+$", RECA_NULL}
   ,
-  {RECT_CONFIG, "proxy.config.http2.max_settings_frames_per_minute", RECD_INT, 
"14", RECU_DYNAMIC, RR_NULL, RECC_STR, "^[0-9]+$", RECA_NULL}
+  {RECT_CONFIG, "proxy.config.http2.max_settings_frames_per_minute", RECD_INT, 
"14", RECU_DYNAMIC, RR_NULL, RECC_STR, "^-?[0-9]+$", RECA_NULL}
   ,
-  {RECT_CONFIG, "proxy.config.http2.max_ping_frames_per_minute", RECD_INT, 
"60", RECU_DYNAMIC, RR_NULL, RECC_STR, "^[0-9]+$", RECA_NULL}
+  {RECT_CONFIG, "proxy.config.http2.max_ping_frames_per_minute", RECD_INT, 
"60", RECU_DYNAMIC, RR_NULL, RECC_STR, "^-?[0-9]+$", RECA_NULL}
   ,
-  {RECT_CONFIG, "proxy.config.http2.max_priority_frames_per_minute", RECD_INT, 
"120", RECU_DYNAMIC, RR_NULL, RECC_STR, "^[0-9]+$", RECA_NULL}
+  {RECT_CONFIG, "proxy.config.http2.max_priority_frames_per_minute", RECD_INT, 
"120", RECU_DYNAMIC, RR_NULL, RECC_STR, "^-?[0-9]+$", RECA_NULL}
   ,
-  {RECT_CONFIG, "proxy.config.http2.max_rst_stream_frames_per_minute", 
RECD_INT, "200", RECU_DYNAMIC, RR_NULL, RECC_STR, "^[0-9]+$", RECA_NULL}
+  {RECT_CONFIG, "proxy.config.http2.max_rst_stream_frames_per_minute", 
RECD_INT, "200", RECU_DYNAMIC, RR_NULL, RECC_STR, "^-?[0-9]+$", RECA_NULL}
+  ,
+  {RECT_CONFIG, "proxy.config.http2.max_empty_frames_per_minute", RECD_INT, 
"0", RECU_DYNAMIC, RR_NULL, RECC_STR, "^-?[0-9]+$", RECA_NULL}
   ,
   {RECT_CONFIG, "proxy.config.http2.max_continuation_frames_per_minute", 
RECD_INT, "120", RECU_DYNAMIC, RR_NULL, RECC_STR, "^[0-9]+$", RECA_NULL}
   ,

Reply via email to