From b8c6a23b74af1772e5cb0de25b38c234a418cb1d Mon Sep 17 00:00:00 2001
From: Masakazu Kitajo <maskit@apache.org>
Date: Wed, 3 Apr 2024 09:31:37 -0600
Subject: [PATCH] proxy.config.http2.max_continuation_frames_per_minute
 (#11206)

Origin: https://github.com/apache/trafficserver/commit/b8c6a23b74af1772e5cb0de25b38c234a418cb1d

This adds the ability to rate limite HTTP/2 CONTINUATION frames per
stream per minute.

Co-authored-by: Brian Neradt <brian.neradt@gmail.com>
---
 doc/admin-guide/files/records.config.en.rst   | 11 +++-
 .../statistics/core/http-connection.en.rst    | 11 +++-
 iocore/net/P_SNIActionPerformer.h             | 17 +++++
 iocore/net/SSLSNIConfig.cc                    |  4 ++
 iocore/net/TLSSNISupport.h                    |  1 +
 iocore/net/YamlSNIConfig.cc                   |  4 ++
 iocore/net/YamlSNIConfig.h                    |  2 +
 mgmt/RecordsConfig.cc                         |  2 +
 proxy/http2/HTTP2.cc                          | 66 ++++++++++---------
 proxy/http2/HTTP2.h                           |  2 +
 proxy/http2/Http2ConnectionState.cc           | 36 ++++++++--
 proxy/http2/Http2ConnectionState.h            | 12 ++--
 12 files changed, 126 insertions(+), 42 deletions(-)

diff --git a/doc/admin-guide/files/records.config.en.rst b/doc/admin-guide/files/records.config.en.rst
index f3df888708e..979c8bda2f4 100644
--- a/doc/admin-guide/files/records.config.en.rst
+++ b/doc/admin-guide/files/records.config.en.rst
@@ -4287,8 +4287,15 @@ HTTP/2 Configuration
-.. ts:cv:: CONFIG proxy.config.http2.max_rst_stream_frames_per_minute INT 14
+.. ts:cv:: CONFIG proxy.config.http2.max_rst_stream_frames_per_minute INT 200
    :reloadable:
 
-   Specifies how many RST_STREAM frames |TS| receives for a minute at maximum.
-   Clients exceeded this limit will be immediately disconnected with an error
+   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.
+
+.. ts:cv:: CONFIG proxy.config.http2.max_continuation_frames_per_minute INT 120
+   :reloadable:
+
+   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.
 
 .. ts:cv:: CONFIG proxy.config.http2.min_avg_window_update FLOAT 2560.0
diff --git a/doc/admin-guide/monitoring/statistics/core/http-connection.en.rst b/doc/admin-guide/monitoring/statistics/core/http-connection.en.rst
index b22da8e1c66..ee47a147c01 100644
--- a/doc/admin-guide/monitoring/statistics/core/http-connection.en.rst
+++ b/doc/admin-guide/monitoring/statistics/core/http-connection.en.rst
@@ -263,10 +263,17 @@ HTTP/2
 .. ts:stat:: global proxy.process.http2.max_rst_stream_frames_per_minute_exceeded integer
    :type: counter
 
-   Represents the total number of closed HTTP/2 connections for exceeding the
-   maximum allowed number of rst_stream frames per minute limit which is configured by
+   Represents the total number of HTTP/2 connections closed for exceeding the
+   maximum allowed number of ``RST_STREAM`` frames per minute limit which is configured by
    :ts:cv:`proxy.config.http2.max_rst_stream_frames_per_minute`.
 
+.. ts:stat:: global proxy.process.http2.max_continuation_frames_per_minute_exceeded integer
+   :type: counter
+
+   Represents the total number of HTTP/2 connections closed for exceeding the
+   maximum allowed number of ``CONTINUATION`` frames per minute limit which is
+   configured by :ts:cv:`proxy.config.http2.max_continuation_frames_per_minute`.
+
 .. ts:stat:: global proxy.process.http2.insufficient_avg_window_update integer
    :type: counter
 
diff --git a/iocore/net/P_SNIActionPerformer.h b/iocore/net/P_SNIActionPerformer.h
index e223ac7d0ba..eebe44b75a1 100644
--- a/iocore/net/P_SNIActionPerformer.h
+++ b/iocore/net/P_SNIActionPerformer.h
@@ -186,6 +186,23 @@ class HTTP2MaxRstStreamFramesPerMinute : public ActionItem
   int value = -1;
 };
 
