Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add proxy.config.http2.max_continuation_frames_per_minute #11206

Merged
merged 1 commit into from
Apr 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
11 changes: 9 additions & 2 deletions doc/admin-guide/files/records.config.en.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4287,8 +4287,15 @@ HTTP/2 Configuration
.. 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
Specifies how many RST_STREAM frames |TS| receives per minute at maximum.
Clients exceeding this limit will be immediately disconnected with an error
code of ENHANCE_YOUR_CALM.

.. ts:cv:: CONFIG proxy.config.http2.max_continuation_frames_per_minute INT 120
:reloadable:

Specifies how many CONTINUATION frames |TS| receives per minute at maximum.
Clients exceeding this limit will be immediately disconnected with an error
code of ENHANCE_YOUR_CALM.

.. ts:cv:: CONFIG proxy.config.http2.min_avg_window_update FLOAT 2560.0
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -263,10 +263,17 @@ HTTP/2
.. ts:stat:: global proxy.process.http2.max_rst_stream_frames_per_minute_exceeded integer
:type: counter

Represents the total number of closed HTTP/2 connections for exceeding the
maximum allowed number of rst_stream frames per minute limit which is configured by
Represents the total number of HTTP/2 connections closed for exceeding the
maximum allowed number of ``RST_STREAM`` frames per minute limit which is configured by
:ts:cv:`proxy.config.http2.max_rst_stream_frames_per_minute`.

.. ts:stat:: global proxy.process.http2.max_continuation_frames_per_minute_exceeded integer
:type: counter

Represents the total number of HTTP/2 connections closed for exceeding the
maximum allowed number of ``CONTINUATION`` frames per minute limit which is
configured by :ts:cv:`proxy.config.http2.max_continuation_frames_per_minute`.

.. ts:stat:: global proxy.process.http2.insufficient_avg_window_update integer
:type: counter

Expand Down
17 changes: 17 additions & 0 deletions iocore/net/P_SNIActionPerformer.h
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,23 @@ class HTTP2MaxRstStreamFramesPerMinute : public ActionItem
int value = -1;
};

class HTTP2MaxContinuationFramesPerMinute : public ActionItem
{
public:
HTTP2MaxContinuationFramesPerMinute(int value) : value(value) {}
~HTTP2MaxContinuationFramesPerMinute() override {}

int
SNIAction(TLSSNISupport *snis, const Context &ctx) const override
{
snis->hints_from_sni.http2_max_continuation_frames_per_minute = value;
return SSL_TLSEXT_ERR_OK;
}

private:
int value = -1;
};

class TunnelDestination : public ActionItem
{
public:
Expand Down
4 changes: 4 additions & 0 deletions iocore/net/SSLSNIConfig.cc
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,10 @@ SNIConfigParams::load_sni_config()
ai->actions.push_back(
std::make_unique<HTTP2MaxRstStreamFramesPerMinute>(item.http2_max_rst_stream_frames_per_minute.value()));
}
if (item.http2_max_continuation_frames_per_minute.has_value()) {
ai->actions.push_back(
std::make_unique<HTTP2MaxContinuationFramesPerMinute>(item.http2_max_continuation_frames_per_minute.value()));
}

ai->actions.push_back(std::make_unique<SNI_IpAllow>(item.ip_allow, item.fqdn));

Expand Down
1 change: 1 addition & 0 deletions iocore/net/TLSSNISupport.h
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ class TLSSNISupport
std::optional<uint32_t> http2_max_ping_frames_per_minute;
std::optional<uint32_t> http2_max_priority_frames_per_minute;
std::optional<uint32_t> http2_max_rst_stream_frames_per_minute;
std::optional<uint32_t> http2_max_continuation_frames_per_minute;
} hints_from_sni;

