Skip to content

Commit

Permalink
Merge pull request #3 from PiotrSikora/http2-release-1.2
Browse files Browse the repository at this point in the history
HTTP/2 fixes (release-1.2).
  • Loading branch information
PiotrSikora authored Aug 2, 2019
2 parents cba9f5e + 54b37e2 commit 204283e
Show file tree
Hide file tree
Showing 43 changed files with 2,034 additions and 43 deletions.
58 changes: 58 additions & 0 deletions api/envoy/api/v2/core/protocol.proto
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ message Http1ProtocolOptions {
string default_host_for_http_10 = 3;
}

// [#comment:next free field: 13]
message Http2ProtocolOptions {
// `Maximum table size <https://httpwg.org/specs/rfc7541.html#rfc.section.4.2>`_
// (in octets) that the encoder is permitted to use for the dynamic HPACK table. Valid values
Expand Down Expand Up @@ -91,6 +92,63 @@ message Http2ProtocolOptions {
// docs](https://github.com/envoyproxy/envoy/blob/master/source/docs/h2_metadata.md) for more
// information.
bool allow_metadata = 6;

// Limit the number of pending outbound downstream frames of all types (frames that are waiting to
// be written into the socket). Exceeding this limit triggers flood mitigation and connection is
// terminated. The ``http2.outbound_flood`` stat tracks the number of terminated connections due
// to flood mitigation. The default limit is 10000.
// [#comment:TODO: implement same limits for upstream outbound frames as well.]
google.protobuf.UInt32Value max_outbound_frames = 7 [(validate.rules).uint32 = {gte: 1}];

// Limit the number of pending outbound downstream frames of types PING, SETTINGS and RST_STREAM,
// preventing high memory utilization when receiving continuous stream of these frames. Exceeding
// this limit triggers flood mitigation and connection is terminated. The
// ``http2.outbound_control_flood`` stat tracks the number of terminated connections due to flood
// mitigation. The default limit is 1000.
// [#comment:TODO: implement same limits for upstream outbound frames as well.]
google.protobuf.UInt32Value max_outbound_control_frames = 8 [(validate.rules).uint32 = {gte: 1}];

// Limit the number of consecutive inbound frames of types HEADERS, CONTINUATION and DATA with an
// empty payload and no end stream flag. Those frames have no legitimate use and are abusive, but
// might be a result of a broken HTTP/2 implementation. The `http2.inbound_empty_frames_flood``
// stat tracks the number of connections terminated due to flood mitigation.
// Setting this to 0 will terminate connection upon receiving first frame with an empty payload
// and no end stream flag. The default limit is 1.
// [#comment:TODO: implement same limits for upstream inbound frames as well.]
google.protobuf.UInt32Value max_consecutive_inbound_frames_with_empty_payload = 9;

// Limit the number of inbound PRIORITY frames allowed per each opened stream. If the number
// of PRIORITY frames received over the lifetime of connection exceeds the value calculated
// using this formula::
//
// max_inbound_priority_frames_per_stream * (1 + inbound_streams)
//
// the connection is terminated. The ``http2.inbound_priority_frames_flood`` stat tracks
// the number of connections terminated due to flood mitigation. The default limit is 100.
// [#comment:TODO: implement same limits for upstream inbound frames as well.]
google.protobuf.UInt32Value max_inbound_priority_frames_per_stream = 10;

// Limit the number of inbound WINDOW_UPDATE frames allowed per DATA frame sent. If the number
// of WINDOW_UPDATE frames received over the lifetime of connection exceeds the value calculated
// using this formula::
//
// 1 + 2 * (inbound_streams +
// max_inbound_window_update_frames_per_data_frame_sent * outbound_data_frames)
//
// the connection is terminated. The ``http2.inbound_priority_frames_flood`` stat tracks
// the number of connections terminated due to flood mitigation. The default limit is 10.
// Setting this to 1 should be enough to support HTTP/2 implementations with basic flow control,
// but more complex implementations that try to estimate available bandwidth require at least 2.
// [#comment:TODO: implement same limits for upstream inbound frames as well.]
google.protobuf.UInt32Value max_inbound_window_update_frames_per_data_frame_sent = 11
[(validate.rules).uint32 = {gte: 1}];

// Allows invalid HTTP messaging and headers. When this option is disabled (default), then
// the whole HTTP/2 connection is terminated upon receiving invalid HEADERS frame. However,
// when this option is enabled, only the offending stream is terminated.
//
// See [RFC7540, sec. 8.1](https://tools.ietf.org/html/rfc7540#section-8.1) for details.
bool stream_error_on_invalid_http_messaging = 12;
}

// [#not-implemented-hide:]
Expand Down
5 changes: 5 additions & 0 deletions docs/root/configuration/http_conn_man/stats.rst
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,11 @@ All http2 statistics are rooted at *http2.*

header_overflow, Counter, Total number of connections reset due to the headers being larger than the :ref:`configured value <envoy_api_field_config.filter.network.http_connection_manager.v2.HttpConnectionManager.max_request_headers_kb>`.
headers_cb_no_stream, Counter, Total number of errors where a header callback is called without an associated stream. This tracks an unexpected occurrence due to an as yet undiagnosed bug
inbound_empty_frames_flood, Counter, Total number of connections terminated for exceeding the limit on consecutive inbound frames with an empty payload and no end stream flag. The limit is configured by setting the :ref:`max_consecutive_inbound_frames_with_empty_payload config setting <envoy_api_field_core.Http2ProtocolOptions.max_consecutive_inbound_frames_with_empty_payload>`.
inbound_priority_frames_flood, Counter, Total number of connections terminated for exceeding the limit on inbound frames of type PRIORITY. The limit is configured by setting the :ref:`max_inbound_priority_frames_per_stream config setting <envoy_api_field_core.Http2ProtocolOptions.max_inbound_priority_frames_per_stream>`.
inbound_window_update_frames_flood, Counter, Total number of connections terminated for exceeding the limit on inbound frames of type WINDOW_UPDATE. The limit is configured by setting the :ref:`max_inbound_window_updateframes_per_data_frame_sent config setting <envoy_api_field_core.Http2ProtocolOptions.max_inbound_window_update_frames_per_data_frame_sent>`.
outbound_flood, Counter, Total number of connections terminated for exceeding the limit on outbound frames of all types. The limit is configured by setting the :ref:`max_outbound_frames config setting <envoy_api_field_core.Http2ProtocolOptions.max_outbound_frames>`.
outbound_control_flood, Counter, "Total number of connections terminated for exceeding the limit on outbound frames of types PING, SETTINGS and RST_STREAM. The limit is configured by setting the :ref:`max_outbound_control_frames config setting <envoy_api_field_core.Http2ProtocolOptions.max_outbound_control_frames>`."
rx_messaging_error, Counter, Total number of invalid received frames that violated `section 8 <https://tools.ietf.org/html/rfc7540#section-8>`_ of the HTTP/2 spec. This will result in a *tx_reset*
rx_reset, Counter, Total number of reset stream frames received by Envoy
too_many_header_frames, Counter, Total number of times an HTTP2 connection is reset due to receiving too many headers frames. Envoy currently supports proxying at most one header frame for 100-Continue one non-100 response code header frame and one frame with trailers
Expand Down
12 changes: 12 additions & 0 deletions docs/root/intro/version_history.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,18 @@ Version history

1.11.0 (Pending)
================
1.11.1 (Pending)
================
* http: added mitigation of client initiated atacks that result in flooding of the downstream HTTP/2 connections.
* http: added :ref:`inbound_empty_frames_flood <config_http_conn_man_stats_per_codec>` counter stat to the HTTP/2 codec stats, for tracking number of connections terminated for exceeding the limit on consecutive inbound frames with an empty payload and no end stream flag. The limit is configured by setting the :ref:`max_consecutive_inbound_frames_with_empty_payload config setting <envoy_api_field_core.Http2ProtocolOptions.max_consecutive_inbound_frames_with_empty_payload>`.
* http: added :ref:`inbound_priority_frames_flood <config_http_conn_man_stats_per_codec>` counter stat to the HTTP/2 codec stats, for tracking number of connections terminated for exceeding the limit on inbound PRIORITY frames. The limit is configured by setting the :ref:`max_inbound_priority_frames_per_stream config setting <envoy_api_field_core.Http2ProtocolOptions.max_inbound_priority_frames_per_stream>`.
* http: added :ref:`inbound_window_update_frames_flood <config_http_conn_man_stats_per_codec>` counter stat to the HTTP/2 codec stats, for tracking number of connections terminated for exceeding the limit on inbound WINDOW_UPDATE frames. The limit is configured by setting the :ref:`max_inbound_window_update_frames_per_data_frame_sent config setting <envoy_api_field_core.Http2ProtocolOptions.max_inbound_window_update_frames_per_data_frame_sent>`.
* http: added :ref:`outbound_flood <config_http_conn_man_stats_per_codec>` counter stat to the HTTP/2 codec stats, for tracking number of connections terminated for exceeding the outbound queue limit. The limit is configured by setting the :ref:`max_outbound_frames config setting <envoy_api_field_core.Http2ProtocolOptions.max_outbound_frames>`
* http: added :ref:`outbound_control_flood <config_http_conn_man_stats_per_codec>` counter stat to the HTTP/2 codec stats, for tracking number of connections terminated for exceeding the outbound queue limit for PING, SETTINGS and RST_STREAM frames. The limit is configured by setting the :ref:`max_outbound_control_frames config setting <envoy_api_field_core.Http2ProtocolOptions.max_outbound_control_frames>`.
* http: enabled strict validation of HTTP/2 messaging. Previous behavior can be restored using :ref:`stream_error_on_invalid_http_messaging config setting <envoy_api_field_core.Http2ProtocolOptions.stream_error_on_invalid_http_messaging>`.

1.11.0 (July 11, 2019)
======================
* access log: added a new field for downstream TLS session ID to file and gRPC access logger.
* access log: added a new field for response code details in :ref:`file access logger<config_access_log_format_response_code_details>` and :ref:`gRPC access logger<envoy_api_field_data.accesslog.v2.HTTPResponseProperties.response_code_details>`.
* admin: the administration interface now includes a :ref:`/ready endpoint <operations_admin_interface>` for easier readiness checks.
Expand Down
4 changes: 1 addition & 3 deletions include/envoy/buffer/buffer.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ struct RawSlice {
*/
class BufferFragment {
public:
virtual ~BufferFragment() = default;
/**
* @return const void* a pointer to the referenced data.
*/
Expand All @@ -47,9 +48,6 @@ class BufferFragment {
* Called by a buffer when the referenced data is no longer needed.
*/
virtual void done() PURE;

protected:
virtual ~BufferFragment() {}
};

/**
Expand Down
22 changes: 22 additions & 0 deletions include/envoy/http/codec.h
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,14 @@ struct Http2Settings {
uint32_t initial_connection_window_size_{DEFAULT_INITIAL_CONNECTION_WINDOW_SIZE};
bool allow_connect_{DEFAULT_ALLOW_CONNECT};
bool allow_metadata_{DEFAULT_ALLOW_METADATA};
bool stream_error_on_invalid_http_messaging_{DEFAULT_STREAM_ERROR_ON_INVALID_HTTP_MESSAGING};
uint32_t max_outbound_frames_{DEFAULT_MAX_OUTBOUND_FRAMES};
uint32_t max_outbound_control_frames_{DEFAULT_MAX_OUTBOUND_CONTROL_FRAMES};
uint32_t max_consecutive_inbound_frames_with_empty_payload_{
DEFAULT_MAX_CONSECUTIVE_INBOUND_FRAMES_WITH_EMPTY_PAYLOAD};
uint32_t max_inbound_priority_frames_per_stream_{DEFAULT_MAX_INBOUND_PRIORITY_FRAMES_PER_STREAM};
uint32_t max_inbound_window_update_frames_per_data_frame_sent_{
DEFAULT_MAX_INBOUND_WINDOW_UPDATE_FRAMES_PER_DATA_FRAME_SENT};

// disable HPACK compression
static const uint32_t MIN_HPACK_TABLE_SIZE = 0;
Expand Down Expand Up @@ -272,6 +280,20 @@ struct Http2Settings {
static const bool DEFAULT_ALLOW_CONNECT = false;
// By default Envoy does not allow METADATA support.
static const bool DEFAULT_ALLOW_METADATA = false;
// By default Envoy does not allow invalid headers.
static const bool DEFAULT_STREAM_ERROR_ON_INVALID_HTTP_MESSAGING = false;

// Default limit on the number of outbound frames of all types.
static const uint32_t DEFAULT_MAX_OUTBOUND_FRAMES = 10000;
// Default limit on the number of outbound frames of types PING, SETTINGS and RST_STREAM.
static const uint32_t DEFAULT_MAX_OUTBOUND_CONTROL_FRAMES = 1000;
// Default limit on the number of consecutive inbound frames with an empty payload
// and no end stream flag.
static const uint32_t DEFAULT_MAX_CONSECUTIVE_INBOUND_FRAMES_WITH_EMPTY_PAYLOAD = 1;
// Default limit on the number of inbound frames of type PRIORITY (per stream).
static const uint32_t DEFAULT_MAX_INBOUND_PRIORITY_FRAMES_PER_STREAM = 100;
// Default limit on the number of inbound frames of type WINDOW_UPDATE (per DATA frame sent).
static const uint32_t DEFAULT_MAX_INBOUND_WINDOW_UPDATE_FRAMES_PER_DATA_FRAME_SENT = 10;
};

/**
Expand Down
1 change: 1 addition & 0 deletions source/common/buffer/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ envoy_cc_library(
"//include/envoy/buffer:buffer_interface",
"//source/common/common:non_copyable",
"//source/common/common:stack_array",
"//source/common/common:utility_lib",
"//source/common/event:libevent_lib",
],
)
Expand Down
54 changes: 44 additions & 10 deletions source/common/buffer/buffer_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

#include "common/common/assert.h"
#include "common/common/non_copyable.h"
#include "common/common/utility.h"
#include "common/event/libevent.h"

namespace Envoy {
Expand Down Expand Up @@ -203,7 +204,8 @@ class Slice {

using SlicePtr = std::unique_ptr<Slice>;

class OwnedSlice : public Slice {
// OwnedSlice can not be derived from as it has variable sized array as member.
class OwnedSlice final : public Slice, public InlineStorage {
public:
/**
* Create an empty OwnedSlice.
Expand All @@ -230,16 +232,7 @@ class OwnedSlice : public Slice {
return slice;
}

// Custom delete operator to keep C++14 from using the global operator delete(void*, size_t),
// which would result in the compiler error:
// "exception cleanup for this placement new selects non-placement operator delete"
static void operator delete(void* address) { ::operator delete(address); }

private:
static void* operator new(size_t object_size, size_t data_size) {
return ::operator new(object_size + data_size);
}

OwnedSlice(uint64_t size) : Slice(0, 0, size) { base_ = storage_; }

/**
Expand Down Expand Up @@ -582,5 +575,46 @@ class OwnedImpl : public LibEventInstance {
Event::Libevent::BufferPtr buffer_;
};

using BufferFragmentPtr = std::unique_ptr<BufferFragment>;

/**
* An implementation of BufferFragment where a releasor callback is called when the data is
* no longer needed. Copies data into internal buffer.
*/
class OwnedBufferFragmentImpl final : public BufferFragment, public InlineStorage {
public:
using Releasor = std::function<void(const OwnedBufferFragmentImpl*)>;

/**
* Copies the data into internal buffer. The releasor is called when the data has been
* fully drained or the buffer that contains this fragment is destroyed.
* @param data external data to reference
* @param releasor a callback function to be called when data is no longer needed.
*/

static BufferFragmentPtr create(absl::string_view data, const Releasor& releasor) {
return BufferFragmentPtr(new (sizeof(OwnedBufferFragmentImpl) + data.size())
OwnedBufferFragmentImpl(data, releasor));
}

// Buffer::BufferFragment
const void* data() const override { return data_; }
size_t size() const override { return size_; }
void done() override { releasor_(this); }

private:
OwnedBufferFragmentImpl(absl::string_view data, const Releasor& releasor)
: releasor_(releasor), size_(data.size()) {
ASSERT(releasor != nullptr);
memcpy(data_, data.data(), data.size());
}

const Releasor releasor_;
const size_t size_;
uint8_t data_[];
};

using OwnedBufferFragmentImplPtr = std::unique_ptr<OwnedBufferFragmentImpl>;

} // namespace Buffer
} // namespace Envoy
1 change: 1 addition & 0 deletions source/common/common/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,7 @@ envoy_cc_library(
deps = [
":assert_lib",
":hash_lib",
":non_copyable",
"//include/envoy/common:interval_set_interface",
"//include/envoy/common:time_interface",
"//source/common/singleton:const_singleton",
Expand Down
5 changes: 5 additions & 0 deletions source/common/common/utility.cc
Original file line number Diff line number Diff line change
Expand Up @@ -532,4 +532,9 @@ double WelfordStandardDeviation::computeStandardDeviation() const {
return (std::isnan(variance) || variance < 0) ? std::nan("") : sqrt(variance);
}

InlineString::InlineString(const char* str, size_t size) : size_(size) {
RELEASE_ASSERT(size <= 0xffffffff, "size must fit in 32 bits");
memcpy(data_, str, size);
}

} // namespace Envoy
Loading

0 comments on commit 204283e

Please sign in to comment.