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

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


The following commit(s) were added to refs/heads/9.2.x by this push:
     new b28ad74f11 Add an HTTP/2 related rate limiting (#10564)
b28ad74f11 is described below

commit b28ad74f117307e8de206f1de70c3fa716f90682
Author: Masakazu Kitajo <mas...@apache.org>
AuthorDate: Tue Oct 10 00:02:18 2023 +0900

    Add an HTTP/2 related rate limiting (#10564)
---
 doc/admin-guide/files/records.config.en.rst        |   7 +
 doc/admin-guide/files/sni.yaml.en.rst              | 190 +++++++++++----------
 .../statistics/core/http-connection.en.rst         |   7 +
 iocore/net/P_SNIActionPerformer.h                  |  68 ++++++++
 iocore/net/SSLSNIConfig.cc                         |  13 ++
 iocore/net/TLSSNISupport.h                         |   4 +
 iocore/net/YamlSNIConfig.cc                        |  16 ++
 iocore/net/YamlSNIConfig.h                         |   8 +
 mgmt/RecordsConfig.cc                              |   2 +
 proxy/http2/HTTP2.cc                               |  64 +++----
 proxy/http2/HTTP2.h                                |   2 +
 proxy/http2/Http2ConnectionState.cc                |  54 +++++-
 proxy/http2/Http2ConnectionState.h                 |   8 +
 13 files changed, 323 insertions(+), 120 deletions(-)

diff --git a/doc/admin-guide/files/records.config.en.rst 
b/doc/admin-guide/files/records.config.en.rst
index 50d0dfd941..e6865b8c80 100644
--- a/doc/admin-guide/files/records.config.en.rst
+++ b/doc/admin-guide/files/records.config.en.rst
@@ -4272,6 +4272,13 @@ HTTP/2 Configuration
    This limit only will be enforced if 
:ts:cv:`proxy.config.http2.stream_priority_enabled`
    is set to 1.
 
+.. ts:cv:: CONFIG proxy.config.http2.max_rst_stream_frames_per_minute INT 14
+   :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
+   code of ENHANCE_YOUR_CALM.
+
 .. 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 5c11420142..8dc078eacb 100644
--- a/doc/admin-guide/files/sni.yaml.en.rst
+++ b/doc/admin-guide/files/sni.yaml.en.rst
@@ -52,122 +52,138 @@ for a more detailed description of HTTP/2 connection 
coalescing.
 .. _override-host-sni-policy:
 .. _override-h2-properties:
 
-========================= ========= 
========================================================================================
-Key                       Direction Meaning
-========================= ========= 
========================================================================================
-fqdn                      Both      Fully Qualified Domain Name. This item is 
used if the SNI value matches this.
+====================================== ========= 
========================================================================================
+Key                                    Direction Meaning
+====================================== ========= 
========================================================================================
+fqdn                                   Both      Fully Qualified Domain Name. 
This item is used if the SNI value matches this.
+
+ip_allow                               Inbound   Specify a list of client IP 
address, subnets, or ranges what are allowed to complete
+                                                 the connection. This list is 
comma separated. IPv4 and IPv6 addresses can be specified.
+                                                 Here is an example list: 
192.168.1.0/24,192.168.10.1-4. This would allow connections
+                                                 from clients in the 
19.168.1.0 network or in the range from 192.168.10.1 to 192.168.1.4.
+
+verify_server_policy                   Outbound  One of the values 
:code:`DISABLED`, :code:`PERMISSIVE`, or :code:`ENFORCED`.
+
+                                                 By default this is 
:ts:cv:`proxy.config.ssl.client.verify.server.policy`.
+                                                 This controls how |TS| 
evaluated the origin certificate.
 
-ip_allow                  Inbound   Specify a list of client IP address, 
subnets, or ranges what are allowed to complete
-                                    the connection. This list is comma 
separated. IPv4 and IPv6 addresses can be specified.
-                                    Here is an example list: 
192.168.1.0/24,192.168.10.1-4. This would allow connections
-                                    from clients in the 19.168.1.0 network or 
in the range from 192.168.10.1 to 192.168.1.4.
+verify_server_properties               Outbound  One of the values 
:code:`NONE`, :code:`SIGNATURE`, :code:`NAME`, and :code:`ALL`
 
-verify_server_policy      Outbound  One of the values :code:`DISABLED`, 
:code:`PERMISSIVE`, or :code:`ENFORCED`.
+                                                 By default this is 
:ts:cv:`proxy.config.ssl.client.verify.server.properties`.
+                                                 This controls what |TS| 
checks when evaluating the origin certificate.
 
-                                    By default this is 
:ts:cv:`proxy.config.ssl.client.verify.server.policy`.
-                                    This controls how |TS| evaluated the 
origin certificate.
+verify_client                          Outbound  One of the values 
:code:`NONE`, :code:`MODERATE`, or :code:`STRICT`.
+                                                 If ``NONE`` is specified, 
|TS| requests no certificate.  If ``MODERATE`` is specified
+                                                 |TS| will verify a 
certificate that is presented by the client, but it will not
+                                                 fail the TLS handshake if no 
certificate is presented.  If ``STRICT`` is specified
+                                                 the client must present a 
certificate during the TLS handshake.
 
-verify_server_properties  Outbound  One of the values :code:`NONE`, 
:code:`SIGNATURE`, :code:`NAME`, and :code:`ALL`
+                                                 By default this is 
:ts:cv:`proxy.config.ssl.client.certification_level`.
 
-                                    By default this is 
:ts:cv:`proxy.config.ssl.client.verify.server.properties`.
-                                    This controls what |TS| checks when 
evaluating the origin certificate.
+verify_client_ca_certs                 Both      Specifies an alternate set of 
certificate authority certs to use to verify the
+                                                 client cert.  The value must 
be either a file path, or a nested set of key /
+                                                 value pairs.  If the value is 
a file path, it must specify a file containing the
+                                                 CA certs.  Otherwise, there 
should be up to two nested pairs.  The possible keys
+                                                 are ``file`` and ``dir``.  
The value for ``file`` must be a file path for a file
+                                                 containing CA certs.  The 
value for ``dir`` must be a file path for an OpenSSL
+                                                 X509 hashed directory 
containing CA certs.  If a given file path does not being
+                                                 with ``/`` , it must be 
relative to the |TS| configuration directory.
+                                                 ``verify_client_ca_certs`` 
can only be used with capbilities provided by
+                                                 OpenSSL 1.0.2 or later.
 
-verify_client             Outbound  One of the values :code:`NONE`, 
:code:`MODERATE`, or :code:`STRICT`.
-                                    If ``NONE`` is specified, |TS| requests no 
certificate.  If ``MODERATE`` is specified
-                                    |TS| will verify a certificate that is 
presented by the client, but it will not
-                                    fail the TLS handshake if no certificate 
is presented.  If ``STRICT`` is specified
-                                    the client must present a certificate 
during the TLS handshake.
+host_sni_policy                        Inbound   One of the values 
:code:`DISABLED`, :code:`PERMISSIVE`, or :code:`ENFORCED`.
 
-                                    By default this is 
:ts:cv:`proxy.config.ssl.client.certification_level`.
+                                                 If not specified, the value 
of :ts:cv:`proxy.config.http.host_sni_policy` is used.
+                                                 This controls how policy 
impacting mismatches between host header and SNI values are
+                                                 dealt with.  For details 
about hos this configuration behaves, see the corresponding
+                                                 
:ts:cv:`proxy.config.http.host_sni_policy` :file:`records.config` documentation.
 
-verify_client_ca_certs    Both      Specifies an alternate set of certificate 
authority certs to use to verify the
-                                    client cert.  The value must be either a 
file path, or a nested set of key /
-                                    value pairs.  If the value is a file path, 
it must specify a file containing the
-                                    CA certs.  Otherwise, there should be up 
to two nested pairs.  The possible keys
-                                    are ``file`` and ``dir``.  The value for 
``file`` must be a file path for a file
-                                    containing CA certs.  The value for 
``dir`` must be a file path for an OpenSSL
-                                    X509 hashed directory containing CA certs. 
 If a given file path does not being
-                                    with ``/`` , it must be relative to the 
|TS| configuration directory.
-                                    ``verify_client_ca_certs`` can only be 
used with capbilities provided by
-                                    OpenSSL 1.0.2 or later.
+                                                 Note that this particular 
configuration will be inspected at the time the HTTP Host
+                                                 header field is processed. 
Further, this policy check will be keyed off of the Host header
+                                                 field value rather than the 
SNI in this :file:`sni.yaml` file. This is done because
+                                                 the Host header field is 
ultimately the resource that will be retrieved from the
+                                                 origin and the administrator 
will intend to guard this resource rather than the SNI,
+                                                 which a malicious user may 
alter to some other server value whose policies are more
+                                                 lenient than the host he is 
trying to access.
 
-host_sni_policy           Inbound   One of the values :code:`DISABLED`, 
:code:`PERMISSIVE`, or :code:`ENFORCED`.
+valid_tls_versions_in                  Inbound   This specifies the list of 
TLS protocols that will be offered to user agents during
+                                                 the TLS negotiation.  This 
replaces the global settings in
+                                                 
:ts:cv:`proxy.config.ssl.TLSv1`, :ts:cv:`proxy.config.ssl.TLSv1_1`,
+                                                 
:ts:cv:`proxy.config.ssl.TLSv1_2`, and :ts:cv:`proxy.config.ssl.TLSv1_3`. The 
potential
+                                                 values are TLSv1, TLSv1_1, 
TLSv1_2, and TLSv1_3.  You must list all protocols that |TS|
+                                                 should offer to the client 
when using this key.  This key is only valid for OpenSSL
+                                                 1.1.0 and later and 
BoringSSL. Older versions of OpenSSL do not provide a hook early enough to 
update
+                                                 the SSL object.  It is a 
syntax error for |TS| built against earlier versions.
 
-                                    If not specified, the value of 
:ts:cv:`proxy.config.http.host_sni_policy` is used.
-                                    This controls how policy impacting 
mismatches between host header and SNI values are
-                                    dealt with.  For details about hos this 
configuration behaves, see the corresponding
-                                    :ts:cv:`proxy.config.http.host_sni_policy` 
:file:`records.config` documentation.
+client_cert                            Outbound  The file containing the 
client certificate to use for the outbound connection.
 
-                                    Note that this particular configuration 
will be inspected at the time the HTTP Host
-                                    header field is processed. Further, this 
policy check will be keyed off of the Host header
-                                    field value rather than the SNI in this 
:file:`sni.yaml` file. This is done because
-                                    the Host header field is ultimately the 
resource that will be retrieved from the
-                                    origin and the administrator will intend 
to guard this resource rather than the SNI,
-                                    which a malicious user may alter to some 
other server value whose policies are more
-                                    lenient than the host he is trying to 
access.
+                                                 If this is relative, it is 
relative to the path in
+                                                 
:ts:cv:`proxy.config.ssl.client.cert.path`. If not set
+                                                 
:ts:cv:`proxy.config.ssl.client.cert.filename` is used.
 
-valid_tls_versions_in     Inbound   This specifies the list of TLS protocols 
that will be offered to user agents during
-                                    the TLS negotiation.  This replaces the 
global settings in
-                                    :ts:cv:`proxy.config.ssl.TLSv1`, 
:ts:cv:`proxy.config.ssl.TLSv1_1`,
-                                    :ts:cv:`proxy.config.ssl.TLSv1_2`, and 
:ts:cv:`proxy.config.ssl.TLSv1_3`. The potential
-                                    values are TLSv1, TLSv1_1, TLSv1_2, and 
TLSv1_3.  You must list all protocols that |TS|
-                                    should offer to the client when using this 
key.  This key is only valid for OpenSSL
-                                    1.1.0 and later and BoringSSL. Older 
versions of OpenSSL do not provide a hook early enough to update
-                                    the SSL object.  It is a syntax error for 
|TS| built against earlier versions.
+client_key                             Outbound  The file containing the 
client private key that corresponds to the certificate
+                                                 for the outbound connection.
 
-client_cert               Outbound  The file containing the client certificate 
to use for the outbound connection.
+                                                 If this is relative, it is 
relative to the path in
+                                                 
:ts:cv:`proxy.config.ssl.client.private_key.path`. If not set,
+                                                 |TS| tries to use a private 
key in client_cert.  Otherwise,
+                                                 
:ts:cv:`proxy.config.ssl.client.private_key.filename` is used.
 
-                                    If this is relative, it is relative to the 
path in
-                                    
:ts:cv:`proxy.config.ssl.client.cert.path`. If not set
-                                    
:ts:cv:`proxy.config.ssl.client.cert.filename` is used.
+client_sni_policy                      Outbound  Policy of SNI on outbound 
connection.
 
-client_key                Outbound  The file containing the client private key 
that corresponds to the certificate
-                                    for the outbound connection.
+                                                 If not specified, the value 
of :ts:cv:`proxy.config.ssl.client.sni_policy` is used.
 
-                                    If this is relative, it is relative to the 
path in
-                                    
:ts:cv:`proxy.config.ssl.client.private_key.path`. If not set,
-                                    |TS| tries to use a private key in 
client_cert.  Otherwise,
-                                    
:ts:cv:`proxy.config.ssl.client.private_key.filename` is used.
+http2                                  Inbound   Indicates whether the H2 
protocol should be added to or removed from the
+                                                 protocol negotiation list.  
The valid values are :code:`on` or :code:`off`.
 
-client_sni_policy         Outbound  Policy of SNI on outbound connection.
+http2_buffer_water_mark                Inbound   Specifies the high water mark 
for all HTTP/2 frames on an outoging connection.
+                                                 By default this is 
:ts:cv:`proxy.config.http2.default_buffer_water_mark`.
+                                                 NOTE: Connection coalescing 
may prevent this taking effect.
 
-                                    If not specified, the value of 
:ts:cv:`proxy.config.ssl.client.sni_policy` is used.
+http2_max_settings_frames_per_minute   Inbound   Specifies how many SETTINGS 
frames |TS| receives per minute at maximum.
+                                                 By default this is 
:ts:cv:`proxy.config.http2.max_settings_frames_per_minute`.
+                                                 NOTE: Connection coalescing 
may prevent this from taking effect.
 
-http2                     Inbound   Indicates whether the H2 protocol should 
be added to or removed from the
-                                    protocol negotiation list.  The valid 
values are :code:`on` or :code:`off`.
+http2_max_ping_frames_per_minute       Inbound   Specifies how many PING 
frames |TS| receives per minute at maximum.
+                                                 By default this is 
:ts:cv:`proxy.config.http2.max_settings_frames_per_minute`.
+                                                 NOTE: Connection coalescing 
may prevent this from taking effect.
 
-http2_buffer_water_mark   Inbound   Specifies the high water mark for all 
HTTP/2 frames on an outoging connection.
-                                    By default this is 
:ts:cv:`proxy.config.http2.default_buffer_water_mark`.
-                                    NOTE: Connection coalescing may prevent 
this taking effect.
+http2_max_priority_frames_per_minute   Inbound   Specifies how many PRIORITY 
frames |TS| receives per minute at maximum.
+                                                 By default this is 
:ts:cv:`proxy.config.http2.max_settings_frames_per_minute`.
+                                                 NOTE: Connection coalescing 
may prevent this from taking effect.
 
-disable_h2                Inbound   Deprecated for the more general h2 
setting.  Setting disable_h2
-                                    to :code:`true` is the same as setting 
http2 to :code:`on`.
+http2_max_rst_stream_frames_per_minute Inbound   Specifies how many RST_STREAM 
frames |TS| receives per minute at maximum.
+                                                 By default this is 
:ts:cv:`proxy.config.http2.max_settings_frames_per_minute`.
+                                                 NOTE: Connection coalescing 
may prevent this from taking effect.
 
-tunnel_route              Inbound   Destination as an FQDN and port, separated 
by a colon ``:``.
-                                    Match group number can be specified by 
``$N`` where N should refer to a specified group
-                                    in the FQDN, ``tunnel_route: $1.domain``.
+disable_h2                             Inbound   Deprecated for the more 
general h2 setting.  Setting disable_h2
+                                                 to :code:`true` is the same 
as setting http2 to :code:`on`.
 
-                                    This will forward all traffic to the 
specified destination without first terminating
-                                    the incoming TLS connection.
+tunnel_route                           Inbound   Destination as an FQDN and 
port, separated by a colon ``:``.
+                                                 Match group number can be 
specified by ``$N`` where N should refer to a specified group
+                                                 in the FQDN, ``tunnel_route: 
$1.domain``.
 
-forward_route             Inbound   Destination as an FQDN and port, separated 
by a colon ``:``.
+                                                 This will forward all traffic 
to the specified destination without first terminating
+                                                 the incoming TLS connection.
 
-                                    This is similar to tunnel_route, but it 
terminates the TLS connection and forwards the
-                                    decrypted traffic. |TS| will not interpret 
the decrypted data, so the contents do not
-                                    need to be HTTP.
+forward_route                          Inbound   Destination as an FQDN and 
port, separated by a colon ``:``.
 
-partial_blind_route       Inbound   Destination as an FQDN and port, separated 
by a colon ``:``.
+                                                 This is similar to 
tunnel_route, but it terminates the TLS connection and forwards the
+                                                 decrypted traffic. |TS| will 
not interpret the decrypted data, so the contents do not
+                                                 need to be HTTP.
 
-                                    This is similar to forward_route in that 
|TS| terminates the incoming TLS connection.
-                                    In addition partial_blind_route creates a 
new TLS connection to the specified origin.
-                                    It does not interpret the decrypted data 
before passing it to the origin TLS
-                                    connection, so the contents do not need to 
be HTTP.
+partial_blind_route                    Inbound   Destination as an FQDN and 
port, separated by a colon ``:``.
 
-tunnel_alpn               Inbound   List of ALPN Protocol Ids for Partial 
Blind Tunnel.
+                                                 This is similar to 
forward_route in that |TS| terminates the incoming TLS connection.
+                                                 In addition 
partial_blind_route creates a new TLS connection to the specified origin.
+                                                 It does not interpret the 
decrypted data before passing it to the origin TLS
+                                                 connection, so the contents 
do not need to be HTTP.
 
-                                    ATS negotiates application protocol with 
the client on behalf of the origin server.
-                                    This only works with 
``partial_blind_route``.
-========================= ========= 
========================================================================================
+tunnel_alpn                            Inbound   List of ALPN Protocol Ids for 
Partial Blind Tunnel.
+
+                                                 ATS negotiates application 
protocol with the client on behalf of the origin server.
+                                                 This only works with 
``partial_blind_route``.
+====================================== ========= 
========================================================================================
 
 Pre-warming TLS Tunnel
 ----------------------
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 e00885c294..b22da8e1c6 100644
--- a/doc/admin-guide/monitoring/statistics/core/http-connection.en.rst
+++ b/doc/admin-guide/monitoring/statistics/core/http-connection.en.rst
@@ -260,6 +260,13 @@ HTTP/2
    maximum allowed number of priority frames per minute limit which is 
configured by
    :ts:cv:`proxy.config.http2.max_priority_frames_per_minute`.
 
+.. 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
+   :ts:cv:`proxy.config.http2.max_rst_stream_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 32d803dea0..e223ac7d0b 100644
--- a/iocore/net/P_SNIActionPerformer.h
+++ b/iocore/net/P_SNIActionPerformer.h
@@ -118,6 +118,74 @@ private:
   int value = -1;
 };
 
+class HTTP2MaxSettingsFramesPerMinute : public ActionItem
+{
+public:
+  HTTP2MaxSettingsFramesPerMinute(int value) : value(value) {}
+  ~HTTP2MaxSettingsFramesPerMinute() override {}
+
+  int
+  SNIAction(TLSSNISupport *snis, const Context &ctx) const override
+  {
+    snis->hints_from_sni.http2_max_settings_frames_per_minute = value;
+    return SSL_TLSEXT_ERR_OK;
+  }
+
+private:
+  int value = -1;
+};
+
+class HTTP2MaxPingFramesPerMinute : public ActionItem
+{
+public:
+  HTTP2MaxPingFramesPerMinute(int value) : value(value) {}
+  ~HTTP2MaxPingFramesPerMinute() override {}
+
+  int
+  SNIAction(TLSSNISupport *snis, const Context &ctx) const override
+  {
+    snis->hints_from_sni.http2_max_ping_frames_per_minute = value;
+    return SSL_TLSEXT_ERR_OK;
+  }
+
+private:
+  int value = -1;
+};
+
+class HTTP2MaxPriorityFramesPerMinute : public ActionItem
+{
+public:
+  HTTP2MaxPriorityFramesPerMinute(int value) : value(value) {}
+  ~HTTP2MaxPriorityFramesPerMinute() override {}
+
+  int
+  SNIAction(TLSSNISupport *snis, const Context &ctx) const override
+  {
+    snis->hints_from_sni.http2_max_priority_frames_per_minute = value;
+    return SSL_TLSEXT_ERR_OK;
+  }
+
+private:
+  int value = -1;
+};
+
+class HTTP2MaxRstStreamFramesPerMinute : public ActionItem
+{
+public:
+  HTTP2MaxRstStreamFramesPerMinute(int value) : value(value) {}
+  ~HTTP2MaxRstStreamFramesPerMinute() override {}
+
+  int
+  SNIAction(TLSSNISupport *snis, const Context &ctx) const override
+  {
+    snis->hints_from_sni.http2_max_rst_stream_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 0854251259..a7071013f6 100644
--- a/iocore/net/SSLSNIConfig.cc
+++ b/iocore/net/SSLSNIConfig.cc
@@ -138,6 +138,19 @@ SNIConfigParams::load_sni_config()
     if (item.http2_buffer_water_mark.has_value()) {
       
ai->actions.push_back(std::make_unique<HTTP2BufferWaterMark>(item.http2_buffer_water_mark.value()));
     }
+    if (item.http2_max_settings_frames_per_minute.has_value()) {
+      
ai->actions.push_back(std::make_unique<HTTP2MaxSettingsFramesPerMinute>(item.http2_max_settings_frames_per_minute.value()));
+    }
+    if (item.http2_max_ping_frames_per_minute.has_value()) {
+      
ai->actions.push_back(std::make_unique<HTTP2MaxPingFramesPerMinute>(item.http2_max_ping_frames_per_minute.value()));
+    }
+    if (item.http2_max_priority_frames_per_minute.has_value()) {
+      
ai->actions.push_back(std::make_unique<HTTP2MaxPriorityFramesPerMinute>(item.http2_max_priority_frames_per_minute.value()));
+    }
+    if (item.http2_max_rst_stream_frames_per_minute.has_value()) {
+      ai->actions.push_back(
+        
std::make_unique<HTTP2MaxRstStreamFramesPerMinute>(item.http2_max_rst_stream_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 2f5d6467c2..ba2d13e930 100644
--- a/iocore/net/TLSSNISupport.h
+++ b/iocore/net/TLSSNISupport.h
@@ -52,6 +52,10 @@ public:
 
   struct HintsFromSNI {
     std::optional<uint32_t> http2_buffer_water_mark;
+    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;
   } hints_from_sni;
 
 protected:
diff --git a/iocore/net/YamlSNIConfig.cc b/iocore/net/YamlSNIConfig.cc
index 3f90fe99ce..9a777b806f 100644
--- a/iocore/net/YamlSNIConfig.cc
+++ b/iocore/net/YamlSNIConfig.cc
@@ -144,6 +144,10 @@ std::set<std::string> valid_sni_config_keys = {TS_fqdn,
                                                TS_client_sni_policy,
                                                TS_http2,
                                                TS_http2_buffer_water_mark,
+                                               
TS_http2_max_settings_frames_per_minute,
+                                               
TS_http2_max_ping_frames_per_minute,
+                                               
TS_http2_max_priority_frames_per_minute,
+                                               
TS_http2_max_rst_stream_frames_per_minute,
                                                TS_ip_allow,
 #if TS_USE_HELLO_CB || defined(OPENSSL_IS_BORINGSSL)
                                                TS_valid_tls_versions_in,
@@ -177,6 +181,18 @@ template <> struct convert<YamlSNIConfig::Item> {
     if (node[TS_http2_buffer_water_mark]) {
       item.http2_buffer_water_mark = 
node[TS_http2_buffer_water_mark].as<int>();
     }
+    if (node[TS_http2_max_settings_frames_per_minute]) {
+      item.http2_max_settings_frames_per_minute = 
node[TS_http2_max_settings_frames_per_minute].as<int>();
+    }
+    if (node[TS_http2_max_ping_frames_per_minute]) {
+      item.http2_max_ping_frames_per_minute = 
node[TS_http2_max_ping_frames_per_minute].as<int>();
+    }
+    if (node[TS_http2_max_priority_frames_per_minute]) {
+      item.http2_max_priority_frames_per_minute = 
node[TS_http2_max_priority_frames_per_minute].as<int>();
+    }
+    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>();
+    }
 
     // enum
     if (node[TS_verify_client]) {
diff --git a/iocore/net/YamlSNIConfig.h b/iocore/net/YamlSNIConfig.h
index dbb79ba298..b297bd5c16 100644
--- a/iocore/net/YamlSNIConfig.h
+++ b/iocore/net/YamlSNIConfig.h
@@ -56,6 +56,10 @@ TSDECL(ip_allow);
 TSDECL(valid_tls_versions_in);
 TSDECL(http2);
 TSDECL(http2_buffer_water_mark);
+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(host_sni_policy);
 #undef TSDECL
 
@@ -86,6 +90,10 @@ struct YamlSNIConfig {
     unsigned long protocol_mask;
     std::vector<int> tunnel_alpn{};
     std::optional<int> http2_buffer_water_mark;
+    std::optional<int> http2_max_settings_frames_per_minute;
+    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;
 
     bool tunnel_prewarm_srv                  = false;
     uint32_t tunnel_prewarm_min              = 0;
diff --git a/mgmt/RecordsConfig.cc b/mgmt/RecordsConfig.cc
index b547ece86e..433ff381b8 100644
--- a/mgmt/RecordsConfig.cc
+++ b/mgmt/RecordsConfig.cc
@@ -1391,6 +1391,8 @@ static const RecordElement RecordsConfig[] =
   ,
   {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.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 5b263bbe75..04813d2212 100644
--- a/proxy/http2/HTTP2.cc
+++ b/proxy/http2/HTTP2.cc
@@ -83,6 +83,8 @@ static const char *const 
HTTP2_STAT_MAX_PING_FRAMES_PER_MINUTE_EXCEEDED_NAME =
   "proxy.process.http2.max_ping_frames_per_minute_exceeded";
 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_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";
@@ -796,35 +798,36 @@ 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;
-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;
+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()
@@ -849,6 +852,7 @@ Http2::init()
   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_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");
@@ -917,6 +921,8 @@ Http2::init()
                      
static_cast<int>(HTTP2_STAT_MAX_PING_FRAMES_PER_MINUTE_EXCEEDED), 
RecRawStatSyncSum);
   RecRegisterRawStat(http2_rsb, RECT_PROCESS, 
HTTP2_STAT_MAX_PRIORITY_FRAMES_PER_MINUTE_EXCEEDED_NAME, RECD_INT, 
RECP_PERSISTENT,
                      
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_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 dbc46744bc..5847865a9a 100644
--- a/proxy/http2/HTTP2.h
+++ b/proxy/http2/HTTP2.h
@@ -104,6 +104,7 @@ enum {
   HTTP2_STAT_MAX_SETTINGS_FRAMES_PER_MINUTE_EXCEEDED,
   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_INSUFFICIENT_AVG_WINDOW_UPDATE,
   HTTP2_STAT_MAX_CONCURRENT_STREAMS_EXCEEDED_IN,
   HTTP2_STAT_MAX_CONCURRENT_STREAMS_EXCEEDED_OUT,
@@ -402,6 +403,7 @@ public:
   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 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 dae2e97c88..888fdc39b5 100644
--- a/proxy/http2/Http2ConnectionState.cc
+++ b/proxy/http2/Http2ConnectionState.cc
@@ -28,6 +28,7 @@
 #include "Http2Frame.h"
 #include "Http2DebugNames.h"
 #include "HttpDebugNames.h"
+#include "TLSSNISupport.h"
 
 #include "tscpp/util/PostScript.h"
 #include "tscpp/util/LocalBuffer.h"
@@ -465,8 +466,8 @@ rcv_priority_frame(Http2ConnectionState &cstate, const 
Http2Frame &frame)
   // Update PRIORITY frame count per minute
   cstate.increment_received_priority_frame_count();
   // Close this connection if its priority frame count received exceeds a limit
-  if (Http2::max_priority_frames_per_minute != 0 &&
-      cstate.get_received_priority_frame_count() > 
Http2::max_priority_frames_per_minute) {
+  if (cstate.configured_max_priority_frames_per_minute != 0 &&
+      cstate.get_received_priority_frame_count() > 
cstate.configured_max_priority_frames_per_minute) {
     
HTTP2_INCREMENT_THREAD_DYN_STAT(HTTP2_STAT_MAX_PRIORITY_FRAMES_PER_MINUTE_EXCEEDED,
 this_ethread());
     Http2StreamDebug(cstate.session, stream_id, "Observed too frequent 
priority changes: %u priority changes within a last minute",
                      cstate.get_received_priority_frame_count());
@@ -537,6 +538,18 @@ rcv_rst_stream_frame(Http2ConnectionState &cstate, const 
Http2Frame &frame)
                       "reset frame wrong length");
   }
 
+  // Update RST_STREAM frame count per minute
+  cstate.increment_received_rst_stream_frame_count();
+  // Close this connection if its RST_STREAM frame count exceeds a limit
+  if (cstate.configured_max_rst_stream_frames_per_minute != 0 &&
+      cstate.get_received_rst_stream_frame_count() > 
cstate.configured_max_rst_stream_frames_per_minute) {
+    
HTTP2_INCREMENT_THREAD_DYN_STAT(HTTP2_STAT_MAX_RST_STREAM_FRAMES_PER_MINUTE_EXCEEDED,
 this_ethread());
+    Http2StreamDebug(cstate.session, stream_id, "Observed too frequent 
RST_STREAM frames: %u frames within a last minute",
+                     cstate.get_received_settings_frame_count());
+    return Http2Error(Http2ErrorClass::HTTP2_ERROR_CLASS_CONNECTION, 
Http2ErrorCode::HTTP2_ERROR_ENHANCE_YOUR_CALM,
+                      "reset too frequent RST_STREAM frames");
+  }
+
   if (stream == nullptr || !stream->change_state(frame.header().type, 
frame.header().flags)) {
     // If a RST_STREAM frame identifying an idle stream is received, the
     // recipient MUST treat this as a connection error of type PROTOCOL_ERROR.
@@ -578,7 +591,8 @@ rcv_settings_frame(Http2ConnectionState &cstate, const 
Http2Frame &frame)
   // Update SETTIGNS frame count per minute
   cstate.increment_received_settings_frame_count();
   // Close this connection if its SETTINGS frame count exceeds a limit
-  if (cstate.get_received_settings_frame_count() > 
Http2::max_settings_frames_per_minute) {
+  if (cstate.configured_max_settings_frames_per_minute != 0 &&
+      cstate.get_received_settings_frame_count() > 
cstate.configured_max_settings_frames_per_minute) {
     
HTTP2_INCREMENT_THREAD_DYN_STAT(HTTP2_STAT_MAX_SETTINGS_FRAMES_PER_MINUTE_EXCEEDED,
 this_ethread());
     Http2StreamDebug(cstate.session, stream_id, "Observed too frequent 
SETTINGS frames: %u frames within a last minute",
                      cstate.get_received_settings_frame_count());
@@ -712,7 +726,8 @@ rcv_ping_frame(Http2ConnectionState &cstate, const 
Http2Frame &frame)
   // Update PING frame count per minute
   cstate.increment_received_ping_frame_count();
   // Close this connection if its ping count received exceeds a limit
-  if (cstate.get_received_ping_frame_count() > 
Http2::max_ping_frames_per_minute) {
+  if (cstate.configured_max_ping_frames_per_minute != 0 &&
+      cstate.get_received_ping_frame_count() > 
cstate.configured_max_ping_frames_per_minute) {
     
HTTP2_INCREMENT_THREAD_DYN_STAT(HTTP2_STAT_MAX_PING_FRAMES_PER_MINUTE_EXCEEDED, 
this_ethread());
     Http2StreamDebug(cstate.session, stream_id, "Observed too frequent PING 
frames: %u PING frames within a last minute",
                      cstate.get_received_ping_frame_count());
@@ -1073,6 +1088,25 @@ 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;
+  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();
+    }
+    if (snis->hints_from_sni.http2_max_ping_frames_per_minute.has_value()) {
+      configured_max_ping_frames_per_minute = 
snis->hints_from_sni.http2_max_ping_frames_per_minute.value();
+    }
+    if (snis->hints_from_sni.http2_max_priority_frames_per_minute.has_value()) 
{
+      configured_max_priority_frames_per_minute = 
snis->hints_from_sni.http2_max_priority_frames_per_minute.value();
+    }
+    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();
+    }
+  }
+
   _cop = ActivityCop<Http2Stream>(this->mutex, &stream_list, 1);
   _cop.start();
 }
@@ -2094,6 +2128,18 @@ Http2ConnectionState::get_received_priority_frame_count()
   return this->_received_priority_frame_counter.get_count();
 }
 
+void
+Http2ConnectionState::increment_received_rst_stream_frame_count()
+{
+  this->_received_rst_stream_frame_counter.increment();
+}
+
+uint32_t
+Http2ConnectionState::get_received_rst_stream_frame_count()
+{
+  return this->_received_rst_stream_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 ca65a90846..76d2e2a8e1 100644
--- a/proxy/http2/Http2ConnectionState.h
+++ b/proxy/http2/Http2ConnectionState.h
@@ -102,6 +102,11 @@ public:
   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;
+
   void init(Http2CommonSession *ssn);
   void send_connection_preface();
   void destroy();
@@ -167,6 +172,8 @@ public:
   uint32_t get_received_ping_frame_count();
   void increment_received_priority_frame_count();
   uint32_t get_received_priority_frame_count();
+  void increment_received_rst_stream_frame_count();
+  uint32_t get_received_rst_stream_frame_count();
 
   ssize_t client_rwnd() const;
   Http2ErrorCode increment_client_rwnd(size_t amount);
@@ -212,6 +219,7 @@ private:
   Http2FrequencyCounter _received_settings_frame_counter;
   Http2FrequencyCounter _received_ping_frame_counter;
   Http2FrequencyCounter _received_priority_frame_counter;
+  Http2FrequencyCounter _received_rst_stream_frame_counter;
 
   // NOTE: Id of stream which MUST receive CONTINUATION frame.
   //   - [RFC 7540] 6.2 HEADERS


Reply via email to