Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions changelogs/current.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@ date: Pending

behavior_changes:
# *Changes that are expected to cause an incompatibility if applicable; deployment changes are likely required*
- area: http2
change: |
Discard the ``Host`` header if the ``:authority`` header was received to bring Envoy into compliance with
https://www.rfc-editor.org/rfc/rfc9113#section-8.3.1 This behavioral change can be reverted by setting runtime flag
``envoy.reloadable_features.http2_discard_host_header`` to false.

minor_behavior_changes:
# *Changes that may cause incompatibilities for some users, but should not for most*
Expand Down
12 changes: 12 additions & 0 deletions source/common/http/http2/codec_impl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -2110,6 +2110,18 @@ int ServerConnectionImpl::onHeader(const nghttp2_frame* frame, HeaderString&& na
// For a server connection, we should never get push promise frames.
ASSERT(frame->hd.type == NGHTTP2_HEADERS);
ASSERT(frame->headers.cat == NGHTTP2_HCAT_REQUEST || frame->headers.cat == NGHTTP2_HCAT_HEADERS);
if (Runtime::runtimeFeatureEnabled("envoy.reloadable_features.http2_discard_host_header")) {
StreamImpl* stream = getStreamUnchecked(frame->hd.stream_id);
if (stream && name == static_cast<absl::string_view>(Http::Headers::get().HostLegacy)) {
// Check if there is already the :authority header
const auto result = stream->headers().get(Http::Headers::get().Host);
if (!result.empty()) {
// Discard the host header value
return 0;
}
// Otherwise use host value as :authority
}
}
return saveHeader(frame, std::move(name), std::move(value));
}

Expand Down
1 change: 1 addition & 0 deletions source/common/runtime/runtime_features.cc
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ RUNTIME_GUARD(envoy_reloadable_features_format_ports_as_numbers);
RUNTIME_GUARD(envoy_reloadable_features_handle_uppercase_scheme);
RUNTIME_GUARD(envoy_reloadable_features_http1_allow_codec_error_response_after_1xx_headers);
RUNTIME_GUARD(envoy_reloadable_features_http2_decode_metadata_with_quiche);
RUNTIME_GUARD(envoy_reloadable_features_http2_discard_host_header);
RUNTIME_GUARD(envoy_reloadable_features_http2_validate_authority_with_quiche);
RUNTIME_GUARD(envoy_reloadable_features_http_allow_partial_urls_in_referer);
RUNTIME_GUARD(envoy_reloadable_features_http_ext_auth_failure_mode_allow_header_add);
Expand Down
6 changes: 3 additions & 3 deletions test/common/http/http2/http2_frame.h
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,9 @@ class Http2Frame {
ASSERT(size() >= HeaderSize);
setPayloadSize(size() - HeaderSize);
}
// Headers are directly encoded
void appendStaticHeader(StaticHeaderIndex index);
void appendHeaderWithoutIndexing(StaticHeaderIndex index, absl::string_view value);

private:
void buildHeader(Type type, uint32_t payload_size = 0, uint8_t flags = 0, uint32_t stream_id = 0);
Expand All @@ -277,9 +280,6 @@ class Http2Frame {
std::copy(data.begin(), data.end(), data_.begin() + 9);
}

// Headers are directly encoded
void appendStaticHeader(StaticHeaderIndex index);
void appendHeaderWithoutIndexing(StaticHeaderIndex index, absl::string_view value);
void appendEmptyHeader();

