Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Do not 503 on Upgrade: h2c instead remove the header and ignore. #7981

Merged
merged 38 commits into from
Sep 4, 2019
Merged
Show file tree
Hide file tree
Changes from 35 commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
a27ef3f
Do not 503 on Upgrade: h2c instead remove the header and ignore.
jplevyak Aug 20, 2019
36cd5ca
Add release note.
jplevyak Aug 20, 2019
66243c3
Address comments.
jplevyak Aug 20, 2019
b516bd7
Address comments.
jplevyak Aug 21, 2019
215e3aa
Address comments.
jplevyak Aug 21, 2019
b563518
Address comments.
jplevyak Aug 21, 2019
b5ea6c6
Address comments.
jplevyak Aug 21, 2019
34632d5
Address comments.
jplevyak Aug 21, 2019
92726a5
Address comments.
jplevyak Aug 21, 2019
a82cc9d
Address comments.
jplevyak Aug 21, 2019
30ef294
Address comments.
jplevyak Aug 21, 2019
9d3a216
Address comments.
jplevyak Aug 21, 2019
1e1ab46
Address comments.
jplevyak Aug 21, 2019
11dbe74
Merge remote-tracking branch 'envoyproxy/master' into ignore-upgrade-h2c
jplevyak Aug 21, 2019
ad6c698
Address comments.
jplevyak Aug 22, 2019
51e0372
Address comments.
jplevyak Aug 22, 2019
069b642
Address comments.
jplevyak Aug 22, 2019
a2ac07f
Address comments.
jplevyak Aug 22, 2019
756224b
Address comments.
jplevyak Aug 22, 2019
5d0a1a6
Address comments.
jplevyak Aug 22, 2019
561650a
Fix clang-tidy error.
jplevyak Aug 22, 2019
427b0aa
Merge remote-tracking branch 'envoyproxy/master' into ignore-upgrade-h2c
jplevyak Aug 26, 2019
cecc97f
Address comments.
jplevyak Aug 26, 2019
53134ef
Rewritten to use <algorithm> as requested.
jplevyak Aug 27, 2019
7378c2e
Merge remote-tracking branch 'envoyproxy/master' into ignore-upgrade-h2c
jplevyak Aug 27, 2019
150f6fd
Fix test.
jplevyak Aug 27, 2019
bba47af
Address comments.
jplevyak Aug 27, 2019
f6c0797
Address comments.
jplevyak Aug 27, 2019
84a2f7b
Address comments.
jplevyak Aug 27, 2019
b79eb9c
Address comments.
jplevyak Aug 27, 2019
46b6993
Remove unnecessary include.
jplevyak Aug 27, 2019
973aa7f
Merge remote-tracking branch 'envoyproxy/master' into ignore-upgrade-h2c
jplevyak Aug 28, 2019
a2e7210
Merge remote-tracking branch 'envoyproxy/master' into ignore-upgrade-h2c
jplevyak Aug 28, 2019
e230448
Address comments.
jplevyak Aug 29, 2019
f97827f
Format
jplevyak Aug 29, 2019
9663c31
Merge remote-tracking branch 'envoyproxy/master' into ignore-upgrade-h2c
jplevyak Aug 30, 2019
73a2b9c
Merge remote-tracking branch 'envoyproxy/master' into ignore-upgrade-h2c
jplevyak Sep 4, 2019
af9d672
Merge remote-tracking branch 'envoyproxy/master' into ignore-upgrade-h2c
jplevyak Sep 4, 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
1 change: 1 addition & 0 deletions docs/root/intro/version_history.rst
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ Version history
* http: added the ability to configure the behavior of the server response header, via the :ref:`server_header_transformation<envoy_api_field_config.filter.network.http_connection_manager.v2.HttpConnectionManager.server_header_transformation>` field.
* http: changed Envoy to forward existing x-forwarded-proto from downstream trusted proxies. Guarded by `envoy.reloadable_features.trusted_forwarded_proto` which defaults true.
* http: added the ability to :ref:`merge adjacent slashes<envoy_api_field_config.filter.network.http_connection_manager.v2.HttpConnectionManager.merge_slashes>` in the path.
* http: remove h2c upgrade headers for HTTP/1 as h2c upgrades are currently not supported.
* listeners: added :ref:`continue_on_listener_filters_timeout <envoy_api_field_Listener.continue_on_listener_filters_timeout>` to configure whether a listener will still create a connection when listener filters time out.
* listeners: added :ref:`HTTP inspector listener filter <config_listener_filters_http_inspector>`.
* lua: extended `httpCall()` and `respond()` APIs to accept headers with entry values that can be a string or table of strings.
Expand Down
10 changes: 10 additions & 0 deletions source/common/common/utility.cc
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,16 @@ std::vector<absl::string_view> StringUtil::splitToken(absl::string_view source,
return absl::StrSplit(source, absl::ByAnyChar(delimiters), absl::SkipEmpty());
}

