This is an automated email from the ASF dual-hosted git repository.
zwoop pushed a commit to branch 7.1.x
in repository https://gitbox.apache.org/repos/asf/trafficserver.git
The following commit(s) were added to refs/heads/7.1.x by this push:
new 7959e3f HTTP/2 rate limiting
7959e3f is described below
commit 7959e3f623f1cbcbd9208031af2c04f8e7df6a28
Author: Masakazu Kitajo <[email protected]>
AuthorDate: Tue Aug 13 10:57:06 2019 -0600
HTTP/2 rate limiting
---
doc/admin-guide/files/records.config.en.rst | 35 +++++
iocore/eventsystem/I_VIO.h | 6 +
iocore/eventsystem/P_VIO.h | 14 ++
iocore/net/SSLNetVConnection.cc | 5 +-
iocore/net/UnixNetVConnection.cc | 2 +-
mgmt/RecordsConfig.cc | 10 ++
proxy/http2/HTTP2.cc | 71 ++++++----
proxy/http2/HTTP2.h | 6 +
proxy/http2/Http2ClientSession.cc | 53 ++++++-
proxy/http2/Http2ClientSession.h | 13 ++
proxy/http2/Http2ConnectionState.cc | 209 ++++++++++++++++++++++++----
proxy/http2/Http2ConnectionState.h | 59 +++++++-
proxy/http2/Http2Stream.cc | 59 ++++++++
proxy/http2/Http2Stream.h | 29 ++--
14 files changed, 498 insertions(+), 73 deletions(-)
diff --git a/doc/admin-guide/files/records.config.en.rst
b/doc/admin-guide/files/records.config.en.rst
index a1cfec0..d070dd2 100644
--- a/doc/admin-guide/files/records.config.en.rst
+++ b/doc/admin-guide/files/records.config.en.rst
@@ -3383,6 +3383,13 @@ HTTP/2 Configuration
HTTP/2 connection to avoid duplicate pushes on the same connection. If the
maximum number is reached, new entries are not remembered.
+.. ts:cv:: CONFIG proxy.config.http2.stream_error_rate_threshold FLOAT 0.1
+ :reloadable:
+
+ This is the maximum stream error rate |TS| allows on an HTTP/2 connection.
+ |TS| gracefully closes connections that have stream error rates above this
+ setting by sending GOAWAY frames.
+
.. ts:cv:: CONFIG proxy.config.http2.max_settings_per_frame INT 7
:reloadable:
@@ -3397,6 +3404,34 @@ HTTP/2 Configuration
Clients exceeded this limit will be immediately disconnected with an error
code of ENHANCE_YOUR_CALM.
+.. ts:cv:: CONFIG proxy.config.http2.max_settings_frames_per_minute INT 14
+ :reloadable:
+
+ Specifies how many SETTINGS 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.max_ping_frames_per_minute INT 60
+ :reloadable:
+
+ Specifies how many number of PING 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.max_priority_frames_per_minute INT 120
+ :reloadable:
+
+ Specifies how many number of PRIORITY 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:
+
+ Specifies the minimum average window increment |TS| allows. The average
will be calculated based on the last 5 WINDOW_UPDATE frames.
+ Clients that send smaller window increments lower than this limit will be
immediately disconnected with an error
+ code of ENHANCE_YOUR_CALM.
+
Plug-in Configuration
=====================
diff --git a/iocore/eventsystem/I_VIO.h b/iocore/eventsystem/I_VIO.h
index 986b12e..7f44139 100644
--- a/iocore/eventsystem/I_VIO.h
+++ b/iocore/eventsystem/I_VIO.h
@@ -140,6 +140,9 @@ public:
*/
inkcoreapi void reenable_re();
+ void disable();
+ bool is_disabled();
+
VIO(int aop);
VIO();
@@ -219,6 +222,9 @@ public:
*/
Ptr<ProxyMutex> mutex;
+
+private:
+ bool _disabled = false;
};
#include "I_VConnection.h"
diff --git a/iocore/eventsystem/P_VIO.h b/iocore/eventsystem/P_VIO.h
index c45443f..8901c48 100644
--- a/iocore/eventsystem/P_VIO.h
+++ b/iocore/eventsystem/P_VIO.h
@@ -106,6 +106,7 @@ VIO::set_continuation(Continuation *acont)
TS_INLINE void
VIO::reenable()
{
+ this->_disabled = false;
if (vc_server)
vc_server->reenable(this);
}
@@ -118,6 +119,19 @@ VIO::reenable()
TS_INLINE void
VIO::reenable_re()
{
+ this->_disabled = false;
if (vc_server)
vc_server->reenable_re(this);
}
+
+TS_INLINE void
+VIO::disable()
+{
+ this->_disabled = true;
+}
+
+TS_INLINE bool
+VIO::is_disabled()
+{
+ return this->_disabled;
+}
diff --git a/iocore/net/SSLNetVConnection.cc b/iocore/net/SSLNetVConnection.cc
index f2de327..9d7e38e 100644
--- a/iocore/net/SSLNetVConnection.cc
+++ b/iocore/net/SSLNetVConnection.cc
@@ -469,7 +469,7 @@ SSLNetVConnection::net_read_io(NetHandler *nh, EThread
*lthread)
// If it is not enabled, lower its priority. This allows
// a fast connection to speed match a slower connection by
// shifting down in priority even if it could read.
- if (!s->enabled || s->vio.op != VIO::READ) {
+ if (!s->enabled || s->vio.op != VIO::READ || s->vio.is_disabled()) {
read_disable(nh, this);
return;
}
@@ -574,7 +574,7 @@ SSLNetVConnection::net_read_io(NetHandler *nh, EThread
*lthread)
}
// If there is nothing to do or no space available, disable connection
- if (ntodo <= 0 || !buf.writer()->write_avail()) {
+ if (ntodo <= 0 || !buf.writer()->write_avail() || s->vio.is_disabled()) {
read_disable(nh, this);
return;
}
@@ -1487,6 +1487,7 @@ SSLNetVConnection::reenable(NetHandler *nh)
}
Debug("ssl", "iterate from reenable curHook=%p %d", curHook,
sslHandshakeHookState);
}
+
this->readReschedule(nh);
}
diff --git a/iocore/net/UnixNetVConnection.cc b/iocore/net/UnixNetVConnection.cc
index 2981ab1..a30200b 100644
--- a/iocore/net/UnixNetVConnection.cc
+++ b/iocore/net/UnixNetVConnection.cc
@@ -263,7 +263,7 @@ read_from_net(NetHandler *nh, UnixNetVConnection *vc,
EThread *thread)
}
// if it is not enabled.
- if (!s->enabled || s->vio.op != VIO::READ) {
+ if (!s->enabled || s->vio.op != VIO::READ || s->vio.is_disabled()) {
read_disable(nh, vc);
return;
}
diff --git a/mgmt/RecordsConfig.cc b/mgmt/RecordsConfig.cc
index 30613ff..c3393f0 100644
--- a/mgmt/RecordsConfig.cc
+++ b/mgmt/RecordsConfig.cc
@@ -1438,10 +1438,20 @@ static const RecordElement RecordsConfig[] =
,
{RECT_CONFIG, "proxy.config.http2.push_diary_size", RECD_INT, "256",
RECU_DYNAMIC, RR_NULL, RECC_STR, "^[0-9]+$", RECA_NULL}
,
+ {RECT_CONFIG, "proxy.config.http2.stream_error_rate_threshold", RECD_FLOAT,
"0.1", RECU_DYNAMIC, RR_NULL, RECC_NULL, nullptr, RECA_NULL}
+ ,
{RECT_CONFIG, "proxy.config.http2.max_settings_per_frame", RECD_INT, "7",
RECU_DYNAMIC, RR_NULL, RECC_STR, "^[0-9]+$", RECA_NULL}
,
{RECT_CONFIG, "proxy.config.http2.max_settings_per_minute", RECD_INT, "14",
RECU_DYNAMIC, RR_NULL, RECC_STR, "^[0-9]+$", RECA_NULL}
,
+ {RECT_CONFIG, "proxy.config.http2.max_settings_frames_per_minute", RECD_INT,
"14", RECU_DYNAMIC, RR_NULL, RECC_STR, "^[0-9]+$", RECA_NULL}
+ ,
+ {RECT_CONFIG, "proxy.config.http2.max_ping_frames_per_minute", RECD_INT,
"60", RECU_DYNAMIC, RR_NULL, RECC_STR, "^[0-9]+$", RECA_NULL}
+ ,
+ {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.min_avg_window_update", RECD_FLOAT,
"2560.0", RECU_DYNAMIC, RR_NULL, RECC_NULL, nullptr, RECA_NULL}
+ ,
//# Add LOCAL Records Here
{RECT_LOCAL, "proxy.local.incoming_ip_to_bind", RECD_STRING, nullptr,
RECU_NULL, RR_NULL, RECC_NULL, nullptr, RECA_NULL}
diff --git a/proxy/http2/HTTP2.cc b/proxy/http2/HTTP2.cc
index 568638a..d76256f 100644
--- a/proxy/http2/HTTP2.cc
+++ b/proxy/http2/HTTP2.cc
@@ -48,19 +48,20 @@ static const int HTTP2_MAX_TABLE_SIZE_LIMIT = 64 * 1024;
// Statistics
RecRawStatBlock *http2_rsb;
-static const char *const HTTP2_STAT_CURRENT_CLIENT_SESSION_NAME =
"proxy.process.http2.current_client_sessions";
-static const char *const HTTP2_STAT_CURRENT_CLIENT_STREAM_NAME =
"proxy.process.http2.current_client_streams";
-static const char *const HTTP2_STAT_TOTAL_CLIENT_STREAM_NAME =
"proxy.process.http2.total_client_streams";
-static const char *const HTTP2_STAT_TOTAL_TRANSACTIONS_TIME_NAME =
"proxy.process.http2.total_transactions_time";
-static const char *const HTTP2_STAT_TOTAL_CLIENT_CONNECTION_NAME =
"proxy.process.http2.total_client_connections";
-static const char *const HTTP2_STAT_CONNECTION_ERRORS_NAME =
"proxy.process.http2.connection_errors";
-static const char *const HTTP2_STAT_STREAM_ERRORS_NAME =
"proxy.process.http2.stream_errors";
-static const char *const HTTP2_STAT_SESSION_DIE_DEFAULT_NAME =
"proxy.process.http2.session_die_default";
-static const char *const HTTP2_STAT_SESSION_DIE_OTHER_NAME =
"proxy.process.http2.session_die_other";
-static const char *const HTTP2_STAT_SESSION_DIE_ACTIVE_NAME =
"proxy.process.http2.session_die_active";
-static const char *const HTTP2_STAT_SESSION_DIE_INACTIVE_NAME =
"proxy.process.http2.session_die_inactive";
-static const char *const HTTP2_STAT_SESSION_DIE_EOS_NAME =
"proxy.process.http2.session_die_eos";
-static const char *const HTTP2_STAT_SESSION_DIE_ERROR_NAME =
"proxy.process.http2.session_die_error";
+static const char *const HTTP2_STAT_CURRENT_CLIENT_SESSION_NAME =
"proxy.process.http2.current_client_sessions";
+static const char *const HTTP2_STAT_CURRENT_CLIENT_STREAM_NAME =
"proxy.process.http2.current_client_streams";
+static const char *const HTTP2_STAT_TOTAL_CLIENT_STREAM_NAME =
"proxy.process.http2.total_client_streams";
+static const char *const HTTP2_STAT_TOTAL_TRANSACTIONS_TIME_NAME =
"proxy.process.http2.total_transactions_time";
+static const char *const HTTP2_STAT_TOTAL_CLIENT_CONNECTION_NAME =
"proxy.process.http2.total_client_connections";
+static const char *const HTTP2_STAT_CONNECTION_ERRORS_NAME =
"proxy.process.http2.connection_errors";
+static const char *const HTTP2_STAT_STREAM_ERRORS_NAME =
"proxy.process.http2.stream_errors";
+static const char *const HTTP2_STAT_SESSION_DIE_DEFAULT_NAME =
"proxy.process.http2.session_die_default";
+static const char *const HTTP2_STAT_SESSION_DIE_OTHER_NAME =
"proxy.process.http2.session_die_other";
+static const char *const HTTP2_STAT_SESSION_DIE_ACTIVE_NAME =
"proxy.process.http2.session_die_active";
+static const char *const HTTP2_STAT_SESSION_DIE_INACTIVE_NAME =
"proxy.process.http2.session_die_inactive";
+static const char *const HTTP2_STAT_SESSION_DIE_EOS_NAME =
"proxy.process.http2.session_die_eos";
+static const char *const HTTP2_STAT_SESSION_DIE_ERROR_NAME =
"proxy.process.http2.session_die_error";
+static const char *const HTTP2_STAT_SESSION_DIE_HIGH_ERROR_RATE_NAME =
"proxy.process.http2.session_die_high_error_rate";
union byte_pointer {
byte_pointer(void *p) : ptr(p) {}
@@ -178,7 +179,7 @@ http2_settings_parameter_is_valid(const
Http2SettingsParameter ¶m)
// +---------------+---------------+---------------+
// | Type (8) | Flags (8) |
// +-+-+-----------+---------------+-------------------------------+
-// |R| Stream Identifier (31) |
+// |R| Stream Identifier (32) |
// +=+=============================================================+
// | Frame Payload (0...) ...
// +---------------------------------------------------------------+
@@ -716,21 +717,26 @@ 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 = 1048576;
-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::max_settings_per_frame = 7;
-uint32_t Http2::max_settings_per_minute = 14;
+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 = 1048576;
+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;
+float Http2::stream_error_rate_threshold = 0.1;
+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;
void
Http2::init()
@@ -747,8 +753,13 @@ Http2::init()
REC_EstablishStaticConfigInt32U(no_activity_timeout_in,
"proxy.config.http2.no_activity_timeout_in");
REC_EstablishStaticConfigInt32U(active_timeout_in,
"proxy.config.http2.active_timeout_in");
REC_EstablishStaticConfigInt32U(push_diary_size,
"proxy.config.http2.push_diary_size");
+ REC_EstablishStaticConfigFloat(stream_error_rate_threshold,
"proxy.config.http2.stream_error_rate_threshold");
REC_EstablishStaticConfigInt32U(max_settings_per_frame,
"proxy.config.http2.max_settings_per_frame");
REC_EstablishStaticConfigInt32U(max_settings_per_minute,
"proxy.config.http2.max_settings_per_minute");
+ 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_EstablishStaticConfigFloat(min_avg_window_update,
"proxy.config.http2.min_avg_window_update");
// If any settings is broken, ATS should not start
ink_release_assert(http2_settings_parameter_is_valid({HTTP2_SETTINGS_MAX_CONCURRENT_STREAMS,
max_concurrent_streams_in}));
@@ -794,6 +805,8 @@ Http2::init()
static_cast<int>(HTTP2_STAT_SESSION_DIE_INACTIVE),
RecRawStatSyncSum);
RecRegisterRawStat(http2_rsb, RECT_PROCESS,
HTTP2_STAT_SESSION_DIE_ERROR_NAME, RECD_INT, RECP_PERSISTENT,
static_cast<int>(HTTP2_STAT_SESSION_DIE_ERROR),
RecRawStatSyncSum);
+ RecRegisterRawStat(http2_rsb, RECT_PROCESS,
HTTP2_STAT_SESSION_DIE_HIGH_ERROR_RATE_NAME, RECD_INT, RECP_PERSISTENT,
+ static_cast<int>(HTTP2_STAT_SESSION_DIE_HIGH_ERROR_RATE),
RecRawStatSyncSum);
}
#if TS_HAS_TESTS
diff --git a/proxy/http2/HTTP2.h b/proxy/http2/HTTP2.h
index 4b1da0b..b4fec2f 100644
--- a/proxy/http2/HTTP2.h
+++ b/proxy/http2/HTTP2.h
@@ -83,6 +83,7 @@ enum {
HTTP2_STAT_SESSION_DIE_INACTIVE,
HTTP2_STAT_SESSION_DIE_EOS,
HTTP2_STAT_SESSION_DIE_ERROR,
+ HTTP2_STAT_SESSION_DIE_HIGH_ERROR_RATE,
HTTP2_N_STATS // Terminal counter, NOT A STAT INDEX.
};
@@ -376,8 +377,13 @@ public:
static uint32_t no_activity_timeout_in;
static uint32_t active_timeout_in;
static uint32_t push_diary_size;
+ static float stream_error_rate_threshold;
static uint32_t max_settings_per_frame;
static uint32_t max_settings_per_minute;
+ 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 float min_avg_window_update;
static void init();
};
diff --git a/proxy/http2/Http2ClientSession.cc
b/proxy/http2/Http2ClientSession.cc
index 1028548..a92aa82 100644
--- a/proxy/http2/Http2ClientSession.cc
+++ b/proxy/http2/Http2ClientSession.cc
@@ -86,6 +86,11 @@ Http2ClientSession::destroy()
void
Http2ClientSession::free()
{
+ if (this->_reenable_event) {
+ this->_reenable_event->cancel();
+ this->_reenable_event = nullptr;
+ }
+
if (h2_pushed_urls) {
this->h2_pushed_urls = ink_hash_table_destroy(this->h2_pushed_urls);
}
@@ -338,6 +343,13 @@ Http2ClientSession::main_event_handler(int event, void
*edata)
break;
}
+ case HTTP2_SESSION_EVENT_REENABLE:
+ // VIO will be reenableed in this handler
+ retval = (this->*session_handler)(VC_EVENT_READ_READY, static_cast<VIO
*>(e->cookie));
+ // Clear the event after calling session_handler to not reschedule
REENABLE in it
+ this->_reenable_event = nullptr;
+ break;
+
case VC_EVENT_ACTIVE_TIMEOUT:
case VC_EVENT_INACTIVITY_TIMEOUT:
case VC_EVENT_ERROR:
@@ -475,7 +487,16 @@ Http2ClientSession::state_complete_frame_read(int event,
void *edata)
STATE_ENTER(&Http2ClientSession::state_complete_frame_read, event);
ink_assert(event == VC_EVENT_READ_COMPLETE || event == VC_EVENT_READ_READY);
if (this->sm_reader->read_avail() < this->current_hdr.length) {
- vio->reenable();
+ if (this->_should_do_something_else()) {
+ if (this->_reenable_event == nullptr) {
+ vio->disable();
+ this->_reenable_event = mutex->thread_holding->schedule_in(this,
HRTIME_MSECONDS(1), HTTP2_SESSION_EVENT_REENABLE, vio);
+ } else {
+ vio->reenable();
+ }
+ } else {
+ vio->reenable();
+ }
return 0;
}
DebugHttp2Ssn("completed frame read, %" PRId64 " bytes available",
this->sm_reader->read_avail());
@@ -492,6 +513,7 @@ Http2ClientSession::do_complete_frame_read()
Http2Frame frame(this->current_hdr, this->sm_reader);
send_connection_event(&this->connection_state, HTTP2_SESSION_EVENT_RECV,
&frame);
this->sm_reader->consume(this->current_hdr.length);
+ ++(this->_n_frame_read);
// Set the event handler if there is no more data to process a new frame
HTTP2_SET_SESSION_HANDLER(&Http2ClientSession::state_start_frame_read);
@@ -512,9 +534,19 @@ Http2ClientSession::state_process_frame_read(int event,
VIO *vio, bool inside_fr
DebugHttp2Ssn("reading a frame has been canceled (%u)",
connection_state.tx_error_code.code);
break;
}
+
+ Http2ErrorCode err = Http2ErrorCode::HTTP2_ERROR_NO_ERROR;
+ if (this->connection_state.get_stream_error_rate() > std::min(1.0,
Http2::stream_error_rate_threshold * 2.0)) {
+ ip_port_text_buffer ipb;
+ const char *client_ip = ats_ip_ntop(get_client_addr(), ipb, sizeof(ipb));
+ Error("HTTP/2 session error client_ip=%s session_id=%" PRId64
+ " closing a connection, because its stream error rate (%f) is too
high",
+ client_ip, connection_id(),
this->connection_state.get_stream_error_rate());
+ err = Http2ErrorCode::HTTP2_ERROR_ENHANCE_YOUR_CALM;
+ }
+
// Return if there was an error
- Http2ErrorCode err;
- if (do_start_frame_read(err) < 0) {
+ if (err > Http2ErrorCode::HTTP2_ERROR_NO_ERROR || do_start_frame_read(err)
< 0) {
// send an error if specified. Otherwise, just go away
if (err > Http2ErrorCode::HTTP2_ERROR_NO_ERROR) {
SCOPED_MUTEX_LOCK(lock, this->connection_state.mutex, this_ethread());
@@ -533,6 +565,14 @@ Http2ClientSession::state_process_frame_read(int event,
VIO *vio, bool inside_fr
break;
}
do_complete_frame_read();
+
+ if (this->_should_do_something_else()) {
+ if (this->_reenable_event == nullptr) {
+ vio->disable();
+ this->_reenable_event = mutex->thread_holding->schedule_in(this,
HRTIME_MSECONDS(1), HTTP2_SESSION_EVENT_REENABLE, vio);
+ return 0;
+ }
+ }
}
// If the client hasn't shut us down, reenable
@@ -541,3 +581,10 @@ Http2ClientSession::state_process_frame_read(int event,
VIO *vio, bool inside_fr
}
return 0;
}
+
+bool
+Http2ClientSession::_should_do_something_else()
+{
+ // Do something else every 128 incoming frames
+ return (this->_n_frame_read & 0x7F) == 0;
+}
diff --git a/proxy/http2/Http2ClientSession.h b/proxy/http2/Http2ClientSession.h
index 6a52141..8674123 100644
--- a/proxy/http2/Http2ClientSession.h
+++ b/proxy/http2/Http2ClientSession.h
@@ -40,6 +40,14 @@
#define HTTP2_SESSION_EVENT_FINI (HTTP2_SESSION_EVENTS_START + 2)
#define HTTP2_SESSION_EVENT_RECV (HTTP2_SESSION_EVENTS_START + 3)
#define HTTP2_SESSION_EVENT_XMIT (HTTP2_SESSION_EVENTS_START + 4)
+#define HTTP2_SESSION_EVENT_SHUTDOWN_INIT (HTTP2_SESSION_EVENTS_START + 5)
+#define HTTP2_SESSION_EVENT_SHUTDOWN_CONT (HTTP2_SESSION_EVENTS_START + 6)
+#define HTTP2_SESSION_EVENT_REENABLE (HTTP2_SESSION_EVENTS_START + 7)
+
+enum class Http2SessionCod : int {
+ NOT_PROVIDED,
+ HIGH_ERROR_RATE,
+};
size_t const HTTP2_HEADER_BUFFER_SIZE_INDEX =
CLIENT_CONNECTION_FIRST_READ_BUFFER_SIZE_INDEX;
@@ -313,6 +321,8 @@ private:
// if there are multiple frames ready on the wire
int state_process_frame_read(int event, VIO *vio, bool inside_frame);
+ bool _should_do_something_else();
+
int64_t total_write_len;
SessionHandler session_handler;
NetVConnection *client_vc;
@@ -333,6 +343,9 @@ private:
InkHashTable *h2_pushed_urls = nullptr;
uint32_t h2_pushed_urls_size = 0;
+
+ Event *_reenable_event = nullptr;
+ int _n_frame_read = 0;
};
extern ClassAllocator<Http2ClientSession> http2ClientSessionAllocator;
diff --git a/proxy/http2/Http2ConnectionState.cc
b/proxy/http2/Http2ConnectionState.cc
index 009e80f..73274cc 100644
--- a/proxy/http2/Http2ConnectionState.cc
+++ b/proxy/http2/Http2ConnectionState.cc
@@ -27,6 +27,7 @@
#include "Http2Stream.h"
#include "Http2DebugNames.h"
#include <sstream>
+#include <numeric>
#define DebugHttp2Con(ua_session, fmt, ...) \
DebugSsn(ua_session, "http2_con", "[%" PRId64 "] " fmt,
ua_session->connection_id(), ##__VA_ARGS__);
@@ -133,18 +134,18 @@ rcv_data_frame(Http2ConnectionState &cstate, const
Http2Frame &frame)
}
// Check whether Window Size is acceptable
- if (cstate.server_rwnd < payload_length) {
+ if (cstate.server_rwnd() < payload_length) {
return Http2Error(Http2ErrorClass::HTTP2_ERROR_CLASS_CONNECTION,
Http2ErrorCode::HTTP2_ERROR_FLOW_CONTROL_ERROR,
"recv data cstate.server_rwnd < payload_length");
}
- if (stream->server_rwnd < payload_length) {
+ if (stream->server_rwnd() < payload_length) {
return Http2Error(Http2ErrorClass::HTTP2_ERROR_CLASS_STREAM,
Http2ErrorCode::HTTP2_ERROR_FLOW_CONTROL_ERROR,
"recv data stream->server_rwnd < payload_length");
}
// Update Window size
- cstate.server_rwnd -= payload_length;
- stream->server_rwnd -= payload_length;
+ cstate.decrement_server_rwnd(payload_length);
+ stream->decrement_server_rwnd(payload_length);
const uint32_t unpadded_length = payload_length - pad_length;
// If we call write() multiple times, we must keep the same reader, so we can
@@ -170,15 +171,15 @@ rcv_data_frame(Http2ConnectionState &cstate, const
Http2Frame &frame)
uint32_t initial_rwnd =
cstate.server_settings.get(HTTP2_SETTINGS_INITIAL_WINDOW_SIZE);
uint32_t min_rwnd = std::min(initial_rwnd,
cstate.server_settings.get(HTTP2_SETTINGS_MAX_FRAME_SIZE));
// Connection level WINDOW UPDATE
- if (cstate.server_rwnd <= min_rwnd) {
- Http2WindowSize diff_size = initial_rwnd - cstate.server_rwnd;
- cstate.server_rwnd += diff_size;
+ if (cstate.server_rwnd() <= min_rwnd) {
+ Http2WindowSize diff_size = initial_rwnd - cstate.server_rwnd();
+ cstate.increment_server_rwnd(diff_size);
cstate.send_window_update_frame(0, diff_size);
}
// Stream level WINDOW UPDATE
- if (stream->server_rwnd <= min_rwnd) {
- Http2WindowSize diff_size = initial_rwnd - stream->server_rwnd;
- stream->server_rwnd += diff_size;
+ if (stream->server_rwnd() <= min_rwnd) {
+ Http2WindowSize diff_size = initial_rwnd - stream->server_rwnd();
+ stream->increment_server_rwnd(diff_size);
cstate.send_window_update_frame(stream->get_id(), diff_size);
}
@@ -397,6 +398,21 @@ rcv_priority_frame(Http2ConnectionState &cstate, const
Http2Frame &frame)
"PRIORITY frame depends on itself");
}
+ // Update PRIORITY frame count per minute
+ cstate.increment_received_priority_frame_count();
+ // Close this conection if its priority frame count received exceeds a limit
+ if (cstate.get_received_priority_frame_count() >
Http2::max_priority_frames_per_minute) {
+ Http2StreamDebug(cstate.ua_session, stream_id,
+ "Observed too frequent priority changes: %u priority
changes within a last minute",
+ cstate.get_received_priority_frame_count());
+ return Http2Error(Http2ErrorClass::HTTP2_ERROR_CLASS_CONNECTION,
Http2ErrorCode::HTTP2_ERROR_ENHANCE_YOUR_CALM,
+ "recv priority too frequent priority changes");
+ }
+
+ if (!Http2::stream_priority_enabled) {
+ return Http2Error(Http2ErrorClass::HTTP2_ERROR_CLASS_NONE);
+ }
+
Http2StreamDebug(cstate.ua_session, stream_id, "PRIORITY - dep: %d, weight:
%d, excl: %d, tree size: %d",
priority.stream_dependency, priority.weight,
priority.exclusive_flag, cstate.dependency_tree->size());
@@ -493,6 +509,16 @@ rcv_settings_frame(Http2ConnectionState &cstate, const
Http2Frame &frame)
Http2StreamDebug(cstate.ua_session, stream_id, "Received SETTINGS frame");
+ // Update SETTIGNS frame count per minute
+ cstate.increment_received_settings_frame_count();
+ // Close this conection if its SETTINGS frame count exceeds a limit
+ if (cstate.get_received_settings_frame_count() >
Http2::max_settings_frames_per_minute) {
+ Http2StreamDebug(cstate.ua_session, stream_id, "Observed too frequent
SETTINGS 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,
+ "recv settings too frequent SETTINGS frames");
+ }
+
// [RFC 7540] 6.5. The stream identifier for a SETTINGS frame MUST be zero.
// If an endpoint receives a SETTINGS frame whose stream identifier field is
// anything other than 0x0, the endpoint MUST respond with a connection
@@ -612,6 +638,16 @@ rcv_ping_frame(Http2ConnectionState &cstate, const
Http2Frame &frame)
"ping bad length");
}
+ // Update PING frame count per minute
+ cstate.increment_received_ping_frame_count();
+ // Close this conection if its ping count received exceeds a limit
+ if (cstate.get_received_ping_frame_count() >
Http2::max_ping_frames_per_minute) {
+ Http2StreamDebug(cstate.ua_session, stream_id, "Observed too frequent PING
frames: %u PING frames within a last minute",
+ cstate.get_received_ping_frame_count());
+ return Http2Error(Http2ErrorClass::HTTP2_ERROR_CLASS_CONNECTION,
Http2ErrorCode::HTTP2_ERROR_ENHANCE_YOUR_CALM,
+ "recv ping too frequent PING frame");
+ }
+
// An endpoint MUST NOT respond to PING frames containing this flag.
if (frame.header().flags & HTTP2_FLAGS_PING_ACK) {
return Http2Error(Http2ErrorClass::HTTP2_ERROR_CLASS_NONE);
@@ -694,7 +730,7 @@ rcv_window_update_frame(Http2ConnectionState &cstate, const
Http2Frame &frame)
if (stream_id == 0) {
// Connection level window update
Http2StreamDebug(cstate.ua_session, stream_id, "Received WINDOW_UPDATE
frame - updated to: %zd delta: %u",
- (cstate.client_rwnd + size), size);
+ (cstate.client_rwnd() + size), size);
// A sender MUST NOT allow a flow-control window to exceed 2^31-1
// octets. If a sender receives a WINDOW_UPDATE that causes a flow-
@@ -703,12 +739,16 @@ rcv_window_update_frame(Http2ConnectionState &cstate,
const Http2Frame &frame)
// sends a RST_STREAM with an error code of FLOW_CONTROL_ERROR; for the
// connection, a GOAWAY frame with an error code of FLOW_CONTROL_ERROR
// is sent.
- if (size > HTTP2_MAX_WINDOW_SIZE - cstate.client_rwnd) {
+ if (size > HTTP2_MAX_WINDOW_SIZE - cstate.client_rwnd()) {
return Http2Error(Http2ErrorClass::HTTP2_ERROR_CLASS_CONNECTION,
Http2ErrorCode::HTTP2_ERROR_FLOW_CONTROL_ERROR,
"window update too big");
}
- cstate.client_rwnd += size;
+ auto error = cstate.increment_client_rwnd(size);
+ if (error != Http2ErrorCode::HTTP2_ERROR_NO_ERROR) {
+ return Http2Error(Http2ErrorClass::HTTP2_ERROR_CLASS_CONNECTION, error);
+ }
+
cstate.restart_streams();
} else {
// Stream level window update
@@ -724,7 +764,7 @@ rcv_window_update_frame(Http2ConnectionState &cstate, const
Http2Frame &frame)
}
Http2StreamDebug(cstate.ua_session, stream_id, "Received WINDOW_UPDATE
frame - updated to: %zd delta: %u",
- (stream->client_rwnd + size), size);
+ (stream->client_rwnd() + size), size);
// A sender MUST NOT allow a flow-control window to exceed 2^31-1
// octets. If a sender receives a WINDOW_UPDATE that causes a flow-
@@ -733,14 +773,17 @@ rcv_window_update_frame(Http2ConnectionState &cstate,
const Http2Frame &frame)
// sends a RST_STREAM with an error code of FLOW_CONTROL_ERROR; for the
// connection, a GOAWAY frame with an error code of FLOW_CONTROL_ERROR
// is sent.
- if (size > HTTP2_MAX_WINDOW_SIZE - stream->client_rwnd) {
+ if (size > HTTP2_MAX_WINDOW_SIZE - stream->client_rwnd()) {
return Http2Error(Http2ErrorClass::HTTP2_ERROR_CLASS_STREAM,
Http2ErrorCode::HTTP2_ERROR_FLOW_CONTROL_ERROR,
"window update too big 2");
}
- stream->client_rwnd += size;
- ssize_t wnd = std::min(cstate.client_rwnd, stream->client_rwnd);
+ auto error = stream->increment_client_rwnd(size);
+ if (error != Http2ErrorCode::HTTP2_ERROR_NO_ERROR) {
+ return Http2Error(Http2ErrorClass::HTTP2_ERROR_CLASS_STREAM, error);
+ }
+ ssize_t wnd = std::min(cstate.client_rwnd(), stream->client_rwnd());
if (!stream->is_closed() && stream->get_state() ==
Http2StreamState::HTTP2_STREAM_STATE_HALF_CLOSED_REMOTE && wnd > 0) {
SCOPED_MUTEX_LOCK(lock, stream->mutex, this_ethread());
stream->restart_sending();
@@ -1099,7 +1142,7 @@ Http2ConnectionState::restart_streams()
while (s != end) {
Http2Stream *next = static_cast<Http2Stream *>(s->link.next ?
s->link.next : stream_list.head);
if (!s->is_closed() && s->get_state() ==
Http2StreamState::HTTP2_STREAM_STATE_HALF_CLOSED_REMOTE &&
- std::min(this->client_rwnd, s->client_rwnd) > 0) {
+ std::min(this->client_rwnd(), s->client_rwnd()) > 0) {
SCOPED_MUTEX_LOCK(lock, s->mutex, this_ethread());
s->restart_sending();
}
@@ -1107,7 +1150,7 @@ Http2ConnectionState::restart_streams()
s = next;
}
if (!s->is_closed() && s->get_state() ==
Http2StreamState::HTTP2_STREAM_STATE_HALF_CLOSED_REMOTE &&
- std::min(this->client_rwnd, s->client_rwnd) > 0) {
+ std::min(this->client_rwnd(), s->client_rwnd()) > 0) {
SCOPED_MUTEX_LOCK(lock, s->mutex, this_ethread());
s->restart_sending();
}
@@ -1234,7 +1277,7 @@ Http2ConnectionState::update_initial_rwnd(Http2WindowSize
new_size)
// Update stream level window sizes
for (Http2Stream *s = stream_list.head; s; s = static_cast<Http2Stream
*>(s->link.next)) {
SCOPED_MUTEX_LOCK(lock, s->mutex, this_ethread());
- s->client_rwnd = new_size -
(client_settings.get(HTTP2_SETTINGS_INITIAL_WINDOW_SIZE) - s->client_rwnd);
+ s->update_initial_rwnd(new_size -
(client_settings.get(HTTP2_SETTINGS_INITIAL_WINDOW_SIZE) - s->client_rwnd()));
}
}
@@ -1263,7 +1306,7 @@
Http2ConnectionState::send_data_frames_depends_on_priority()
Http2DependencyTree::Node *node = dependency_tree->top();
// No node to send or no connection level window left
- if (node == nullptr || client_rwnd <= 0) {
+ if (node == nullptr || _client_rwnd <= 0) {
return;
}
@@ -1305,7 +1348,7 @@
Http2ConnectionState::send_data_frames_depends_on_priority()
Http2SendDataFrameResult
Http2ConnectionState::send_a_data_frame(Http2Stream *stream, size_t
&payload_length)
{
- const ssize_t window_size = std::min(this->client_rwnd,
stream->client_rwnd);
+ const ssize_t window_size = std::min(this->client_rwnd(),
stream->client_rwnd());
const size_t buf_len =
BUFFER_SIZE_FOR_INDEX(buffer_size_index[HTTP2_FRAME_TYPE_DATA]);
const size_t write_available_size = std::min(buf_len,
static_cast<size_t>(window_size));
size_t read_available_size = 0;
@@ -1351,12 +1394,12 @@ Http2ConnectionState::send_a_data_frame(Http2Stream
*stream, size_t &payload_len
}
// Update window size
- this->client_rwnd -= payload_length;
- stream->client_rwnd -= payload_length;
+ this->decrement_client_rwnd(payload_length);
+ stream->decrement_client_rwnd(payload_length);
// Create frame
Http2StreamDebug(ua_session, stream->get_id(), "Send a DATA frame - client
window con: %5zd stream: %5zd payload: %5zd",
- client_rwnd, stream->client_rwnd, payload_length);
+ _client_rwnd, stream->client_rwnd(), payload_length);
Http2Frame data(HTTP2_FRAME_TYPE_DATA, stream->get_id(), flags);
data.alloc(buffer_size_index[HTTP2_FRAME_TYPE_DATA]);
@@ -1630,6 +1673,7 @@ Http2ConnectionState::send_rst_stream_frame(Http2StreamId
id, Http2ErrorCode ec)
if (ec != Http2ErrorCode::HTTP2_ERROR_NO_ERROR) {
HTTP2_INCREMENT_THREAD_DYN_STAT(HTTP2_STAT_STREAM_ERRORS_COUNT,
this_ethread());
+ ++stream_error_count;
}
Http2Frame rst_stream(HTTP2_FRAME_TYPE_RST_STREAM, id, 0);
@@ -1788,6 +1832,72 @@ Http2ConnectionState::get_received_settings_count()
return this->settings_count[0] + this->settings_count[1];
}
+void
+Http2ConnectionState::increment_received_settings_frame_count()
+{
+ ink_hrtime hrtime_sec = Thread::get_hrtime() / HRTIME_SECOND;
+ uint8_t counter_index = ((hrtime_sec % 60) >= 30);
+
+ if ((hrtime_sec - 60) > this->settings_frame_count_last_update) {
+ this->settings_frame_count[0] = 0;
+ this->settings_frame_count[1] = 0;
+ } else if (counter_index != ((this->settings_frame_count_last_update % 60)
>= 30)) {
+ this->settings_frame_count[counter_index] = 0;
+ }
+ ++this->settings_frame_count[counter_index];
+ this->settings_frame_count_last_update = hrtime_sec;
+}
+
+uint32_t
+Http2ConnectionState::get_received_settings_frame_count()
+{
+ return this->settings_frame_count[0] + this->settings_frame_count[1];
+}
+
+void
+Http2ConnectionState::increment_received_ping_frame_count()
+{
+ ink_hrtime hrtime_sec = Thread::get_hrtime() / HRTIME_SECOND;
+ uint8_t counter_index = ((hrtime_sec % 60) >= 30);
+
+ if ((hrtime_sec - 60) > this->ping_frame_count_last_update) {
+ this->ping_frame_count[0] = 0;
+ this->ping_frame_count[1] = 0;
+ } else if (counter_index != ((this->ping_frame_count_last_update % 60) >=
30)) {
+ this->ping_frame_count[counter_index] = 0;
+ }
+ ++this->ping_frame_count[counter_index];
+ this->ping_frame_count_last_update = hrtime_sec;
+}
+
+uint32_t
+Http2ConnectionState::get_received_ping_frame_count()
+{
+ return this->ping_frame_count[0] + this->ping_frame_count[1];
+}
+
+void
+Http2ConnectionState::increment_received_priority_frame_count()
+{
+ ink_hrtime hrtime_sec = Thread::get_hrtime() / HRTIME_SECOND;
+ uint8_t counter_index = ((hrtime_sec % 60) >= 30);
+
+ if ((hrtime_sec - 60) > this->priority_frame_count_last_update) {
+ this->priority_frame_count[0] = 0;
+ this->priority_frame_count[1] = 0;
+ } else if (counter_index != ((this->priority_frame_count_last_update % 60)
>= 30)) {
+ this->priority_frame_count[counter_index] = 0;
+ }
+ ++this->priority_frame_count[counter_index];
+ this->priority_frame_count_last_update = hrtime_sec;
+}
+
+uint32_t
+Http2ConnectionState::get_received_priority_frame_count()
+{
+ return this->priority_frame_count[0] + this->priority_frame_count[1];
+}
+
// 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
@@ -1820,3 +1930,52 @@ Http2ConnectionState::_adjust_concurrent_stream()
return Http2::max_concurrent_streams_in;
}
+
+ssize_t
+Http2ConnectionState::client_rwnd() const
+{
+ return this->_client_rwnd;
+}
+
+Http2ErrorCode
+Http2ConnectionState::increment_client_rwnd(size_t amount)
+{
+ this->_client_rwnd += amount;
+
+ this->_recent_rwnd_increment[this->_recent_rwnd_increment_index] = amount;
+ ++this->_recent_rwnd_increment_index;
+ this->_recent_rwnd_increment_index %= this->_recent_rwnd_increment.size();
+ double sum = std::accumulate(this->_recent_rwnd_increment.begin(),
this->_recent_rwnd_increment.end(), 0.0);
+ double avg = sum / this->_recent_rwnd_increment.size();
+ if (avg < Http2::min_avg_window_update) {
+ return Http2ErrorCode::HTTP2_ERROR_ENHANCE_YOUR_CALM;
+ }
+ return Http2ErrorCode::HTTP2_ERROR_NO_ERROR;
+}
+
+Http2ErrorCode
+Http2ConnectionState::decrement_client_rwnd(size_t amount)
+{
+ this->_client_rwnd -= amount;
+ return Http2ErrorCode::HTTP2_ERROR_NO_ERROR;
+}
+
+ssize_t
+Http2ConnectionState::server_rwnd() const
+{
+ return this->_server_rwnd;
+}
+
+Http2ErrorCode
+Http2ConnectionState::increment_server_rwnd(size_t amount)
+{
+ this->_server_rwnd += amount;
+ return Http2ErrorCode::HTTP2_ERROR_NO_ERROR;
+}
+
+Http2ErrorCode
+Http2ConnectionState::decrement_server_rwnd(size_t amount)
+{
+ this->_server_rwnd -= amount;
+ return Http2ErrorCode::HTTP2_ERROR_NO_ERROR;
+}
diff --git a/proxy/http2/Http2ConnectionState.h
b/proxy/http2/Http2ConnectionState.h
index a802ed0..d937536 100644
--- a/proxy/http2/Http2ConnectionState.h
+++ b/proxy/http2/Http2ConnectionState.h
@@ -38,6 +38,8 @@ enum class Http2SendDataFrameResult {
DONE,
};
+enum Http2ShutdownState { HTTP2_SHUTDOWN_NONE, HTTP2_SHUTDOWN_NOT_INITIATED,
HTTP2_SHUTDOWN_INITIATED, HTTP2_SHUTDOWN_IN_PROGRESS };
+
class Http2ConnectionSettings
{
public:
@@ -114,8 +116,6 @@ public:
: Continuation(NULL),
ua_session(NULL),
dependency_tree(NULL),
- client_rwnd(HTTP2_INITIAL_WINDOW_SIZE),
- server_rwnd(Http2::initial_window_size),
stream_list(),
latest_streamid_in(0),
latest_streamid_out(0),
@@ -123,6 +123,9 @@ public:
client_streams_in_count(0),
client_streams_out_count(0),
total_client_streams_count(0),
+ stream_error_count(0),
+ _client_rwnd(HTTP2_INITIAL_WINDOW_SIZE),
+ _server_rwnd(Http2::initial_window_size),
continued_stream_id(0),
_scheduled(false),
fini_received(false),
@@ -231,8 +234,22 @@ public:
return client_streams_in_count;
}
- // Connection level window size
- ssize_t client_rwnd, server_rwnd;
+ double
+ get_stream_error_rate() const
+ {
+ int total = get_stream_requests();
+ if (total > 0) {
+ return (double)stream_error_count / (double)total;
+ } else {
+ return 0;
+ }
+ }
+
+ Http2ErrorCode
+ get_shutdown_reason() const
+ {
+ return shutdown_reason;
+ }
// HTTP/2 frame sender
void schedule_stream(Http2Stream *stream);
@@ -271,6 +288,19 @@ public:
void increment_received_settings_count(uint32_t count);
uint32_t get_received_settings_count();
+ void increment_received_settings_frame_count();
+ uint32_t get_received_settings_frame_count();
+ void increment_received_ping_frame_count();
+ uint32_t get_received_ping_frame_count();
+ void increment_received_priority_frame_count();
+ uint32_t get_received_priority_frame_count();
+
+ ssize_t client_rwnd() const;
+ Http2ErrorCode increment_client_rwnd(size_t amount);
+ Http2ErrorCode decrement_client_rwnd(size_t amount);
+ ssize_t server_rwnd() const;
+ Http2ErrorCode increment_server_rwnd(size_t amount);
+ Http2ErrorCode decrement_server_rwnd(size_t amount);
private:
Http2ConnectionState(const Http2ConnectionState &); // noncopyable
@@ -298,10 +328,28 @@ private:
// Counter for current active streams and streams in the process of shutting
down
uint32_t total_client_streams_count;
+ // Counter for stream errors ATS sent
+ uint32_t stream_error_count;
+
+ // Connection level window size
+ ssize_t _client_rwnd;
+ ssize_t _server_rwnd;
+
+ std::vector<size_t> _recent_rwnd_increment = {SIZE_MAX, SIZE_MAX, SIZE_MAX,
SIZE_MAX, SIZE_MAX};
+ int _recent_rwnd_increment_index = 0;
+
// Counter for settings received within last 60 seconds
// Each item holds a count for 30 seconds.
uint16_t settings_count[2] = {0};
ink_hrtime settings_count_last_update = 0;
+ // Counters for frames received within last 60 seconds
+ // Each item in an array holds a count for 30 seconds.
+ uint16_t settings_frame_count[2] = {0};
+ ink_hrtime settings_frame_count_last_update = 0;
+ uint16_t ping_frame_count[2] = {0};
+ ink_hrtime ping_frame_count_last_update = 0;
+ uint16_t priority_frame_count[2] = {0};
+ ink_hrtime priority_frame_count_last_update = 0;
// NOTE: Id of stream which MUST receive CONTINUATION frame.
// - [RFC 7540] 6.2 HEADERS
@@ -314,5 +362,8 @@ private:
bool _scheduled;
bool fini_received;
int recursion;
+ Http2ShutdownState shutdown_state = HTTP2_SHUTDOWN_NONE;
+ Http2ErrorCode shutdown_reason = Http2ErrorCode::HTTP2_ERROR_MAX;
+ Event *shutdown_cont_event = nullptr;
Event *fini_event;
};
diff --git a/proxy/http2/Http2Stream.cc b/proxy/http2/Http2Stream.cc
index 429109e..526a747 100644
--- a/proxy/http2/Http2Stream.cc
+++ b/proxy/http2/Http2Stream.cc
@@ -26,6 +26,8 @@
#include "Http2ClientSession.h"
#include "../http/HttpSM.h"
+#include <numeric>
+
#define Http2StreamDebug(fmt, ...) \
DebugSsn(parent, "http2_stream", "[%" PRId64 "] [%u] " fmt,
parent->connection_id(), this->get_id(), ##__VA_ARGS__);
@@ -889,3 +891,60 @@ Http2Stream::release(IOBufferReader *r)
current_reader = nullptr; // State machine is on its own way down.
this->do_io_close();
}
+
+ssize_t
+Http2Stream::client_rwnd() const
+{
+ return this->_client_rwnd;
+}
+
+Http2ErrorCode
+Http2Stream::increment_client_rwnd(size_t amount)
+{
+ this->_client_rwnd += amount;
+
+ this->_recent_rwnd_increment[this->_recent_rwnd_increment_index] = amount;
+ ++this->_recent_rwnd_increment_index;
+ this->_recent_rwnd_increment_index %= this->_recent_rwnd_increment.size();
+ double sum = std::accumulate(this->_recent_rwnd_increment.begin(),
this->_recent_rwnd_increment.end(), 0.0);
+ double avg = sum / this->_recent_rwnd_increment.size();
+ if (avg < Http2::min_avg_window_update) {
+ return Http2ErrorCode::HTTP2_ERROR_ENHANCE_YOUR_CALM;
+ }
+ return Http2ErrorCode::HTTP2_ERROR_NO_ERROR;
+}
+
+Http2ErrorCode
+Http2Stream::decrement_client_rwnd(size_t amount)
+{
+ this->_client_rwnd -= amount;
+ if (this->_client_rwnd < 0) {
+ return Http2ErrorCode::HTTP2_ERROR_PROTOCOL_ERROR;
+ } else {
+ return Http2ErrorCode::HTTP2_ERROR_NO_ERROR;
+ }
+}
+
+ssize_t
+Http2Stream::server_rwnd() const
+{
+ return this->_server_rwnd;
+}
+
+Http2ErrorCode
+Http2Stream::increment_server_rwnd(size_t amount)
+{
+ this->_server_rwnd += amount;
+ return Http2ErrorCode::HTTP2_ERROR_NO_ERROR;
+}
+
+Http2ErrorCode
+Http2Stream::decrement_server_rwnd(size_t amount)
+{
+ this->_server_rwnd -= amount;
+ if (this->_server_rwnd < 0) {
+ return Http2ErrorCode::HTTP2_ERROR_PROTOCOL_ERROR;
+ } else {
+ return Http2ErrorCode::HTTP2_ERROR_NO_ERROR;
+ }
+}
diff --git a/proxy/http2/Http2Stream.h b/proxy/http2/Http2Stream.h
index ddbf7b4..c67efb6 100644
--- a/proxy/http2/Http2Stream.h
+++ b/proxy/http2/Http2Stream.h
@@ -39,9 +39,7 @@ class Http2Stream : public ProxyClientTransaction
public:
typedef ProxyClientTransaction super; ///< Parent type.
Http2Stream(Http2StreamId sid = 0, ssize_t initial_rwnd =
Http2::initial_window_size)
- : client_rwnd(initial_rwnd),
- server_rwnd(Http2::initial_window_size),
- header_blocks(NULL),
+ : header_blocks(NULL),
header_blocks_length(0),
request_header_length(0),
recv_end_stream(false),
@@ -58,6 +56,8 @@ public:
_thread(NULL),
_id(sid),
_state(Http2StreamState::HTTP2_STREAM_STATE_IDLE),
+ _client_rwnd(initial_rwnd),
+ _server_rwnd(Http2::initial_window_size),
cross_thread_event(NULL),
active_timeout(0),
active_event(NULL),
@@ -73,10 +73,10 @@ public:
void
init(Http2StreamId sid, ssize_t initial_rwnd)
{
- _id = sid;
- _start_time = Thread::get_hrtime();
- _thread = this_ethread();
- this->client_rwnd = initial_rwnd;
+ _id = sid;
+ _start_time = Thread::get_hrtime();
+ _thread = this_ethread();
+ this->_client_rwnd = initial_rwnd;
HTTP2_INCREMENT_THREAD_DYN_STAT(HTTP2_STAT_CURRENT_CLIENT_STREAM_COUNT,
_thread);
HTTP2_INCREMENT_THREAD_DYN_STAT(HTTP2_STAT_TOTAL_CLIENT_STREAM_COUNT,
_thread);
sm_reader = request_reader = request_buffer.alloc_reader();
@@ -144,7 +144,7 @@ public:
void
update_initial_rwnd(Http2WindowSize new_size)
{
- client_rwnd = new_size;
+ this->_client_rwnd = new_size;
}
bool
@@ -191,7 +191,12 @@ public:
void push_promise(URL &url, const MIMEField *accept_encoding);
// Stream level window size
- ssize_t client_rwnd, server_rwnd;
+ ssize_t client_rwnd() const;
+ Http2ErrorCode increment_client_rwnd(size_t amount);
+ Http2ErrorCode decrement_client_rwnd(size_t amount);
+ ssize_t server_rwnd() const;
+ Http2ErrorCode increment_server_rwnd(size_t amount);
+ Http2ErrorCode decrement_server_rwnd(size_t amount);
uint8_t *header_blocks;
uint32_t header_blocks_length; // total length of header blocks (not include
@@ -307,6 +312,12 @@ private:
uint64_t data_length = 0;
uint64_t bytes_sent = 0;
+ ssize_t _client_rwnd;
+ ssize_t _server_rwnd = Http2::initial_window_size;
+
+ std::vector<size_t> _recent_rwnd_increment = {SIZE_MAX, SIZE_MAX, SIZE_MAX,
SIZE_MAX, SIZE_MAX};
+ int _recent_rwnd_increment_index = 0;
+
ChunkedHandler chunked_handler;
Event *cross_thread_event = nullptr;
Event *buffer_full_write_event = nullptr;