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 <[email protected]>
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