+class HTTP2MaxContinuationFramesPerMinute : public ActionItem
+{
+public:
+  HTTP2MaxContinuationFramesPerMinute(int value) : value(value) {}
+  ~HTTP2MaxContinuationFramesPerMinute() override {}
+
+  int
+  SNIAction(TLSSNISupport *snis, const Context &ctx) const override
+  {
+    snis->hints_from_sni.http2_max_continuation_frames_per_minute = value;
+    return SSL_TLSEXT_ERR_OK;
+  }
+
+private:
+  int value = -1;
+};
+
 class TunnelDestination : public ActionItem
 {
 public:
diff --git a/iocore/net/SSLSNIConfig.cc b/iocore/net/SSLSNIConfig.cc
index a7071013f6a..942e6c420f0 100644
--- a/iocore/net/SSLSNIConfig.cc
+++ b/iocore/net/SSLSNIConfig.cc
@@ -151,6 +151,10 @@ SNIConfigParams::load_sni_config()
       ai->actions.push_back(
         std::make_unique<HTTP2MaxRstStreamFramesPerMinute>(item.http2_max_rst_stream_frames_per_minute.value()));
     }
+    if (item.http2_max_continuation_frames_per_minute.has_value()) {
+      ai->actions.push_back(
+        std::make_unique<HTTP2MaxContinuationFramesPerMinute>(item.http2_max_continuation_frames_per_minute.value()));
+    }
 
     ai->actions.push_back(std::make_unique<SNI_IpAllow>(item.ip_allow, item.fqdn));
 
diff --git a/iocore/net/TLSSNISupport.h b/iocore/net/TLSSNISupport.h
index ba2d13e9300..e8614ffa9b8 100644
--- a/iocore/net/TLSSNISupport.h
+++ b/iocore/net/TLSSNISupport.h
@@ -56,6 +56,7 @@ class TLSSNISupport
     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;
   } hints_from_sni;
 
 protected:
