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.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
=====================
*/
inkcoreapi void reenable_re();
+ void disable();
+ bool is_disabled();
+
VIO(int aop);
VIO();
*/
Ptr<ProxyMutex> mutex;
+
+private:
+ bool _disabled = false;
};
#include "I_VConnection.h"
TS_INLINE void
VIO::reenable()
{
+ this->_disabled = false;
if (vc_server) {
vc_server->reenable(this);
}
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;
+}
// 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;
}
}
// 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;
}
}
Debug("ssl", "iterate from reenable curHook=%p %d", curHook, sslHandshakeHookState);
}
+
this->readReschedule(nh);
}
return;
}
// 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;
}
,
{RECT_LOCAL, "proxy.local.log.collation_mode", RECD_INT, "0", RECU_DYNAMIC, RR_NULL, RECC_INT, "[0-4]", 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}
+ ,
//# Librecords based stats system (new as of v2.1.3)
{RECT_CONFIG, "proxy.config.stat_api.max_stats_allowed", RECD_INT, "256", RECU_RESTART_TS, RR_NULL, RECC_INT, "[256-1000]", RECA_NULL}
}
// 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::max_request_header_size = 131072;
-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;
+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::max_request_header_size = 131072;
+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::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()
static uint32_t active_timeout_in;
static uint32_t push_diary_size;
static uint32_t zombie_timeout_in;
+ 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();
};
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);
}
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:
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;
}
Http2SsnDebug("completed frame read, %" PRId64 " bytes available", this->sm_reader->read_avail());
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);
}
while (this->sm_reader->read_avail() >= (int64_t)HTTP2_FRAME_HEADER_LEN) {
+
+ 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());
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
}
return 0;
}
+
+bool
+Http2ClientSession::_should_do_something_else()
+{
+ // Do something else every 128 incoming frames
+ return (this->_n_frame_read & 0x7F) == 0;
+}
#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)
size_t const HTTP2_HEADER_BUFFER_SIZE_INDEX = CLIENT_CONNECTION_FIRST_READ_BUFFER_SIZE_INDEX;
// 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 = 0;
SessionHandler session_handler = nullptr;
NetVConnection *client_vc = nullptr;
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;
#include "Http2Stream.h"
#include "Http2DebugNames.h"
#include <sstream>
+#include <numeric>
#define Http2ConDebug(ua_session, fmt, ...) \
SsnDebug(ua_session, "http2_con", "[%" PRId64 "] " fmt, ua_session->connection_id(), ##__VA_ARGS__);
}
// 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
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);
}
"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);
}
Warning("Setting frame for zombied sessoin %" PRId64, cstate.ua_session->connection_id());
}
+ // 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
"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);
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-
// 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
}
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-
// 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();
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();
}
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();
}
// 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()));
}
}
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;
}
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;
}
// 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]);
this->ua_session->handleEvent(HTTP2_SESSION_EVENT_XMIT, &window_update);
}
+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
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;
+}
return client_streams_in_count;
}
- // Connection level window size
- ssize_t client_rwnd = HTTP2_INITIAL_WINDOW_SIZE;
- ssize_t server_rwnd = Http2::initial_window_size;
+ double
+ get_stream_error_rate() const
+ {
+ int total = get_stream_requests();
+ if (total > 0) {
+ return (double)stream_error_count / (double)total;
+ } else {
+ return 0;
+ }
+ }
// HTTP/2 frame sender
void schedule_stream(Http2Stream *stream);
}
}
+ 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:
unsigned _adjust_concurrent_stream();
Event *shutdown_cont_event = nullptr;
Event *fini_event = nullptr;
Event *zombie_event = nullptr;
+
+ // Counter for stream errors ATS sent
+ uint32_t stream_error_count;
+
+ // Connection level window size
+ ssize_t _client_rwnd = HTTP2_INITIAL_WINDOW_SIZE;
+ 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;
+
+ // 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;
+
};
#include "Http2ClientSession.h"
#include "../http/HttpSM.h"
+#include <numeric>
+
#define Http2StreamDebug(fmt, ...) \
SsnDebug(parent, "http2_stream", "[%" PRId64 "] [%u] " fmt, parent->connection_id(), this->get_id(), ##__VA_ARGS__);
}
}
+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;
+ }
+}
+
bool
Http2Stream::response_is_data_available() const
{
{
public:
typedef ProxyClientTransaction super; ///< Parent type.
- Http2Stream(Http2StreamId sid = 0, ssize_t initial_rwnd = Http2::initial_window_size) : client_rwnd(initial_rwnd), _id(sid)
+ Http2Stream(Http2StreamId sid = 0, ssize_t initial_rwnd = Http2::initial_window_size) : _id(sid), _client_rwnd(initial_rwnd)
{
SET_HANDLER(&Http2Stream::main_event_handler);
}
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();
void
update_initial_rwnd(Http2WindowSize new_size)
{
- client_rwnd = new_size;
+ this->_client_rwnd = new_size;
}
bool
void push_promise(URL &url, const MIMEField *accept_encoding);
// Stream level window size
- ssize_t client_rwnd;
- ssize_t server_rwnd = Http2::initial_window_size;
+ 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 = nullptr;
uint32_t header_blocks_length = 0; // total length of header blocks (not include
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;