DataContainer data_;
Expand Down
77 changes: 77 additions & 0 deletions test/integration/multiplexed_integration_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -2129,7 +2129,84 @@ TEST_P(Http2FrameIntegrationTest, AccessLogOfWireBytesIfResponseSizeGreaterThanW
// Cleanup.
tcp_client_->close();
}
TEST_P(Http2FrameIntegrationTest, HostDifferentFromAuthority) {
beginSession();

uint32_t request_idx = 0;
auto request = Http2Frame::makeRequest(Http2Frame::makeClientStreamId(request_idx),
"one.example.com", "/path", {{"host", "two.example.com"}});
sendFrame(request);

waitForNextUpstreamRequest();
EXPECT_EQ(upstream_request_->headers().getHostValue(), "one.example.com");
upstream_request_->encodeHeaders(default_response_headers_, true);
auto frame = readFrame();
EXPECT_EQ(Http2Frame::Type::Headers, frame.type());
EXPECT_EQ(Http2Frame::ResponseStatus::Ok, frame.responseStatus());
tcp_client_->close();
}

TEST_P(Http2FrameIntegrationTest, HostSameAsAuthority) {
beginSession();

uint32_t request_idx = 0;
auto request = Http2Frame::makeRequest(Http2Frame::makeClientStreamId(request_idx),
"one.example.com", "/path", {{"host", "one.example.com"}});
sendFrame(request);

waitForNextUpstreamRequest();
EXPECT_EQ(upstream_request_->headers().getHostValue(), "one.example.com");
upstream_request_->encodeHeaders(default_response_headers_, true);
auto frame = readFrame();
EXPECT_EQ(Http2Frame::Type::Headers, frame.type());
EXPECT_EQ(Http2Frame::ResponseStatus::Ok, frame.responseStatus());
tcp_client_->close();
}
Comment on lines +2132 to +2164
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.

i think these 2 tests have been added in this pr - i guess because the original pr made alterations to them


TEST_P(Http2FrameIntegrationTest, HostConcatenatedWithAuthorityWithOverride) {
config_helper_.addRuntimeOverride("envoy.reloadable_features.http2_discard_host_header", "false");
beginSession();

uint32_t request_idx = 0;
auto request = Http2Frame::makeRequest(Http2Frame::makeClientStreamId(request_idx),
"one.example.com", "/path", {{"host", "two.example.com"}});
sendFrame(request);

waitForNextUpstreamRequest();
EXPECT_EQ(upstream_request_->headers().getHostValue(), "one.example.com,two.example.com");
upstream_request_->encodeHeaders(default_response_headers_, true);
auto frame = readFrame();
EXPECT_EQ(Http2Frame::Type::Headers, frame.type());
EXPECT_EQ(Http2Frame::ResponseStatus::Ok, frame.responseStatus());
tcp_client_->close();
}

// All HTTP/2 static headers must be before non-static headers.
// Verify that codecs validate this.
TEST_P(Http2FrameIntegrationTest, HostBeforeAuthorityIsRejected) {
#ifdef ENVOY_ENABLE_UHV
// TODO(yanavlasov): fix this check for oghttp2 in UHV mode.
return;
#endif
beginSession();

Http2Frame request = Http2Frame::makeEmptyHeadersFrame(Http2Frame::makeClientStreamId(0),
Http2Frame::HeadersFlags::EndHeaders);
request.appendStaticHeader(Http2Frame::StaticHeaderIndex::MethodPost);
request.appendStaticHeader(Http2Frame::StaticHeaderIndex::SchemeHttps);
request.appendHeaderWithoutIndexing(Http2Frame::StaticHeaderIndex::Path, "/path");
// Add the `host` header before `:authority`
request.appendHeaderWithoutIndexing({"host", "two.example.com"});
request.appendHeaderWithoutIndexing(Http2Frame::StaticHeaderIndex::Authority, "one.example.com");
request.adjustPayloadSize();

sendFrame(request);

// By default codec treats stream errors as protocol errors and closes the connection.
tcp_client_->waitForDisconnect();
tcp_client_->close();
EXPECT_EQ(1, test_server_->counter("http.config_test.downstream_cx_protocol_error")->value());
}
TEST_P(Http2FrameIntegrationTest, MultipleHeaderOnlyRequests) {
const int kRequestsSentPerIOCycle = 20;
autonomous_upstream_ = true;
Expand Down