Skip to content
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
fdd1442
Configure codec library max request header limits
Feb 4, 2019
932eaf0
Add hd to dictionary
Feb 6, 2019
999b247
Undo http1 codec changes
Feb 6, 2019
6e2e092
set http_parser limit in test
Feb 6, 2019
1ba5e19
Remove http2options for nghttp2 check
Feb 7, 2019
51e8fb7
Fix format
Feb 13, 2019
37d6900
add comment
Feb 14, 2019
8c1be4a
Clean up codec tests and bump limit to 96
Feb 14, 2019
d3e146d
Merge remote-tracking branch 'envoy/master' into maxer
Feb 14, 2019
d14f019
Add protocol tests
Feb 15, 2019
7fae643
Revert "Undo http1 codec changes"
Feb 15, 2019
6c6cd5c
Revert "Revert "Undo http1 codec changes""
Feb 20, 2019
87cd65b
set http parser in conn manager config
Feb 20, 2019
12cb34d
Finalize tests
Feb 20, 2019
2370471
Fix format and rmeove dead tests
Feb 20, 2019
7aba9ea
Move http_parser config to compile-time
Feb 22, 2019
dc0b47e
Remove request header rejected tests
Feb 22, 2019
e241e35
Clean comment
Feb 22, 2019
cff066e
bump up nghttp2 huge
Feb 22, 2019
358fd16
Clean test values for large headers
Feb 22, 2019
bbf0df2
matt comments
Feb 26, 2019
c8b006e
word
Feb 26, 2019
8bb762c
Revert "Remove request header rejected tests"
Feb 26, 2019
3bcfbdf
hard coded check w test
Feb 27, 2019
56644a1
Revert "Revert "Undo http1 codec changes""
Feb 27, 2019
7ece1c7
fix compile
Feb 27, 2019
5210dda
fix format
Feb 27, 2019
0f7911c
fix more compile
Feb 27, 2019
9bce125
fix compile more
Feb 27, 2019
7fb8701
fix bork test
Feb 28, 2019
19d10a2
fix format
Feb 28, 2019
b55c483
pr comments
Feb 28, 2019
6473a65
mattfix
Mar 1, 2019
e084dc7
Fix response side client connection
Mar 1, 2019
c7ec56b
fix format
Mar 1, 2019
f520e89
idk
Mar 1, 2019
4a5d64e
pr comments
Mar 4, 2019
9a20bb5
pr comments
Mar 4, 2019
bfbeed4
fix fuzz test
Mar 4, 2019
e28fcdb
update comments
Mar 4, 2019
be85a3f
Merge remote-tracking branch 'envoy/master' into maxer
Mar 4, 2019
b5ba8ef
pr change
Mar 5, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -134,13 +134,15 @@ message HttpConnectionManager {
string server_name = 10;

// The maximum request headers size for incoming connections. The default max
// is 60K, based on default settings for http codecs. For HTTP1, the current
// limit set by http_parser is 80K. for HTTP2, the default allowed header
// block in nghttp2 is 64K. The max configurable setting is 64K in order to
// is 60 KiB, based on default settings for http codecs. For HTTP1, the currentB
Comment thread
aunu53 marked this conversation as resolved.
Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given the new changes, are these sizes still correct/relevant? Isn't is basically fully configurable now on both codecs or at least up to 96KiB max below? Can you potentially simplify the docs?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. Simplified the docs.
  2. Removed the bit on per k/v pair limits, I want to work on adding more thorough and cross-codec regression testing around that before it's documented.

// limit set by http_parser is 80 KiB. for HTTP2, the default allowed header
// block in nghttp2 is 64 KiB. The max configurable setting is 64K in order to
// stay under both codec limits.
// The maximum per-header limit, i.e. for each key-value pair, is 64 KiB in
// http2.
// Requests that exceed this size will receive a 431 response.
google.protobuf.UInt32Value max_request_headers_kb = 29
[(validate.rules).uint32.gt = 0, (validate.rules).uint32.lte = 64];
[(validate.rules).uint32.gt = 0, (validate.rules).uint32.lte = 96];

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Worth commenting on the new max?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a note in the API.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, worth commenting the reasons behind the max?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ummmm, a prior version had more detail about the reason behind the max, but I removed the detail because it wasn't really a "public API" reason.

basically 96 kb is the highest I could get it to go without triggering a different nghttp2 bug, but I stopped trying to debug at that point so I don't know what actually breaks.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think documenting somewhere that we can't go above 96 and have nghttp2 work is worthwhile.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry to come back to this, but can you say something like "The max configurable limit is 96 KiB which is gated by the current implementation details." And then put a comment about whatever nghttp2 issues you had somewhere in the code? I don't think nghttp2 should show up in the docs, it's not relevant to the end user. If you want to put the comment here you can also put in a doc comment (see other examples) which won't get rendered.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1 - that works for me as a compromise

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@alyssawilk could you please describe a bit why such hard limit has been chosen?
Is there any internal implementation issues with higher limit?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See the comment thread above. IIRC there was some problem getting higher limits to work either in the tests or in prod code which would need debugging to allow raising the limits higher. I agree from a product perspective we should allow higher limits.


// The idle timeout for connections managed by the connection manager. The
// idle timeout is defined as the period in which there are no active
Expand Down
1 change: 1 addition & 0 deletions bazel/external/http-parser.BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ cc_library(
"http_parser.h",
],
hdrs = ["http_parser.h"],
copts = ["-DHTTP_MAX_HEADER_SIZE=0x7fffffff"],

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add a comment on why we set this and why it's safe (we have checks elsewhere, etc.)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, don't you need HTTP/1 codec changes now to check the current header map size on each new header as we do with HTTP/2? I'm not sure how this is safe otherwise?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done on the comment, working on the http1 codec check

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also as an aside, when this lands please ping the googlers on current and next import duty as this will almost certainly need custom tweaks on import.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ack

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually sorry, on second thought I'm having a moment of paranoia. We don't have anywhere else in the Envoy code base where we use http_parser so this is 100% OK today but what do we think of adding a limit just in case? If we added a 200KB limit or some such it'd be very safely above our window such that we should never hit the http_parser limits in the codec, without allowing total infinite memory if someone else uses http parser.
Thoughts?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I changed it to 0xfffffff, i.e. ~2 gb -> ~300 mb.

includes = ["."],
visibility = ["//visibility:public"],
)
2 changes: 2 additions & 0 deletions source/common/http/http2/codec_impl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -867,6 +867,8 @@ ConnectionImpl::Http2Options::Http2Options(const Http2Settings& http2_settings)
// of kept alive HTTP/2 connections.
nghttp2_option_set_no_closed_streams(options_, 1);
nghttp2_option_set_no_auto_window_update(options_, 1);
nghttp2_option_set_max_send_header_block_length(options_,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't this the setting for the size of an individual header frame? I think we want to change the total allowed headers, not the per-frame limits.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IIRC this is not very well named, but I think this does actually set the header send limit, and the library will chunk it into frames using continuation frames if necessary. @auni53 to check me here.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could be. I'm not seeing any other size-related fields to tweak. I'd checked
https://nghttp2.org/documentation/nghttp2_option_set_max_send_header_block_length.html
"This option sets the maximum length of header block (a set of header fields per one HEADERS frame) to send. "
It was the "per one HEADERS frame" that got me thinking it might be the wrong field.
in code it's used in prep_frame which I thought was after the chunking but I could easily be misremembering. I'm happy to let Auni do the code sleuthing though :-)

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ahh,

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeeee, I'm pretty certain this is the right call. Also because I did this TDD-style and the testing behaviour checks out.