std::string StringUtil::removeTokens(absl::string_view source, absl::string_view delimiters,
alyssawilk marked this conversation as resolved.
Show resolved Hide resolved
const CaseUnorderedSet& tokens_to_remove,
absl::string_view joiner) {
auto values = Envoy::StringUtil::splitToken(source, delimiters);
jplevyak marked this conversation as resolved.
Show resolved Hide resolved
std::for_each(values.begin(), values.end(), [](auto& v) { v = StringUtil::trim(v); });
auto end = std::remove_if(values.begin(), values.end(),
[&](absl::string_view t) { return tokens_to_remove.count(t) != 0; });
return absl::StrJoin(values.begin(), end, joiner);
}

uint32_t StringUtil::itoa(char* out, size_t buffer_size, uint64_t i) {
// The maximum size required for an unsigned 64-bit integer is 21 chars (including null).
if (buffer_size < 21) {
Expand Down
72 changes: 43 additions & 29 deletions source/common/common/utility.h
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,35 @@ class DateUtil {
*/
class StringUtil {
public:
/**
* Callable struct that returns the result of string comparison ignoring case.
* @param lhs supplies the first string view.
* @param rhs supplies the second string view.
* @return true if strings are semantically the same and false otherwise.
*/
struct CaseInsensitiveCompare {
// Enable heterogeneous lookup (https://abseil.io/tips/144)
using is_transparent = void;
bool operator()(absl::string_view lhs, absl::string_view rhs) const;
};

/**
* Callable struct that returns the hash representation of a case-insensitive string_view input.
* @param key supplies the string view.
* @return uint64_t hash representation of the supplied string view.
*/
struct CaseInsensitiveHash {
// Enable heterogeneous lookup (https://abseil.io/tips/144)
using is_transparent = void;
uint64_t operator()(absl::string_view key) const;
};

/**
* Definition of unordered set of case-insensitive std::string.
*/
using CaseUnorderedSet =
absl::flat_hash_set<std::string, CaseInsensitiveHash, CaseInsensitiveCompare>;

static const char WhitespaceChars[];

/**
Expand Down Expand Up @@ -281,6 +310,20 @@ class StringUtil {
absl::string_view delimiters,
bool keep_empty_string = false);

/**
* Remove tokens from a delimiter-separated string view. The tokens are trimmed before
* they are compared ignoring case with the elements of 'tokens_to_remove'. The output is
* built from the trimmed tokens preserving case.
* @param source supplies the delimiter-separated string view.
* @param multi-delimiters supplies chars used to split the delimiter-separated string view.
* @param tokens_to_remove supplies a set of tokens which should not appear in the result.
* @param joiner contains a string used between tokens in the result.
* @return string of the remaining joined tokens.
*/
static std::string removeTokens(absl::string_view source, absl::string_view delimiters,
const CaseUnorderedSet& tokens_to_remove,
absl::string_view joiner);

/**
* Size-bounded string copying and concatenation
*/
Expand Down Expand Up @@ -332,35 +375,6 @@ class StringUtil {
*/
static std::string toLower(absl::string_view s);

/**
* Callable struct that returns the result of string comparison ignoring case.
* @param lhs supplies the first string view.
* @param rhs supplies the second string view.
* @return true if strings are semantically the same and false otherwise.
*/
struct CaseInsensitiveCompare {
// Enable heterogeneous lookup (https://abseil.io/tips/144)
using is_transparent = void;
bool operator()(absl::string_view lhs, absl::string_view rhs) const;
};

/**
* Callable struct that returns the hash representation of a case-insensitive string_view input.
* @param key supplies the string view.
* @return uint64_t hash representation of the supplied string view.
*/
struct CaseInsensitiveHash {
// Enable heterogeneous lookup (https://abseil.io/tips/144)
using is_transparent = void;
uint64_t operator()(absl::string_view key) const;
};

/**
* Definition of unordered set of case-insensitive std::string.
*/
using CaseUnorderedSet =
absl::flat_hash_set<std::string, CaseInsensitiveHash, CaseInsensitiveCompare>;

/**
* Removes all the character indices from str contained in the interval-set.
* @param str the string containing the characters to be removed.
Expand Down
3 changes: 3 additions & 0 deletions source/common/http/headers.h
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ class HeaderValues {
const LowerCaseString GrpcAcceptEncoding{"grpc-accept-encoding"};
const LowerCaseString Host{":authority"};
const LowerCaseString HostLegacy{"host"};
const LowerCaseString Http2Settings{"http2-settings"};
const LowerCaseString KeepAlive{"keep-alive"};
const LowerCaseString LastModified{"last-modified"};
const LowerCaseString Location{"location"};
Expand Down Expand Up @@ -153,11 +154,13 @@ class HeaderValues {

struct {
const std::string Close{"close"};
const std::string Http2Settings{"http2-settings"};
const std::string KeepAlive{"keep-alive"};
const std::string Upgrade{"upgrade"};
} ConnectionValues;

struct {
const std::string H2c{"h2c"};
const std::string WebSocket{"websocket"};
} UpgradeValues;

Expand Down
33 changes: 31 additions & 2 deletions source/common/http/http1/codec_impl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,15 @@
namespace Envoy {
namespace Http {
namespace Http1 {
namespace {

const StringUtil::CaseUnorderedSet& caseUnorderdSetContainingUpgradeAndHttp2Settings() {
CONSTRUCT_ON_FIRST_USE(StringUtil::CaseUnorderedSet,
Http::Headers::get().ConnectionValues.Upgrade,
Http::Headers::get().ConnectionValues.Http2Settings);
}

} // namespace

const std::string StreamEncoderImpl::CRLF = "\r\n";
const std::string StreamEncoderImpl::LAST_CHUNK = "0\r\n\r\n";
Expand Down Expand Up @@ -469,8 +478,28 @@ int ConnectionImpl::onHeadersCompleteBase() {
protocol_ = Protocol::Http10;
}
if (Utility::isUpgrade(*current_header_map_)) {
ENVOY_CONN_LOG(trace, "codec entering upgrade mode.", connection_);
handling_upgrade_ = true;
// Ignore h2c upgrade requests until we support them.
// See https://github.com/envoyproxy/envoy/issues/7161 for details.
if (current_header_map_->Upgrade() &&
absl::EqualsIgnoreCase(current_header_map_->Upgrade()->value().getStringView(),
Http::Headers::get().UpgradeValues.H2c)) {
jplevyak marked this conversation as resolved.
Show resolved Hide resolved
ENVOY_CONN_LOG(trace, "removing unsupported h2c upgrade headers.", connection_);
current_header_map_->removeUpgrade();
jplevyak marked this conversation as resolved.
Show resolved Hide resolved
jplevyak marked this conversation as resolved.
Show resolved Hide resolved
if (current_header_map_->Connection()) {
const auto& tokens_to_remove = caseUnorderdSetContainingUpgradeAndHttp2Settings();
std::string new_value = StringUtil::removeTokens(
current_header_map_->Connection()->value().getStringView(), ",", tokens_to_remove, ",");
if (new_value.empty()) {
current_header_map_->removeConnection();
} else {
current_header_map_->Connection()->value(new_value);
}
}
current_header_map_->remove(Headers::get().Http2Settings);
} else {
ENVOY_CONN_LOG(trace, "codec entering upgrade mode.", connection_);
handling_upgrade_ = true;
}
}

int rc = onHeadersComplete(std::move(current_header_map_));
Expand Down
22 changes: 22 additions & 0 deletions test/common/common/utility_speed_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,28 @@ static void BM_FindTokenValueNoSplit(benchmark::State& state) {
}
BENCHMARK(BM_FindTokenValueNoSplit);

static void BM_RemoveTokensLong(benchmark::State& state) {
auto size = state.range(0);
std::string input(size, ',');
std::vector<std::string> to_remove;
StringUtil::CaseUnorderedSet to_remove_set;
for (decltype(size) i = 0; i < size; i++) {
to_remove.push_back(std::to_string(i));
}
for (int i = 0; i < size; i++) {
if (i & 1) {
to_remove_set.insert(to_remove[i]);
}
input.append(",");
input.append(to_remove[i]);
}
for (auto _ : state) {
Envoy::StringUtil::removeTokens(input, ",", to_remove_set, ",");
state.SetBytesProcessed(static_cast<int64_t>(state.iterations()) * input.size());
}
}
BENCHMARK(BM_RemoveTokensLong)->Range(8, 8 << 10);

static void BM_IntervalSetInsert17(benchmark::State& state) {
for (auto _ : state) {
Envoy::IntervalSetImpl<size_t> interval_set;
Expand Down
22 changes: 22 additions & 0 deletions test/common/common/utility_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -368,6 +368,28 @@ TEST(StringUtil, StringViewSplit) {
}
}

TEST(StringUtil, StringViewRemoveTokens) {
// Basic cases.
EXPECT_EQ(StringUtil::removeTokens("", ",", {"two"}, ","), "");
EXPECT_EQ(StringUtil::removeTokens("one", ",", {"two"}, ","), "one");
EXPECT_EQ(StringUtil::removeTokens("one,two ", ",", {"two"}, ","), "one");
jplevyak marked this conversation as resolved.
Show resolved Hide resolved
EXPECT_EQ(StringUtil::removeTokens("one,two ", ",", {"two", "one"}, ","), "");
EXPECT_EQ(StringUtil::removeTokens("one,two ", ",", {"one"}, ","), "two");
EXPECT_EQ(StringUtil::removeTokens("one,two,three ", ",", {"two"}, ","), "one,three");
EXPECT_EQ(StringUtil::removeTokens(" one , two , three ", ",", {"two"}, ","), "one,three");
EXPECT_EQ(StringUtil::removeTokens(" one , two , three ", ",", {"three"}, ","), "one,two");
EXPECT_EQ(StringUtil::removeTokens(" one , two , three ", ",", {"three"}, ", "), "one, two");
EXPECT_EQ(StringUtil::removeTokens("one,two,three", ",", {"two", "three"}, ","), "one");
EXPECT_EQ(StringUtil::removeTokens("one,two,three,four", ",", {"two", "three"}, ","), "one,four");
// Ignore case.
EXPECT_EQ(StringUtil::removeTokens("One,Two,Three,Four", ",", {"two", "three"}, ","), "One,Four");
// Longer joiner.
EXPECT_EQ(StringUtil::removeTokens("one,two,three,four", ",", {"two", "three"}, " , "),
"one , four");
// Delimiters.
EXPECT_EQ(StringUtil::removeTokens("one,two;three ", ",;", {"two"}, ","), "one,three");
}

TEST(StringUtil, removeCharacters) {
IntervalSetImpl<size_t> removals;
removals.insert(3, 5);
Expand Down
37 changes: 37 additions & 0 deletions test/common/http/http1/codec_impl_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -720,6 +720,43 @@ TEST_F(Http1ServerConnectionImplTest, RequestWithTrailers) {
EXPECT_EQ(0U, buffer.length());
}

TEST_F(Http1ServerConnectionImplTest, IgnoreUpgradeH2c) {
initialize();

TestHeaderMapImpl expected_headers{
{":authority", "www.somewhere.com"}, {":path", "/"}, {":method", "GET"}};
Buffer::OwnedImpl buffer(
"GET http://www.somewhere.com/ HTTP/1.1\r\nConnection: "
jplevyak marked this conversation as resolved.
Show resolved Hide resolved
"Upgrade, HTTP2-Settings\r\nUpgrade: h2c\r\nHTTP2-Settings: token64\r\nHost: bah\r\n\r\n");
expectHeadersTest(Protocol::Http11, true, buffer, expected_headers);
}

TEST_F(Http1ServerConnectionImplTest, IgnoreUpgradeH2cClose) {
initialize();

TestHeaderMapImpl expected_headers{{":authority", "www.somewhere.com"},
{":path", "/"},
{":method", "GET"},
{"connection", "Close"}};
Buffer::OwnedImpl buffer("GET http://www.somewhere.com/ HTTP/1.1\r\nConnection: "
"Upgrade, Close, HTTP2-Settings\r\nUpgrade: h2c\r\nHTTP2-Settings: "
"token64\r\nHost: bah\r\n\r\n");
expectHeadersTest(Protocol::Http11, true, buffer, expected_headers);
}

TEST_F(Http1ServerConnectionImplTest, IgnoreUpgradeH2cCloseEtc) {
initialize();

TestHeaderMapImpl expected_headers{{":authority", "www.somewhere.com"},
{":path", "/"},
{":method", "GET"},
{"connection", "Close,Etc"}};
Buffer::OwnedImpl buffer("GET http://www.somewhere.com/ HTTP/1.1\r\nConnection: "
"Upgrade, Close, HTTP2-Settings, Etc\r\nUpgrade: h2c\r\nHTTP2-Settings: "
"token64\r\nHost: bah\r\n\r\n");
expectHeadersTest(Protocol::Http11, true, buffer, expected_headers);
}

TEST_F(Http1ServerConnectionImplTest, UpgradeRequest) {
initialize();

Expand Down