protected:
Expand Down
4 changes: 4 additions & 0 deletions iocore/net/YamlSNIConfig.cc
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,7 @@ std::set<std::string> valid_sni_config_keys = {TS_fqdn,
TS_http2_max_ping_frames_per_minute,
TS_http2_max_priority_frames_per_minute,
TS_http2_max_rst_stream_frames_per_minute,
TS_http2_max_continuation_frames_per_minute,
TS_ip_allow,
#if TS_USE_HELLO_CB || defined(OPENSSL_IS_BORINGSSL)
TS_valid_tls_versions_in,
Expand Down Expand Up @@ -193,6 +194,9 @@ template <> struct convert<YamlSNIConfig::Item> {
if (node[TS_http2_max_rst_stream_frames_per_minute]) {
item.http2_max_rst_stream_frames_per_minute = node[TS_http2_max_rst_stream_frames_per_minute].as<int>();
}
if (node[TS_http2_max_continuation_frames_per_minute]) {
item.http2_max_continuation_frames_per_minute = node[TS_http2_max_continuation_frames_per_minute].as<int>();
}

// enum
if (node[TS_verify_client]) {
Expand Down
2 changes: 2 additions & 0 deletions iocore/net/YamlSNIConfig.h
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ TSDECL(http2_max_settings_frames_per_minute);
TSDECL(http2_max_ping_frames_per_minute);
TSDECL(http2_max_priority_frames_per_minute);
TSDECL(http2_max_rst_stream_frames_per_minute);
TSDECL(http2_max_continuation_frames_per_minute);
TSDECL(host_sni_policy);
#undef TSDECL

Expand Down Expand Up @@ -94,6 +95,7 @@ struct YamlSNIConfig {
std::optional<int> http2_max_ping_frames_per_minute;
std::optional<int> http2_max_priority_frames_per_minute;
std::optional<int> http2_max_rst_stream_frames_per_minute;
std::optional<int> http2_max_continuation_frames_per_minute;

bool tunnel_prewarm_srv = false;
uint32_t tunnel_prewarm_min = 0;
Expand Down
2 changes: 2 additions & 0 deletions mgmt/RecordsConfig.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1395,6 +1395,8 @@ static const RecordElement RecordsConfig[] =
,
{RECT_CONFIG, "proxy.config.http2.max_rst_stream_frames_per_minute", RECD_INT, "200", RECU_DYNAMIC, RR_NULL, RECC_STR, "^[0-9]+$", RECA_NULL}
,
{RECT_CONFIG, "proxy.config.http2.max_continuation_frames_per_minute", RECD_INT, "120", RECU_DYNAMIC, RR_NULL, RECC_STR, "^[0-9]+$", RECA_NULL}
,
{RECT_CONFIG, "proxy.config.http2.min_avg_window_update", RECD_FLOAT, "2560.0", RECU_DYNAMIC, RR_NULL, RECC_NULL, nullptr, RECA_NULL}
,
{RECT_CONFIG, "proxy.config.http2.header_table_size_limit", RECD_INT, "65536", RECU_DYNAMIC, RR_NULL, RECC_STR, "^[0-9]+$", RECA_NULL}
Expand Down
66 changes: 36 additions & 30 deletions proxy/http2/HTTP2.cc
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,8 @@ static const char *const HTTP2_STAT_MAX_PRIORITY_FRAMES_PER_MINUTE_EXCEEDED_NAME
"proxy.process.http2.max_priority_frames_per_minute_exceeded";
static const char *const HTTP2_STAT_MAX_RST_STREAM_FRAMES_PER_MINUTE_EXCEEDED_NAME =
"proxy.process.http2.max_rst_stream_frames_per_minute_exceeded";
static const char *const HTTP2_STAT_MAX_CONTINUATION_FRAMES_PER_MINUTE_EXCEEDED_NAME =
"proxy.process.http2.max_continuation_frames_per_minute_exceeded";
static const char *const HTTP2_STAT_INSUFFICIENT_AVG_WINDOW_UPDATE_NAME = "proxy.process.http2.insufficient_avg_window_update";
static const char *const HTTP2_STAT_MAX_CONCURRENT_STREAMS_EXCEEDED_IN_NAME =
"proxy.process.http2.max_concurrent_streams_exceeded_in";
Expand Down Expand Up @@ -798,36 +800,37 @@ http2_decode_header_blocks(HTTPHdr *hdr, const uint8_t *buf_start, const uint32_
}

// Initialize this subsystem with librecords configs (for now)
uint32_t Http2::max_concurrent_streams_in = 100;
uint32_t Http2::min_concurrent_streams_in = 10;
uint32_t Http2::max_active_streams_in = 0;
bool Http2::throttling = false;
uint32_t Http2::stream_priority_enabled = 0;
uint32_t Http2::initial_window_size = 65535;
uint32_t Http2::max_frame_size = 16384;
uint32_t Http2::header_table_size = 4096;
uint32_t Http2::max_header_list_size = 4294967295;
uint32_t Http2::accept_no_activity_timeout = 120;
uint32_t Http2::no_activity_timeout_in = 120;
uint32_t Http2::active_timeout_in = 0;
uint32_t Http2::push_diary_size = 256;
uint32_t Http2::zombie_timeout_in = 0;
float Http2::stream_error_rate_threshold = 0.1;
uint32_t Http2::stream_error_sampling_threshold = 10;
uint32_t Http2::max_settings_per_frame = 7;
uint32_t Http2::max_settings_per_minute = 14;
uint32_t Http2::max_settings_frames_per_minute = 14;
uint32_t Http2::max_ping_frames_per_minute = 60;
uint32_t Http2::max_priority_frames_per_minute = 120;
uint32_t Http2::max_rst_stream_frames_per_minute = 200;
float Http2::min_avg_window_update = 2560.0;
uint32_t Http2::con_slow_log_threshold = 0;
uint32_t Http2::stream_slow_log_threshold = 0;
uint32_t Http2::header_table_size_limit = 65536;
uint32_t Http2::write_buffer_block_size = 262144;
float Http2::write_size_threshold = 0.5;
uint32_t Http2::write_time_threshold = 100;
uint32_t Http2::buffer_water_mark = 0;
uint32_t Http2::max_concurrent_streams_in = 100;
uint32_t Http2::min_concurrent_streams_in = 10;
uint32_t Http2::max_active_streams_in = 0;
bool Http2::throttling = false;
uint32_t Http2::stream_priority_enabled = 0;
uint32_t Http2::initial_window_size = 65535;
uint32_t Http2::max_frame_size = 16384;
uint32_t Http2::header_table_size = 4096;
uint32_t Http2::max_header_list_size = 4294967295;
uint32_t Http2::accept_no_activity_timeout = 120;
uint32_t Http2::no_activity_timeout_in = 120;
uint32_t Http2::active_timeout_in = 0;
uint32_t Http2::push_diary_size = 256;
uint32_t Http2::zombie_timeout_in = 0;
float Http2::stream_error_rate_threshold = 0.1;
uint32_t Http2::stream_error_sampling_threshold = 10;
uint32_t Http2::max_settings_per_frame = 7;
uint32_t Http2::max_settings_per_minute = 14;
uint32_t Http2::max_settings_frames_per_minute = 14;
uint32_t Http2::max_ping_frames_per_minute = 60;
uint32_t Http2::max_priority_frames_per_minute = 120;
uint32_t Http2::max_rst_stream_frames_per_minute = 200;
uint32_t Http2::max_continuation_frames_per_minute = 120;
float Http2::min_avg_window_update = 2560.0;
uint32_t Http2::con_slow_log_threshold = 0;
uint32_t Http2::stream_slow_log_threshold = 0;
uint32_t Http2::header_table_size_limit = 65536;
uint32_t Http2::write_buffer_block_size = 262144;
float Http2::write_size_threshold = 0.5;
uint32_t Http2::write_time_threshold = 100;
uint32_t Http2::buffer_water_mark = 0;

void
Http2::init()
Expand All @@ -853,6 +856,7 @@ Http2::init()
REC_EstablishStaticConfigInt32U(max_ping_frames_per_minute, "proxy.config.http2.max_ping_frames_per_minute");
REC_EstablishStaticConfigInt32U(max_priority_frames_per_minute, "proxy.config.http2.max_priority_frames_per_minute");
REC_EstablishStaticConfigInt32U(max_rst_stream_frames_per_minute, "proxy.config.http2.max_rst_stream_frames_per_minute");
REC_EstablishStaticConfigInt32U(max_continuation_frames_per_minute, "proxy.config.http2.max_continuation_frames_per_minute");
REC_EstablishStaticConfigFloat(min_avg_window_update, "proxy.config.http2.min_avg_window_update");
REC_EstablishStaticConfigInt32U(con_slow_log_threshold, "proxy.config.http2.connection.slow.log.threshold");
REC_EstablishStaticConfigInt32U(stream_slow_log_threshold, "proxy.config.http2.stream.slow.log.threshold");
Expand Down Expand Up @@ -923,6 +927,8 @@ Http2::init()
static_cast<int>(HTTP2_STAT_MAX_PRIORITY_FRAMES_PER_MINUTE_EXCEEDED), RecRawStatSyncSum);
RecRegisterRawStat(http2_rsb, RECT_PROCESS, HTTP2_STAT_MAX_RST_STREAM_FRAMES_PER_MINUTE_EXCEEDED_NAME, RECD_INT, RECP_PERSISTENT,
static_cast<int>(HTTP2_STAT_MAX_RST_STREAM_FRAMES_PER_MINUTE_EXCEEDED), RecRawStatSyncSum);
RecRegisterRawStat(http2_rsb, RECT_PROCESS, HTTP2_STAT_MAX_CONTINUATION_FRAMES_PER_MINUTE_EXCEEDED_NAME, RECD_INT,
RECP_PERSISTENT, static_cast<int>(HTTP2_STAT_MAX_CONTINUATION_FRAMES_PER_MINUTE_EXCEEDED), RecRawStatSyncSum);
RecRegisterRawStat(http2_rsb, RECT_PROCESS, HTTP2_STAT_INSUFFICIENT_AVG_WINDOW_UPDATE_NAME, RECD_INT, RECP_PERSISTENT,
static_cast<int>(HTTP2_STAT_INSUFFICIENT_AVG_WINDOW_UPDATE), RecRawStatSyncSum);
RecRegisterRawStat(http2_rsb, RECT_PROCESS, HTTP2_STAT_MAX_CONCURRENT_STREAMS_EXCEEDED_IN_NAME, RECD_INT, RECP_PERSISTENT,
Expand Down
2 changes: 2 additions & 0 deletions proxy/http2/HTTP2.h
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ enum {
HTTP2_STAT_MAX_PING_FRAMES_PER_MINUTE_EXCEEDED,
HTTP2_STAT_MAX_PRIORITY_FRAMES_PER_MINUTE_EXCEEDED,
HTTP2_STAT_MAX_RST_STREAM_FRAMES_PER_MINUTE_EXCEEDED,
HTTP2_STAT_MAX_CONTINUATION_FRAMES_PER_MINUTE_EXCEEDED,
HTTP2_STAT_INSUFFICIENT_AVG_WINDOW_UPDATE,
HTTP2_STAT_MAX_CONCURRENT_STREAMS_EXCEEDED_IN,
HTTP2_STAT_MAX_CONCURRENT_STREAMS_EXCEEDED_OUT,
Expand Down Expand Up @@ -404,6 +405,7 @@ class Http2
static uint32_t max_ping_frames_per_minute;
static uint32_t max_priority_frames_per_minute;
static uint32_t max_rst_stream_frames_per_minute;
static uint32_t max_continuation_frames_per_minute;
static float min_avg_window_update;
static uint32_t con_slow_log_threshold;
static uint32_t stream_slow_log_threshold;
Expand Down
36 changes: 32 additions & 4 deletions proxy/http2/Http2ConnectionState.cc
Original file line number Diff line number Diff line change
Expand Up @@ -924,6 +924,18 @@ rcv_continuation_frame(Http2ConnectionState &cstate, const Http2Frame &frame)
}
}

// Update CONTINUATION frame count per minute.
cstate.increment_received_continuation_frame_count();
// Close this connection if its CONTINUATION frame count exceeds a limit.
if (cstate.configured_max_continuation_frames_per_minute != 0 &&
cstate.get_received_continuation_frame_count() > cstate.configured_max_continuation_frames_per_minute) {
HTTP2_INCREMENT_THREAD_DYN_STAT(HTTP2_STAT_MAX_CONTINUATION_FRAMES_PER_MINUTE_EXCEEDED, this_ethread());
Http2StreamDebug(cstate.session, stream_id, "Observed too frequent CONTINUATION frames: %u frames within a last minute",
cstate.get_received_continuation_frame_count());
return Http2Error(Http2ErrorClass::HTTP2_ERROR_CLASS_CONNECTION, Http2ErrorCode::HTTP2_ERROR_ENHANCE_YOUR_CALM,
"reset too frequent CONTINUATION frames");
}

uint32_t header_blocks_offset = stream->header_blocks_length;
stream->header_blocks_length += payload_length;

Expand Down Expand Up @@ -1088,10 +1100,11 @@ Http2ConnectionState::init(Http2CommonSession *ssn)
dependency_tree = new DependencyTree(Http2::max_concurrent_streams_in);
}

configured_max_settings_frames_per_minute = Http2::max_settings_frames_per_minute;
configured_max_ping_frames_per_minute = Http2::max_ping_frames_per_minute;
configured_max_priority_frames_per_minute = Http2::max_priority_frames_per_minute;
configured_max_rst_stream_frames_per_minute = Http2::max_rst_stream_frames_per_minute;
configured_max_settings_frames_per_minute = Http2::max_settings_frames_per_minute;
configured_max_ping_frames_per_minute = Http2::max_ping_frames_per_minute;
configured_max_priority_frames_per_minute = Http2::max_priority_frames_per_minute;
configured_max_rst_stream_frames_per_minute = Http2::max_rst_stream_frames_per_minute;
configured_max_continuation_frames_per_minute = Http2::max_continuation_frames_per_minute;
if (auto snis = dynamic_cast<TLSSNISupport *>(session->get_netvc()); snis) {
if (snis->hints_from_sni.http2_max_settings_frames_per_minute.has_value()) {
configured_max_settings_frames_per_minute = snis->hints_from_sni.http2_max_settings_frames_per_minute.value();
Expand All @@ -1105,6 +1118,9 @@ Http2ConnectionState::init(Http2CommonSession *ssn)
if (snis->hints_from_sni.http2_max_rst_stream_frames_per_minute.has_value()) {
configured_max_rst_stream_frames_per_minute = snis->hints_from_sni.http2_max_rst_stream_frames_per_minute.value();
}
if (snis->hints_from_sni.http2_max_continuation_frames_per_minute.has_value()) {
configured_max_continuation_frames_per_minute = snis->hints_from_sni.http2_max_continuation_frames_per_minute.value();
}
}

_cop = ActivityCop<Http2Stream>(this->mutex, &stream_list, 1);
Expand Down Expand Up @@ -2140,6 +2156,18 @@ Http2ConnectionState::get_received_rst_stream_frame_count()
return this->_received_rst_stream_frame_counter.get_count();
}

void
Http2ConnectionState::increment_received_continuation_frame_count()
{
this->_received_continuation_frame_counter.increment();
}

uint32_t
Http2ConnectionState::get_received_continuation_frame_count()
{
return this->_received_continuation_frame_counter.get_count();
}

// Return min_concurrent_streams_in when current client streams number is larger than max_active_streams_in.
// Main purpose of this is preventing DDoS Attacks.
unsigned
Expand Down
12 changes: 8 additions & 4 deletions proxy/http2/Http2ConnectionState.h
Original file line number Diff line number Diff line change
Expand Up @@ -102,10 +102,11 @@ class Http2ConnectionState : public Continuation
Http2ConnectionSettings server_settings;
Http2ConnectionSettings client_settings;

uint32_t configured_max_settings_frames_per_minute = 0;
uint32_t configured_max_ping_frames_per_minute = 0;
uint32_t configured_max_priority_frames_per_minute = 0;
uint32_t configured_max_rst_stream_frames_per_minute = 0;
uint32_t configured_max_settings_frames_per_minute = 0;
uint32_t configured_max_ping_frames_per_minute = 0;
uint32_t configured_max_priority_frames_per_minute = 0;
uint32_t configured_max_rst_stream_frames_per_minute = 0;
uint32_t configured_max_continuation_frames_per_minute = 0;

void init(Http2CommonSession *ssn);
void send_connection_preface();
Expand Down Expand Up @@ -174,6 +175,8 @@ class Http2ConnectionState : public Continuation
uint32_t get_received_priority_frame_count();
void increment_received_rst_stream_frame_count();
uint32_t get_received_rst_stream_frame_count();
void increment_received_continuation_frame_count();
uint32_t get_received_continuation_frame_count();

ssize_t client_rwnd() const;
Http2ErrorCode increment_client_rwnd(size_t amount);
Expand Down Expand Up @@ -220,6 +223,7 @@ class Http2ConnectionState : public Continuation
Http2FrequencyCounter _received_ping_frame_counter;
Http2FrequencyCounter _received_priority_frame_counter;
Http2FrequencyCounter _received_rst_stream_frame_counter;
Http2FrequencyCounter _received_continuation_frame_counter;

// NOTE: Id of stream which MUST receive CONTINUATION frame.
// - [RFC 7540] 6.2 HEADERS
Expand Down