NGHTTP2_MAX_SEND_HEADER_BLOCK_LENGTH * 1024);

if (http2_settings.hpack_table_size_ != NGHTTP2_DEFAULT_HEADER_TABLE_SIZE) {
nghttp2_option_set_max_deflate_dynamic_table_size(options_, http2_settings.hpack_table_size_);
Expand Down
5 changes: 5 additions & 0 deletions source/common/http/http2/codec_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,11 @@ const std::string ALPN_STRING = "h2";
// differentiate between HTTP/1 and HTTP/2.
const std::string CLIENT_MAGIC_PREFIX = "PRI * HTTP/2";

// This setting will be passed to the nghttp2 library's max request headers check. It is set to an
// arbitrarily high number so as to never trigger the check, as we check request headers length in
// codec_impl::saveHeader.
static const uint32_t NGHTTP2_MAX_SEND_HEADER_BLOCK_LENGTH = 0x7fffffff;
Comment thread
aunu53 marked this conversation as resolved.
Outdated

/**
* All stats for the HTTP/2 codec. @see stats_macros.h
*/
Expand Down
31 changes: 21 additions & 10 deletions test/common/http/http1/codec_impl_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1007,11 +1007,9 @@ TEST_F(Http1ClientConnectionImplTest, HighwatermarkMultipleResponses) {
->onUnderlyingConnectionBelowWriteBufferLowWatermark();
}

// For issue #1421 regression test that Envoy's HTTP parser applies header limits early.
TEST_F(Http1ServerConnectionImplTest, TestCodecHeaderLimits) {
TEST_F(Http1ServerConnectionImplTest, TestLargeRequestHeadersAccepted) {
initialize();

std::string exception_reason;
NiceMock<Http::MockStreamDecoder> decoder;
Http::StreamEncoder* response_encoder = nullptr;
EXPECT_CALL(callbacks_, newStream(_, _))
Expand All @@ -1022,14 +1020,27 @@ TEST_F(Http1ServerConnectionImplTest, TestCodecHeaderLimits) {

Buffer::OwnedImpl buffer("GET / HTTP/1.1\r\n");
codec_->dispatch(buffer);
std::string long_string = "foo: " + std::string(1024, 'q') + "\r\n";
for (int i = 0; i < 79; ++i) {
buffer = Buffer::OwnedImpl(long_string);
codec_->dispatch(buffer);
}
std::string long_string = "big: " + std::string(64 * 1024, 'q') + "\r\n";
buffer = Buffer::OwnedImpl(long_string);
codec_->dispatch(buffer);
}

TEST_F(Http1ServerConnectionImplTest, TestLargeRequestHeadersAcceptedMaxConfigurable) {
initialize();

NiceMock<Http::MockStreamDecoder> decoder;
Http::StreamEncoder* response_encoder = nullptr;
EXPECT_CALL(callbacks_, newStream(_, _))
.WillOnce(Invoke([&](Http::StreamEncoder& encoder, bool) -> Http::StreamDecoder& {
response_encoder = &encoder;
return decoder;
}));

Buffer::OwnedImpl buffer("GET / HTTP/1.1\r\n");
codec_->dispatch(buffer);
std::string long_string = "big: " + std::string(95 * 1024, 'q') + "\r\n";
buffer = Buffer::OwnedImpl(long_string);
EXPECT_THROW_WITH_MESSAGE(codec_->dispatch(buffer), EnvoyException,
"http/1.1 protocol error: HPE_HEADER_OVERFLOW");
codec_->dispatch(buffer);
}

} // namespace Http1
Expand Down
74 changes: 70 additions & 4 deletions test/common/http/http2/codec_impl_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ class Http2CodecImplTest : public TestBaseWithParam<Http2SettingsTestParam> {
setupDefaultConnectionMocks();

EXPECT_CALL(server_callbacks_, newStream(_, _))
.WillOnce(Invoke([&](StreamEncoder& encoder, bool) -> StreamDecoder& {
.WillRepeatedly(Invoke([&](StreamEncoder& encoder, bool) -> StreamDecoder& {
response_encoder_ = &encoder;
encoder.getStream().addCallbacks(server_stream_callbacks_);
return request_decoder_;
Expand Down Expand Up @@ -865,7 +865,7 @@ TEST(Http2CodecUtility, reconstituteCrumbledCookies) {
}
}

TEST_P(Http2CodecImplTest, TestLargeHeadersInvokeResetStream) {
TEST_P(Http2CodecImplTest, TestLargeRequestHeadersInvokeResetStream) {
initialize();

TestHeaderMapImpl request_headers;
Expand All @@ -876,7 +876,7 @@ TEST_P(Http2CodecImplTest, TestLargeHeadersInvokeResetStream) {
request_encoder_->encodeHeaders(request_headers, false);
}

TEST_P(Http2CodecImplTest, TestLargeHeadersAcceptedIfConfigured) {
TEST_P(Http2CodecImplTest, TestLargeRequestHeadersAccepted) {
max_request_headers_kb_ = 64;
initialize();

Expand All @@ -890,7 +890,7 @@ TEST_P(Http2CodecImplTest, TestLargeHeadersAcceptedIfConfigured) {
request_encoder_->encodeHeaders(request_headers, false);
}

TEST_P(Http2CodecImplTest, TestLargeHeadersAtLimitAccepted) {
TEST_P(Http2CodecImplTest, TestLargeRequestHeadersAtLimitAccepted) {
uint32_t codec_limit_kb = 64;
max_request_headers_kb_ = codec_limit_kb;
initialize();
Expand All @@ -913,6 +913,72 @@ TEST_P(Http2CodecImplTest, TestLargeHeadersAtLimitAccepted) {
request_encoder_->encodeHeaders(request_headers, true);
}

TEST_P(Http2CodecImplTest, TestLargeRequestHeadersOverDefaultCodecLibraryLimit) {
max_request_headers_kb_ = 66;
initialize();

TestHeaderMapImpl request_headers;
HttpTestUtility::addDefaultHeaders(request_headers);
std::string long_string = std::string(65 * 1024, 'q');
request_headers.addCopy("big", long_string);

EXPECT_CALL(request_decoder_, decodeHeaders_(_, _)).Times(1);
EXPECT_CALL(server_stream_callbacks_, onResetStream(_)).Times(0);
request_encoder_->encodeHeaders(request_headers, true);
}

TEST_P(Http2CodecImplTest, TestLargeRequestHeadersExceedPerHeaderLimit) {
// The name-value pair max is set by NGHTTP2_HD_MAX_NV in lib/nghttp2_hd.h to 64KB, and
// creates a per-request header limit for us in h2. Note that the nghttp2
// calculated byte size will differ from envoy due to H2 compression and frames.

max_request_headers_kb_ = 81;
initialize();

TestHeaderMapImpl request_headers;
HttpTestUtility::addDefaultHeaders(request_headers);
std::string long_string = std::string(80 * 1024, 'q');
request_headers.addCopy("big", long_string);

EXPECT_CALL(request_decoder_, decodeHeaders_(_, _)).Times(0);
EXPECT_CALL(client_callbacks_, onGoAway());
server_->shutdownNotice();
server_->goAway();
request_encoder_->encodeHeaders(request_headers, true);
}

TEST_P(Http2CodecImplTest, TestManyLargeRequestHeadersUnderPerHeaderLimit) {
max_request_headers_kb_ = 81;
initialize();

TestHeaderMapImpl request_headers;
HttpTestUtility::addDefaultHeaders(request_headers);
std::string long_string = std::string(1024, 'q');
for (int i = 0; i < 80; i++) {
request_headers.addCopy(fmt::format("{}", i), long_string);
}

EXPECT_CALL(request_decoder_, decodeHeaders_(_, _)).Times(1);
EXPECT_CALL(server_stream_callbacks_, onResetStream(_)).Times(0);
request_encoder_->encodeHeaders(request_headers, true);
}

TEST_P(Http2CodecImplTest, TestLargeRequestHeadersAtMaxConfigurable) {
max_request_headers_kb_ = 96;
initialize();

TestHeaderMapImpl request_headers;
HttpTestUtility::addDefaultHeaders(request_headers);
std::string long_string = std::string(1024, 'q');
for (int i = 0; i < 95; i++) {
request_headers.addCopy(fmt::format("{}", i), long_string);
}

EXPECT_CALL(request_decoder_, decodeHeaders_(_, _)).Times(1);
EXPECT_CALL(server_stream_callbacks_, onResetStream(_)).Times(0);
request_encoder_->encodeHeaders(request_headers, true);
}

TEST_P(Http2CodecImplTest, TestCodecHeaderCompression) {
initialize();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ TEST_F(HttpConnectionManagerConfigTest, UnixSocketInternalAddress) {
EXPECT_FALSE(config.internalAddressConfig().isInternalAddress(externalIpAddress));
}

TEST_F(HttpConnectionManagerConfigTest, MaxRequestHeadersSizeDefault) {
TEST_F(HttpConnectionManagerConfigTest, MaxRequestHeadersKbDefault) {
const std::string yaml_string = R"EOF(
stat_prefix: ingress_http
route_config:
Expand All @@ -166,7 +166,7 @@ TEST_F(HttpConnectionManagerConfigTest, MaxRequestHeadersSizeDefault) {
EXPECT_EQ(60, config.maxRequestHeadersKb());
}

TEST_F(HttpConnectionManagerConfigTest, MaxRequestHeadersSizeConfigured) {
TEST_F(HttpConnectionManagerConfigTest, MaxRequestHeadersKbConfigured) {
const std::string yaml_string = R"EOF(
stat_prefix: ingress_http
max_request_headers_kb: 16
Expand All @@ -181,6 +181,21 @@ TEST_F(HttpConnectionManagerConfigTest, MaxRequestHeadersSizeConfigured) {
EXPECT_EQ(16, config.maxRequestHeadersKb());
}

TEST_F(HttpConnectionManagerConfigTest, MaxRequestHeadersKbMaxConfigurable) {
const std::string yaml_string = R"EOF(
stat_prefix: ingress_http
max_request_headers_kb: 96
route_config:
name: local_route
http_filters:
- name: envoy.router
)EOF";

HttpConnectionManagerConfig config(parseHttpConnectionManagerFromV2Yaml(yaml_string), context_,
date_provider_, route_config_provider_manager_);
EXPECT_EQ(96, config.maxRequestHeadersKb());
}

// Validated that an explicit zero stream idle timeout disables.
TEST_F(HttpConnectionManagerConfigTest, DisabledStreamIdleTimeout) {
const std::string yaml_string = R"EOF(
Expand Down
4 changes: 0 additions & 4 deletions test/integration/http2_integration_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -430,10 +430,6 @@ TEST_P(Http2IntegrationTest, GrpcRouterNotFound) {

TEST_P(Http2IntegrationTest, GrpcRetry) { testGrpcRetry(); }

TEST_P(Http2IntegrationTest, LargeHeadersInvokeResetStream) { testLargeRequestHeaders(62, 60); }

TEST_P(Http2IntegrationTest, LargeHeadersAcceptedIfConfigured) { testLargeRequestHeaders(62, 63); }

TEST_P(Http2IntegrationTest, BadMagic) {
initialize();
Buffer::OwnedImpl buffer("hello");
Expand Down
2 changes: 1 addition & 1 deletion test/integration/http_protocol_integration.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ struct HttpProtocolTestParams {
//
// typedef HttpProtocolIntegrationTest MyTest
//
// INSTANTIATE_TEST_SUITE_P(Protocols, BufferIntegrationTest,
// INSTANTIATE_TEST_SUITE_P(Protocols, MyTest,
// testing::ValuesIn(HttpProtocolIntegrationTest::getProtocolTestParams()),
// HttpProtocolIntegrationTest::protocolTestParamsToString);
//
Expand Down
4 changes: 0 additions & 4 deletions test/integration/integration_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -468,10 +468,6 @@ TEST_P(IntegrationTest, Connect) {
EXPECT_EQ(normalizeDate(response1), normalizeDate(response2));
}

TEST_P(IntegrationTest, LargeHeadersRejected) { testLargeRequestHeaders(62, 60); }

TEST_P(IntegrationTest, LargeHeadersAccepted) { testLargeRequestHeaders(62, 63); }

TEST_P(IntegrationTest, UpstreamProtocolError) {
initialize();
codec_client_ = makeHttpConnection(lookupPort("http"));
Expand Down
8 changes: 8 additions & 0 deletions test/integration/protocol_integration_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -719,6 +719,14 @@ name: decode-headers-only
EXPECT_EQ(0, upstream_request_->body().length());
}

TEST_P(DownstreamProtocolIntegrationTest, LargeRequestHeadersRejected) {
testLargeRequestHeaders(95, 60);
}

TEST_P(DownstreamProtocolIntegrationTest, LargeRequestHeadersAccepted) {
testLargeRequestHeaders(95, 96);
}

// For tests which focus on downstream-to-Envoy behavior, and don't need to be
// run with both HTTP/1 and HTTP/2 upstreams.
INSTANTIATE_TEST_SUITE_P(Protocols, DownstreamProtocolIntegrationTest,
Expand Down
1 change: 1 addition & 0 deletions tools/spelling_dictionary.txt
Original file line number Diff line number Diff line change
Expand Up @@ -433,6 +433,7 @@ gregs
gzip
hacky
handshaker
hd
hdr
healths
healthz
Expand Down