diff --git a/iocore/net/YamlSNIConfig.cc b/iocore/net/YamlSNIConfig.cc
index 9a777b806f2..7286197c9c7 100644
--- a/iocore/net/YamlSNIConfig.cc
+++ b/iocore/net/YamlSNIConfig.cc
@@ -148,6 +148,7 @@ std::set<std::string> valid_sni_config_keys = {TS_fqdn,
                                                TS_http2_max_ping_frames_per_minute,
                                                TS_http2_max_priority_frames_per_minute,
                                                TS_http2_max_rst_stream_frames_per_minute,
+                                               TS_http2_max_continuation_frames_per_minute,
                                                TS_ip_allow,
 #if TS_USE_HELLO_CB || defined(OPENSSL_IS_BORINGSSL)
                                                TS_valid_tls_versions_in,
@@ -193,6 +194,9 @@ template <> struct convert<YamlSNIConfig::Item> {
     if (node[TS_http2_max_rst_stream_frames_per_minute]) {
       item.http2_max_rst_stream_frames_per_minute = node[TS_http2_max_rst_stream_frames_per_minute].as<int>();
     }
+    if (node[TS_http2_max_continuation_frames_per_minute]) {
+      item.http2_max_continuation_frames_per_minute = node[TS_http2_max_continuation_frames_per_minute].as<int>();
+    }
 
     // enum
     if (node[TS_verify_client]) {
diff --git a/iocore/net/YamlSNIConfig.h b/iocore/net/YamlSNIConfig.h
index b297bd5c16e..8165dc336c5 100644
--- a/iocore/net/YamlSNIConfig.h
+++ b/iocore/net/YamlSNIConfig.h
@@ -60,6 +60,7 @@ TSDECL(http2_max_settings_frames_per_minute);
 TSDECL(http2_max_ping_frames_per_minute);
 TSDECL(http2_max_priority_frames_per_minute);
 TSDECL(http2_max_rst_stream_frames_per_minute);
+TSDECL(http2_max_continuation_frames_per_minute);
 TSDECL(host_sni_policy);
 #undef TSDECL
 
@@ -94,6 +95,7 @@ struct YamlSNIConfig {
     std::optional<int> http2_max_ping_frames_per_minute;
     std::optional<int> http2_max_priority_frames_per_minute;
     std::optional<int> http2_max_rst_stream_frames_per_minute;
+    std::optional<int> http2_max_continuation_frames_per_minute;
 
     bool tunnel_prewarm_srv                  = false;
     uint32_t tunnel_prewarm_min              = 0;
diff --git a/mgmt/RecordsConfig.cc b/mgmt/RecordsConfig.cc
index b63e0523c2b..a3752ea8359 100644
--- a/mgmt/RecordsConfig.cc
+++ b/mgmt/RecordsConfig.cc
@@ -1395,6 +1395,8 @@ static const RecordElement RecordsConfig[] =
   ,
   {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_continuation_frames_per_minute", RECD_INT, "120", RECU_DYNAMIC, RR_NULL, RECC_STR, "^[0-9]+$", RECA_NULL}
+  ,
   {RECT_CONFIG, "proxy.config.http2.min_avg_window_update", RECD_FLOAT, "2560.0", RECU_DYNAMIC, RR_NULL, RECC_NULL, nullptr, RECA_NULL}
   ,
   {RECT_CONFIG, "proxy.config.http2.header_table_size_limit", RECD_INT, "65536", RECU_DYNAMIC, RR_NULL, RECC_STR, "^[0-9]+$", RECA_NULL}
diff --git a/proxy/http2/HTTP2.cc b/proxy/http2/HTTP2.cc
index 04813d2212b..a3a5a0ac781 100644
--- a/proxy/http2/HTTP2.cc
+++ b/proxy/http2/HTTP2.cc
@@ -85,6 +85,8 @@ static const char *const HTTP2_STAT_MAX_PRIORITY_FRAMES_PER_MINUTE_EXCEEDED_NAME
   "proxy.process.http2.max_priority_frames_per_minute_exceeded";
 static const char *const HTTP2_STAT_MAX_RST_STREAM_FRAMES_PER_MINUTE_EXCEEDED_NAME =
   "proxy.process.http2.max_rst_stream_frames_per_minute_exceeded";
+static const char *const HTTP2_STAT_MAX_CONTINUATION_FRAMES_PER_MINUTE_EXCEEDED_NAME =
+  "proxy.process.http2.max_continuation_frames_per_minute_exceeded";
 static const char *const HTTP2_STAT_INSUFFICIENT_AVG_WINDOW_UPDATE_NAME = "proxy.process.http2.insufficient_avg_window_update";
 static const char *const HTTP2_STAT_MAX_CONCURRENT_STREAMS_EXCEEDED_IN_NAME =
   "proxy.process.http2.max_concurrent_streams_exceeded_in";
@@ -798,36 +800,37 @@ http2_decode_header_blocks(HTTPHdr *hdr, const uint8_t *buf_start, const uint32_
 }
 
 // Initialize this subsystem with librecords configs (for now)
-uint32_t Http2::max_concurrent_streams_in        = 100;
-uint32_t Http2::min_concurrent_streams_in        = 10;
-uint32_t Http2::max_active_streams_in            = 0;
-bool Http2::throttling                           = false;
-uint32_t Http2::stream_priority_enabled          = 0;
-uint32_t Http2::initial_window_size              = 65535;
-uint32_t Http2::max_frame_size                   = 16384;
-uint32_t Http2::header_table_size                = 4096;
-uint32_t Http2::max_header_list_size             = 4294967295;
-uint32_t Http2::accept_no_activity_timeout       = 120;
-uint32_t Http2::no_activity_timeout_in           = 120;
-uint32_t Http2::active_timeout_in                = 0;
-uint32_t Http2::push_diary_size                  = 256;
-uint32_t Http2::zombie_timeout_in                = 0;
-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;
-float Http2::min_avg_window_update               = 2560.0;
-uint32_t Http2::con_slow_log_threshold           = 0;
-uint32_t Http2::stream_slow_log_threshold        = 0;
-uint32_t Http2::header_table_size_limit          = 65536;
-uint32_t Http2::write_buffer_block_size          = 262144;
-float Http2::write_size_threshold                = 0.5;
-uint32_t Http2::write_time_threshold             = 100;
-uint32_t Http2::buffer_water_mark                = 0;
+uint32_t Http2::max_concurrent_streams_in          = 100;
+uint32_t Http2::min_concurrent_streams_in          = 10;
+uint32_t Http2::max_active_streams_in              = 0;
+bool Http2::throttling                             = false;
+uint32_t Http2::stream_priority_enabled            = 0;
+uint32_t Http2::initial_window_size                = 65535;
+uint32_t Http2::max_frame_size                     = 16384;
+uint32_t Http2::header_table_size                  = 4096;
+uint32_t Http2::max_header_list_size               = 4294967295;
+uint32_t Http2::accept_no_activity_timeout         = 120;
+uint32_t Http2::no_activity_timeout_in             = 120;
+uint32_t Http2::active_timeout_in                  = 0;
+uint32_t Http2::push_diary_size                    = 256;
+uint32_t Http2::zombie_timeout_in                  = 0;
+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;
+float Http2::min_avg_window_update                 = 2560.0;
+uint32_t Http2::con_slow_log_threshold             = 0;
+uint32_t Http2::stream_slow_log_threshold          = 0;
+uint32_t Http2::header_table_size_limit            = 65536;
+uint32_t Http2::write_buffer_block_size            = 262144;
+float Http2::write_size_threshold                  = 0.5;
+uint32_t Http2::write_time_threshold               = 100;
+uint32_t Http2::buffer_water_mark                  = 0;
 
 void
 Http2::init()
@@ -853,6 +856,7 @@ Http2::init()
   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_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");
@@ -923,6 +927,8 @@ Http2::init()
                      static_cast<int>(HTTP2_STAT_MAX_PRIORITY_FRAMES_PER_MINUTE_EXCEEDED), RecRawStatSyncSum);
   RecRegisterRawStat(http2_rsb, RECT_PROCESS, HTTP2_STAT_MAX_RST_STREAM_FRAMES_PER_MINUTE_EXCEEDED_NAME, RECD_INT, RECP_PERSISTENT,
                      static_cast<int>(HTTP2_STAT_MAX_RST_STREAM_FRAMES_PER_MINUTE_EXCEEDED), RecRawStatSyncSum);
+  RecRegisterRawStat(http2_rsb, RECT_PROCESS, HTTP2_STAT_MAX_CONTINUATION_FRAMES_PER_MINUTE_EXCEEDED_NAME, RECD_INT,
+                     RECP_PERSISTENT, static_cast<int>(HTTP2_STAT_MAX_CONTINUATION_FRAMES_PER_MINUTE_EXCEEDED), RecRawStatSyncSum);
   RecRegisterRawStat(http2_rsb, RECT_PROCESS, HTTP2_STAT_INSUFFICIENT_AVG_WINDOW_UPDATE_NAME, RECD_INT, RECP_PERSISTENT,
                      static_cast<int>(HTTP2_STAT_INSUFFICIENT_AVG_WINDOW_UPDATE), RecRawStatSyncSum);
   RecRegisterRawStat(http2_rsb, RECT_PROCESS, HTTP2_STAT_MAX_CONCURRENT_STREAMS_EXCEEDED_IN_NAME, RECD_INT, RECP_PERSISTENT,
diff --git a/proxy/http2/HTTP2.h b/proxy/http2/HTTP2.h
index 5847865a9a4..857b199c05d 100644
--- a/proxy/http2/HTTP2.h
+++ b/proxy/http2/HTTP2.h
@@ -105,6 +105,7 @@ enum {
   HTTP2_STAT_MAX_PING_FRAMES_PER_MINUTE_EXCEEDED,
   HTTP2_STAT_MAX_PRIORITY_FRAMES_PER_MINUTE_EXCEEDED,
   HTTP2_STAT_MAX_RST_STREAM_FRAMES_PER_MINUTE_EXCEEDED,
+  HTTP2_STAT_MAX_CONTINUATION_FRAMES_PER_MINUTE_EXCEEDED,
   HTTP2_STAT_INSUFFICIENT_AVG_WINDOW_UPDATE,
   HTTP2_STAT_MAX_CONCURRENT_STREAMS_EXCEEDED_IN,
   HTTP2_STAT_MAX_CONCURRENT_STREAMS_EXCEEDED_OUT,
@@ -404,6 +405,7 @@ class Http2
   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 float min_avg_window_update;
   static uint32_t con_slow_log_threshold;
   static uint32_t stream_slow_log_threshold;
diff --git a/proxy/http2/Http2ConnectionState.cc b/proxy/http2/Http2ConnectionState.cc
index b36e5c11793..b089048eb1d 100644
--- a/proxy/http2/Http2ConnectionState.cc
+++ b/proxy/http2/Http2ConnectionState.cc
@@ -924,6 +924,18 @@ rcv_continuation_frame(Http2ConnectionState &cstate, const Http2Frame &frame)
     }
   }
 
+  // Update CONTINUATION frame count per minute.
+  cstate.increment_received_continuation_frame_count();
+  // Close this connection if its CONTINUATION frame count exceeds a limit.
+  if (cstate.configured_max_continuation_frames_per_minute != 0 &&
+      cstate.get_received_continuation_frame_count() > cstate.configured_max_continuation_frames_per_minute) {
+    HTTP2_INCREMENT_THREAD_DYN_STAT(HTTP2_STAT_MAX_CONTINUATION_FRAMES_PER_MINUTE_EXCEEDED, this_ethread());
+    Http2StreamDebug(cstate.session, stream_id, "Observed too frequent CONTINUATION frames: %u frames within a last minute",
+                     cstate.get_received_continuation_frame_count());
+    return Http2Error(Http2ErrorClass::HTTP2_ERROR_CLASS_CONNECTION, Http2ErrorCode::HTTP2_ERROR_ENHANCE_YOUR_CALM,
+                      "reset too frequent CONTINUATION frames");
+  }
+
   uint32_t header_blocks_offset = stream->header_blocks_length;
   stream->header_blocks_length += payload_length;
 
@@ -1088,10 +1100,11 @@ Http2ConnectionState::init(Http2CommonSession *ssn)
     dependency_tree = new DependencyTree(Http2::max_concurrent_streams_in);
   }
 
-  configured_max_settings_frames_per_minute   = Http2::max_settings_frames_per_minute;
-  configured_max_ping_frames_per_minute       = Http2::max_ping_frames_per_minute;
-  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_settings_frames_per_minute     = Http2::max_settings_frames_per_minute;
+  configured_max_ping_frames_per_minute         = Http2::max_ping_frames_per_minute;
+  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;
   if (auto snis = dynamic_cast<TLSSNISupport *>(session->get_netvc()); 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();
@@ -1105,6 +1118,9 @@ Http2ConnectionState::init(Http2CommonSession *ssn)
     if (snis->hints_from_sni.http2_max_rst_stream_frames_per_minute.has_value()) {
       configured_max_rst_stream_frames_per_minute = snis->hints_from_sni.http2_max_rst_stream_frames_per_minute.value();
     }
+    if (snis->hints_from_sni.http2_max_continuation_frames_per_minute.has_value()) {
+      configured_max_continuation_frames_per_minute = snis->hints_from_sni.http2_max_continuation_frames_per_minute.value();
+    }
   }
 
   _cop = ActivityCop<Http2Stream>(this->mutex, &stream_list, 1);
@@ -2140,6 +2156,18 @@ Http2ConnectionState::get_received_rst_stream_frame_count()
   return this->_received_rst_stream_frame_counter.get_count();
 }
 
+void
+Http2ConnectionState::increment_received_continuation_frame_count()
+{
+  this->_received_continuation_frame_counter.increment();
+}
+
+uint32_t
+Http2ConnectionState::get_received_continuation_frame_count()
+{
+  return this->_received_continuation_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/proxy/http2/Http2ConnectionState.h b/proxy/http2/Http2ConnectionState.h
index 76d2e2a8e17..fff7763f2a1 100644
--- a/proxy/http2/Http2ConnectionState.h
+++ b/proxy/http2/Http2ConnectionState.h
@@ -102,10 +102,11 @@ class Http2ConnectionState : public Continuation
   Http2ConnectionSettings server_settings;
   Http2ConnectionSettings client_settings;
 
-  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_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;
 
   void init(Http2CommonSession *ssn);
   void send_connection_preface();
@@ -174,6 +175,8 @@ class Http2ConnectionState : public Continuation
   uint32_t get_received_priority_frame_count();
   void increment_received_rst_stream_frame_count();
   uint32_t get_received_rst_stream_frame_count();
+  void increment_received_continuation_frame_count();
+  uint32_t get_received_continuation_frame_count();
 
   ssize_t client_rwnd() const;
   Http2ErrorCode increment_client_rwnd(size_t amount);
@@ -220,6 +223,7 @@ class Http2ConnectionState : public Continuation
   Http2FrequencyCounter _received_ping_frame_counter;
   Http2FrequencyCounter _received_priority_frame_counter;
   Http2FrequencyCounter _received_rst_stream_frame_counter;
+  Http2FrequencyCounter _received_continuation_frame_counter;
 
   // NOTE: Id of stream which MUST receive CONTINUATION frame.
   //   - [RFC 7540] 6.2 HEADERS
