Skip to content

Commit

Permalink
Add an HTTP/2 related rate limiting (#10564)
Browse files Browse the repository at this point in the history
  • Loading branch information
maskit committed Oct 9, 2023
1 parent cbbd973 commit b28ad74
Show file tree
Hide file tree
Showing 13 changed files with 323 additions and 120 deletions.
7 changes: 7 additions & 0 deletions doc/admin-guide/files/records.config.en.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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:

Expand Down
190 changes: 103 additions & 87 deletions doc/admin-guide/files/sni.yaml.en.rst

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
68 changes: 68 additions & 0 deletions iocore/net/P_SNIActionPerformer.h
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,74 @@ class HTTP2BufferWaterMark : public ActionItem
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:
Expand Down
13 changes: 13 additions & 0 deletions iocore/net/SSLSNIConfig.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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));

Expand Down
4 changes: 4 additions & 0 deletions iocore/net/TLSSNISupport.h
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,10 @@ class TLSSNISupport

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:
Expand Down
16 changes: 16 additions & 0 deletions iocore/net/YamlSNIConfig.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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]) {
Expand Down
8 changes: 8 additions & 0 deletions iocore/net/YamlSNIConfig.h
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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;
Expand Down
2 changes: 2 additions & 0 deletions mgmt/RecordsConfig.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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}
Expand Down
64 changes: 35 additions & 29 deletions proxy/http2/HTTP2.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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()
Expand All @@ -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");
Expand Down Expand Up @@ -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,
Expand Down
2 changes: 2 additions & 0 deletions proxy/http2/HTTP2.h
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -402,6 +403,7 @@ class Http2
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;
Expand Down
Loading

0 comments on commit b28ad74

Please sign in to comment.