From 6f17ae351ce006ea121abca3e1877c320f5530ca Mon Sep 17 00:00:00 2001 From: Lizan Zhou Date: Thu, 16 Jul 2020 19:35:12 -0700 Subject: [PATCH] Revert "[http] Initial codec splitting with test parametrization (#10591)" (#12142) This reverts commit 568b75960dcf0650cf600d5e1181480ee4f519e0. Signed-off-by: Lizan Zhou --- bazel/BUILD | 5 - bazel/envoy_build_system.bzl | 2 - bazel/envoy_select.bzl | 7 - ci/do_ci.sh | 5 - docs/root/version_history/current.rst | 1 - source/common/http/BUILD | 7 - source/common/http/codec_client.cc | 32 +- source/common/http/conn_manager_utility.cc | 26 +- source/common/http/http1/BUILD | 60 +- source/common/http/http1/codec_impl_legacy.cc | 1246 ---------- source/common/http/http1/codec_impl_legacy.h | 607 ----- source/common/http/http2/BUILD | 74 +- source/common/http/http2/codec_impl.cc | 10 +- source/common/http/http2/codec_impl.h | 11 +- source/common/http/http2/codec_impl_legacy.cc | 1473 ----------- source/common/http/http2/codec_impl_legacy.h | 602 ----- source/common/runtime/runtime_features.cc | 1 - .../network/http_connection_manager/BUILD | 2 - .../network/http_connection_manager/config.cc | 37 +- test/common/http/codec_impl_fuzz_test.cc | 24 +- test/common/http/http1/BUILD | 1 - test/common/http/http1/codec_impl_test.cc | 360 ++- test/common/http/http2/BUILD | 62 +- .../http/http2/codec_impl_legacy_test.cc | 2163 ----------------- test/common/http/http2/codec_impl_test.cc | 152 +- test/common/http/http2/codec_impl_test_util.h | 109 +- test/common/http/http2/frame_replay_test.cc | 18 +- .../http/http2/request_header_fuzz_test.cc | 2 +- .../http/http2/response_header_fuzz_test.cc | 2 +- test/common/stats/stat_test_utility.h | 5 - test/config/utility.cc | 4 - test/config/utility.h | 3 - .../http_connection_manager/config_test.cc | 60 - test/integration/BUILD | 7 - .../api_version_integration_test.cc | 8 +- test/integration/fake_upstream.cc | 38 +- test/integration/integration.cc | 5 - test/integration/integration.h | 1 - tools/code_format/check_format.py | 51 - .../codec_diffs/http1_codec_impl_cc | 35 - .../codec_diffs/http1_codec_impl_h | 130 - .../codec_diffs/http2_codec_impl_cc | 34 - .../codec_diffs/http2_codec_impl_h | 77 - 43 files changed, 358 insertions(+), 7201 deletions(-) delete mode 100644 source/common/http/http1/codec_impl_legacy.cc delete mode 100644 source/common/http/http1/codec_impl_legacy.h delete mode 100644 source/common/http/http2/codec_impl_legacy.cc delete mode 100644 source/common/http/http2/codec_impl_legacy.h delete mode 100644 test/common/http/http2/codec_impl_legacy_test.cc delete mode 100644 tools/code_format/codec_diffs/http1_codec_impl_cc delete mode 100644 tools/code_format/codec_diffs/http1_codec_impl_h delete mode 100644 tools/code_format/codec_diffs/http2_codec_impl_cc delete mode 100644 tools/code_format/codec_diffs/http2_codec_impl_h diff --git a/bazel/BUILD b/bazel/BUILD index 97d9d79fb6be..982d3fa3ac70 100644 --- a/bazel/BUILD +++ b/bazel/BUILD @@ -199,11 +199,6 @@ config_setting( values = {"define": "path_normalization_by_default=true"}, ) -config_setting( - name = "enable_legacy_codecs_in_integration_tests", - values = {"define": "use_legacy_codecs_in_integration_tests=true"}, -) - cc_proto_library( name = "grpc_health_proto", deps = ["@com_github_grpc_grpc//src/proto/grpc/health/v1:_health_proto_only"], diff --git a/bazel/envoy_build_system.bzl b/bazel/envoy_build_system.bzl index 0f062cbfe8d8..70ef3df4f1d2 100644 --- a/bazel/envoy_build_system.bzl +++ b/bazel/envoy_build_system.bzl @@ -18,7 +18,6 @@ load( _envoy_select_boringssl = "envoy_select_boringssl", _envoy_select_google_grpc = "envoy_select_google_grpc", _envoy_select_hot_restart = "envoy_select_hot_restart", - _envoy_select_legacy_codecs_in_integration_tests = "envoy_select_legacy_codecs_in_integration_tests", ) load( ":envoy_test.bzl", @@ -169,7 +168,6 @@ def envoy_google_grpc_external_deps(): envoy_select_boringssl = _envoy_select_boringssl envoy_select_google_grpc = _envoy_select_google_grpc envoy_select_hot_restart = _envoy_select_hot_restart -envoy_select_legacy_codecs_in_integration_tests = _envoy_select_legacy_codecs_in_integration_tests # Binary wrappers (from envoy_binary.bzl) envoy_cc_binary = _envoy_cc_binary diff --git a/bazel/envoy_select.bzl b/bazel/envoy_select.bzl index ba7704ceb02f..f2167f29bec4 100644 --- a/bazel/envoy_select.bzl +++ b/bazel/envoy_select.bzl @@ -31,10 +31,3 @@ def envoy_select_hot_restart(xs, repository = ""): repository + "//bazel:disable_hot_restart_or_apple": [], "//conditions:default": xs, }) - -# Select the given values if use legacy codecs in test is on in the current build. -def envoy_select_legacy_codecs_in_integration_tests(xs, repository = ""): - return select({ - repository + "//bazel:enable_legacy_codecs_in_integration_tests": xs, - "//conditions:default": [], - }) diff --git a/ci/do_ci.sh b/ci/do_ci.sh index a8063dd4d7bc..d13c7be545bd 100755 --- a/ci/do_ci.sh +++ b/ci/do_ci.sh @@ -220,7 +220,6 @@ elif [[ "$CI_TARGET" == "bazel.compile_time_options" ]]; then --define quiche=enabled \ --define path_normalization_by_default=true \ --define deprecated_features=disabled \ - --define use_legacy_codecs_in_integration_tests=true \ --define --cxxopt=-std=c++14 \ " ENVOY_STDLIB="${ENVOY_STDLIB:-libstdc++}" @@ -238,10 +237,6 @@ elif [[ "$CI_TARGET" == "bazel.compile_time_options" ]]; then echo "Building and testing ${TEST_TARGETS}" bazel test ${BAZEL_BUILD_OPTIONS} ${COMPILE_TIME_OPTIONS} -c dbg ${TEST_TARGETS} --test_tag_filters=-nofips --build_tests_only - # Legacy codecs "--define legacy_codecs_in_integration_tests=true" should also be tested in - # integration tests with asan. - bazel test ${BAZEL_BUILD_OPTIONS} ${COMPILE_TIME_OPTIONS} -c dbg @envoy//test/integration/... --config=clang-asan --build_tests_only - # "--define log_debug_assert_in_release=enabled" must be tested with a release build, so run only # these tests under "-c opt" to save time in CI. bazel test ${BAZEL_BUILD_OPTIONS} ${COMPILE_TIME_OPTIONS} -c opt @envoy//test/common/common:assert_test @envoy//test/server:server_test diff --git a/docs/root/version_history/current.rst b/docs/root/version_history/current.rst index 42346d258da0..eb3465f99eeb 100644 --- a/docs/root/version_history/current.rst +++ b/docs/root/version_history/current.rst @@ -36,7 +36,6 @@ New Features * ext_authz filter: added support for emitting dynamic metadata for both :ref:`HTTP ` and :ref:`network ` filters. * grpc-json: support specifying `response_body` field in for `google.api.HttpBody` message. -* http: introduced new HTTP/1 and HTTP/2 codec implementations that will remove the use of exceptions for control flow due to high risk factors and instead use error statuses. The old behavior is deprecated, but can be used during the removal period by setting the runtime feature `envoy.reloadable_features.new_codec_behavior` to false. The removal period will be one month. * load balancer: added a :ref:`configuration` option to specify the active request bias used by the least request load balancer. * tap: added :ref:`generic body matcher` to scan http requests and responses for text or hex patterns. diff --git a/source/common/http/BUILD b/source/common/http/BUILD index 041c3508d650..5a07a56b9f00 100644 --- a/source/common/http/BUILD +++ b/source/common/http/BUILD @@ -57,21 +57,16 @@ envoy_cc_library( "//include/envoy/http:codec_interface", "//include/envoy/network:connection_interface", "//include/envoy/network:filter_interface", - "//include/envoy/runtime:runtime_interface", "//source/common/common:assert_lib", "//source/common/common:enum_to_int", "//source/common/common:linked_object", "//source/common/common:minimal_logger_lib", "//source/common/config:utility_lib", - "//source/common/http/http1:codec_legacy_lib", "//source/common/http/http1:codec_lib", - "//source/common/http/http2:codec_legacy_lib", "//source/common/http/http2:codec_lib", "//source/common/http/http3:quic_codec_factory_lib", "//source/common/http/http3:well_known_names", "//source/common/network:filter_lib", - "//source/common/runtime:runtime_features_lib", - "//source/common/runtime:runtime_lib", ], ) @@ -212,9 +207,7 @@ envoy_cc_library( "//source/common/common:scope_tracker", "//source/common/common:utility_lib", "//source/common/config:utility_lib", - "//source/common/http/http1:codec_legacy_lib", "//source/common/http/http1:codec_lib", - "//source/common/http/http2:codec_legacy_lib", "//source/common/http/http2:codec_lib", "//source/common/http/http3:quic_codec_factory_lib", "//source/common/http/http3:well_known_names", diff --git a/source/common/http/codec_client.cc b/source/common/http/codec_client.cc index e3fbc23ef921..557b5757414a 100644 --- a/source/common/http/codec_client.cc +++ b/source/common/http/codec_client.cc @@ -9,15 +9,11 @@ #include "common/config/utility.h" #include "common/http/exception.h" #include "common/http/http1/codec_impl.h" -#include "common/http/http1/codec_impl_legacy.h" #include "common/http/http2/codec_impl.h" -#include "common/http/http2/codec_impl_legacy.h" #include "common/http/http3/quic_codec_factory.h" #include "common/http/http3/well_known_names.h" #include "common/http/status.h" #include "common/http/utility.h" -#include "common/runtime/runtime_features.h" -#include "common/runtime/runtime_impl.h" namespace Envoy { namespace Http { @@ -154,29 +150,16 @@ CodecClientProd::CodecClientProd(Type type, Network::ClientConnectionPtr&& conne switch (type) { case Type::HTTP1: { - if (Runtime::runtimeFeatureEnabled("envoy.reloadable_features.new_codec_behavior")) { - codec_ = std::make_unique( - *connection_, host->cluster().http1CodecStats(), *this, host->cluster().http1Settings(), - host->cluster().maxResponseHeadersCount()); - } else { - codec_ = std::make_unique( - *connection_, host->cluster().http1CodecStats(), *this, host->cluster().http1Settings(), - host->cluster().maxResponseHeadersCount()); - } + codec_ = std::make_unique( + *connection_, host->cluster().http1CodecStats(), *this, host->cluster().http1Settings(), + host->cluster().maxResponseHeadersCount()); break; } case Type::HTTP2: { - if (Runtime::runtimeFeatureEnabled("envoy.reloadable_features.new_codec_behavior")) { - codec_ = std::make_unique( - *connection_, *this, host->cluster().http2CodecStats(), host->cluster().http2Options(), - Http::DEFAULT_MAX_REQUEST_HEADERS_KB, host->cluster().maxResponseHeadersCount(), - Http2::ProdNghttp2SessionFactory::get()); - } else { - codec_ = std::make_unique( - *connection_, *this, host->cluster().http2CodecStats(), host->cluster().http2Options(), - Http::DEFAULT_MAX_REQUEST_HEADERS_KB, host->cluster().maxResponseHeadersCount(), - Http2::ProdNghttp2SessionFactory::get()); - } + codec_ = std::make_unique( + *connection_, *this, host->cluster().http2CodecStats(), host->cluster().http2Options(), + Http::DEFAULT_MAX_REQUEST_HEADERS_KB, host->cluster().maxResponseHeadersCount(), + Http2::ProdNghttp2SessionFactory::get()); break; } case Type::HTTP3: { @@ -184,7 +167,6 @@ CodecClientProd::CodecClientProd(Type type, Network::ClientConnectionPtr&& conne Config::Utility::getAndCheckFactoryByName( Http::QuicCodecNames::get().Quiche) .createQuicClientConnection(*connection_, *this)); - break; } } } diff --git a/source/common/http/conn_manager_utility.cc b/source/common/http/conn_manager_utility.cc index c8ce01993cfa..65b5af861cc1 100644 --- a/source/common/http/conn_manager_utility.cc +++ b/source/common/http/conn_manager_utility.cc @@ -11,9 +11,7 @@ #include "common/http/header_utility.h" #include "common/http/headers.h" #include "common/http/http1/codec_impl.h" -#include "common/http/http1/codec_impl_legacy.h" #include "common/http/http2/codec_impl.h" -#include "common/http/http2/codec_impl_legacy.h" #include "common/http/path_utility.h" #include "common/http/utility.h" #include "common/network/utility.h" @@ -53,26 +51,14 @@ ServerConnectionPtr ConnectionManagerUtility::autoCreateCodec( headers_with_underscores_action) { if (determineNextProtocol(connection, data) == Utility::AlpnNames::get().Http2) { Http2::CodecStats& stats = Http2::CodecStats::atomicGet(http2_codec_stats, scope); - if (Runtime::runtimeFeatureEnabled("envoy.reloadable_features.new_codec_behavior")) { - return std::make_unique( - connection, callbacks, stats, http2_options, max_request_headers_kb, - max_request_headers_count, headers_with_underscores_action); - } else { - return std::make_unique( - connection, callbacks, stats, http2_options, max_request_headers_kb, - max_request_headers_count, headers_with_underscores_action); - } + return std::make_unique( + connection, callbacks, stats, http2_options, max_request_headers_kb, + max_request_headers_count, headers_with_underscores_action); } else { Http1::CodecStats& stats = Http1::CodecStats::atomicGet(http1_codec_stats, scope); - if (Runtime::runtimeFeatureEnabled("envoy.reloadable_features.new_codec_behavior")) { - return std::make_unique( - connection, stats, callbacks, http1_settings, max_request_headers_kb, - max_request_headers_count, headers_with_underscores_action); - } else { - return std::make_unique( - connection, stats, callbacks, http1_settings, max_request_headers_kb, - max_request_headers_count, headers_with_underscores_action); - } + return std::make_unique( + connection, stats, callbacks, http1_settings, max_request_headers_kb, + max_request_headers_count, headers_with_underscores_action); } } diff --git a/source/common/http/http1/BUILD b/source/common/http/http1/BUILD index 9451c4e29ae3..278e9adaaae5 100644 --- a/source/common/http/http1/BUILD +++ b/source/common/http/http1/BUILD @@ -24,46 +24,36 @@ envoy_cc_library( ], ) -CODEC_LIB_DEPS = [ - ":codec_stats_lib", - ":header_formatter_lib", - "//include/envoy/buffer:buffer_interface", - "//include/envoy/http:codec_interface", - "//include/envoy/http:header_map_interface", - "//include/envoy/network:connection_interface", - "//source/common/buffer:buffer_lib", - "//source/common/buffer:watermark_buffer_lib", - "//source/common/common:assert_lib", - "//source/common/common:statusor_lib", - "//source/common/common:utility_lib", - "//source/common/grpc:common_lib", - "//source/common/http:codec_helper_lib", - "//source/common/http:codes_lib", - "//source/common/http:exception_lib", - "//source/common/http:header_map_lib", - "//source/common/http:header_utility_lib", - "//source/common/http:headers_lib", - "//source/common/http:status_lib", - "//source/common/http:url_utility_lib", - "//source/common/http:utility_lib", - "//source/common/runtime:runtime_features_lib", - "@envoy_api//envoy/config/core/v3:pkg_cc_proto", -] - envoy_cc_library( name = "codec_lib", srcs = ["codec_impl.cc"], hdrs = ["codec_impl.h"], external_deps = ["http_parser"], - deps = CODEC_LIB_DEPS, -) - -envoy_cc_library( - name = "codec_legacy_lib", - srcs = ["codec_impl_legacy.cc"], - hdrs = ["codec_impl_legacy.h"], - external_deps = ["http_parser"], - deps = CODEC_LIB_DEPS, + deps = [ + ":codec_stats_lib", + ":header_formatter_lib", + "//include/envoy/buffer:buffer_interface", + "//include/envoy/http:codec_interface", + "//include/envoy/http:header_map_interface", + "//include/envoy/network:connection_interface", + "//source/common/buffer:buffer_lib", + "//source/common/buffer:watermark_buffer_lib", + "//source/common/common:assert_lib", + "//source/common/common:statusor_lib", + "//source/common/common:utility_lib", + "//source/common/grpc:common_lib", + "//source/common/http:codec_helper_lib", + "//source/common/http:codes_lib", + "//source/common/http:exception_lib", + "//source/common/http:header_map_lib", + "//source/common/http:header_utility_lib", + "//source/common/http:headers_lib", + "//source/common/http:status_lib", + "//source/common/http:url_utility_lib", + "//source/common/http:utility_lib", + "//source/common/runtime:runtime_features_lib", + "@envoy_api//envoy/config/core/v3:pkg_cc_proto", + ], ) envoy_cc_library( diff --git a/source/common/http/http1/codec_impl_legacy.cc b/source/common/http/http1/codec_impl_legacy.cc deleted file mode 100644 index d41d0a41401e..000000000000 --- a/source/common/http/http1/codec_impl_legacy.cc +++ /dev/null @@ -1,1246 +0,0 @@ -#include "common/http/http1/codec_impl_legacy.h" - -#include -#include -#include - -#include "envoy/buffer/buffer.h" -#include "envoy/http/codec.h" -#include "envoy/http/header_map.h" -#include "envoy/network/connection.h" - -#include "common/common/enum_to_int.h" -#include "common/common/utility.h" -#include "common/grpc/common.h" -#include "common/http/exception.h" -#include "common/http/header_utility.h" -#include "common/http/headers.h" -#include "common/http/http1/header_formatter.h" -#include "common/http/url_utility.h" -#include "common/http/utility.h" -#include "common/runtime/runtime_features.h" - -#include "absl/container/fixed_array.h" -#include "absl/strings/ascii.h" - -namespace Envoy { -namespace Http { -namespace Legacy { -namespace Http1 { -namespace { - -struct Http1ResponseCodeDetailValues { - const absl::string_view TooManyHeaders = "http1.too_many_headers"; - const absl::string_view HeadersTooLarge = "http1.headers_too_large"; - const absl::string_view HttpCodecError = "http1.codec_error"; - const absl::string_view InvalidCharacters = "http1.invalid_characters"; - const absl::string_view ConnectionHeaderSanitization = "http1.connection_header_rejected"; - const absl::string_view InvalidUrl = "http1.invalid_url"; - const absl::string_view InvalidTransferEncoding = "http1.invalid_transfer_encoding"; - const absl::string_view BodyDisallowed = "http1.body_disallowed"; - const absl::string_view TransferEncodingNotAllowed = "http1.transfer_encoding_not_allowed"; - const absl::string_view ContentLengthNotAllowed = "http1.content_length_not_allowed"; -}; - -struct Http1HeaderTypesValues { - const absl::string_view Headers = "headers"; - const absl::string_view Trailers = "trailers"; -}; - -using Http1ResponseCodeDetails = ConstSingleton; -using Http1HeaderTypes = ConstSingleton; -using Http::Http1::CodecStats; -using Http::Http1::HeaderKeyFormatter; -using Http::Http1::HeaderKeyFormatterPtr; -using Http::Http1::ProperCaseHeaderKeyFormatter; - -const StringUtil::CaseUnorderedSet& caseUnorderdSetContainingUpgradeAndHttp2Settings() { - CONSTRUCT_ON_FIRST_USE(StringUtil::CaseUnorderedSet, - Http::Headers::get().ConnectionValues.Upgrade, - Http::Headers::get().ConnectionValues.Http2Settings); -} - -HeaderKeyFormatterPtr formatter(const Http::Http1Settings& settings) { - if (settings.header_key_format_ == Http1Settings::HeaderKeyFormat::ProperCase) { - return std::make_unique(); - } - - return nullptr; -} - -} // namespace - -const std::string StreamEncoderImpl::CRLF = "\r\n"; -// Last chunk as defined here https://tools.ietf.org/html/rfc7230#section-4.1 -const std::string StreamEncoderImpl::LAST_CHUNK = "0\r\n"; - -StreamEncoderImpl::StreamEncoderImpl(ConnectionImpl& connection, - HeaderKeyFormatter* header_key_formatter) - : connection_(connection), disable_chunk_encoding_(false), chunk_encoding_(true), - is_response_to_head_request_(false), is_response_to_connect_request_(false), - header_key_formatter_(header_key_formatter) { - if (connection_.connection().aboveHighWatermark()) { - runHighWatermarkCallbacks(); - } -} - -void StreamEncoderImpl::encodeHeader(const char* key, uint32_t key_size, const char* value, - uint32_t value_size) { - - ASSERT(key_size > 0); - - connection_.copyToBuffer(key, key_size); - connection_.addCharToBuffer(':'); - connection_.addCharToBuffer(' '); - connection_.copyToBuffer(value, value_size); - connection_.addToBuffer(CRLF); -} -void StreamEncoderImpl::encodeHeader(absl::string_view key, absl::string_view value) { - this->encodeHeader(key.data(), key.size(), value.data(), value.size()); -} - -void StreamEncoderImpl::encodeFormattedHeader(absl::string_view key, absl::string_view value) { - if (header_key_formatter_ != nullptr) { - encodeHeader(header_key_formatter_->format(key), value); - } else { - encodeHeader(key, value); - } -} - -void ResponseEncoderImpl::encode100ContinueHeaders(const ResponseHeaderMap& headers) { - ASSERT(headers.Status()->value() == "100"); - encodeHeaders(headers, false); -} - -void StreamEncoderImpl::encodeHeadersBase(const RequestOrResponseHeaderMap& headers, - absl::optional status, bool end_stream) { - bool saw_content_length = false; - headers.iterate( - [](const HeaderEntry& header, void* context) -> HeaderMap::Iterate { - absl::string_view key_to_use = header.key().getStringView(); - uint32_t key_size_to_use = header.key().size(); - // Translate :authority -> host so that upper layers do not need to deal with this. - if (key_size_to_use > 1 && key_to_use[0] == ':' && key_to_use[1] == 'a') { - key_to_use = absl::string_view(Headers::get().HostLegacy.get()); - key_size_to_use = Headers::get().HostLegacy.get().size(); - } - - // Skip all headers starting with ':' that make it here. - if (key_to_use[0] == ':') { - return HeaderMap::Iterate::Continue; - } - - static_cast(context)->encodeFormattedHeader( - key_to_use, header.value().getStringView()); - - return HeaderMap::Iterate::Continue; - }, - this); - - if (headers.ContentLength()) { - saw_content_length = true; - } - - ASSERT(!headers.TransferEncoding()); - - // Assume we are chunk encoding unless we are passed a content length or this is a header only - // response. Upper layers generally should strip transfer-encoding since it only applies to - // HTTP/1.1. The codec will infer it based on the type of response. - // for streaming (e.g. SSE stream sent to hystrix dashboard), we do not want - // chunk transfer encoding but we don't have a content-length so disable_chunk_encoding_ is - // consulted before enabling chunk encoding. - // - // Note that for HEAD requests Envoy does best-effort guessing when there is no - // content-length. If a client makes a HEAD request for an upstream resource - // with no bytes but the upstream response doesn't include "Content-length: 0", - // Envoy will incorrectly assume a subsequent response to GET will be chunk encoded. - if (saw_content_length || disable_chunk_encoding_) { - chunk_encoding_ = false; - } else { - if (status && *status == 100) { - // Make sure we don't serialize chunk information with 100-Continue headers. - chunk_encoding_ = false; - } else if (end_stream && !is_response_to_head_request_) { - // If this is a headers-only stream, append an explicit "Content-Length: 0" unless it's a - // response to a HEAD request. - // For 204s and 1xx where content length is disallowed, don't append the content length but - // also don't chunk encode. - if (!status || (*status >= 200 && *status != 204)) { - encodeFormattedHeader(Headers::get().ContentLength.get(), "0"); - } - chunk_encoding_ = false; - } else if (connection_.protocol() == Protocol::Http10) { - chunk_encoding_ = false; - } else if (status && (*status < 200 || *status == 204) && - connection_.strict1xxAnd204Headers()) { - // TODO(zuercher): when the "envoy.reloadable_features.strict_1xx_and_204_response_headers" - // feature flag is removed, this block can be coalesced with the 100 Continue logic above. - - // For 1xx and 204 responses, do not send the chunked encoding header or enable chunked - // encoding: https://tools.ietf.org/html/rfc7230#section-3.3.1 - chunk_encoding_ = false; - } else { - // For responses to connect requests, do not send the chunked encoding header: - // https://tools.ietf.org/html/rfc7231#section-4.3.6. - if (!is_response_to_connect_request_) { - encodeFormattedHeader(Headers::get().TransferEncoding.get(), - Headers::get().TransferEncodingValues.Chunked); - } - // We do not apply chunk encoding for HTTP upgrades, including CONNECT style upgrades. - // If there is a body in a response on the upgrade path, the chunks will be - // passed through via maybeDirectDispatch so we need to avoid appending - // extra chunk boundaries. - // - // When sending a response to a HEAD request Envoy may send an informational - // "Transfer-Encoding: chunked" header, but should not send a chunk encoded body. - chunk_encoding_ = !Utility::isUpgrade(headers) && !is_response_to_head_request_ && - !is_response_to_connect_request_; - } - } - - connection_.addToBuffer(CRLF); - - if (end_stream) { - endEncode(); - } else { - connection_.flushOutput(); - } -} - -void StreamEncoderImpl::encodeData(Buffer::Instance& data, bool end_stream) { - // end_stream may be indicated with a zero length data buffer. If that is the case, so not - // actually write the zero length buffer out. - if (data.length() > 0) { - if (chunk_encoding_) { - connection_.buffer().add(absl::StrCat(absl::Hex(data.length()), CRLF)); - } - - connection_.buffer().move(data); - - if (chunk_encoding_) { - connection_.buffer().add(CRLF); - } - } - - if (end_stream) { - endEncode(); - } else { - connection_.flushOutput(); - } -} - -void StreamEncoderImpl::encodeTrailersBase(const HeaderMap& trailers) { - if (!connection_.enableTrailers()) { - return endEncode(); - } - // Trailers only matter if it is a chunk transfer encoding - // https://tools.ietf.org/html/rfc7230#section-4.4 - if (chunk_encoding_) { - // Finalize the body - connection_.buffer().add(LAST_CHUNK); - - trailers.iterate( - [](const HeaderEntry& header, void* context) -> HeaderMap::Iterate { - static_cast(context)->encodeFormattedHeader( - header.key().getStringView(), header.value().getStringView()); - return HeaderMap::Iterate::Continue; - }, - this); - - connection_.flushOutput(); - connection_.buffer().add(CRLF); - } - - connection_.flushOutput(); - connection_.onEncodeComplete(); -} - -void StreamEncoderImpl::encodeMetadata(const MetadataMapVector&) { - connection_.stats().metadata_not_supported_error_.inc(); -} - -void StreamEncoderImpl::endEncode() { - if (chunk_encoding_) { - connection_.buffer().add(LAST_CHUNK); - connection_.buffer().add(CRLF); - } - - connection_.flushOutput(true); - connection_.onEncodeComplete(); -} - -void ServerConnectionImpl::maybeAddSentinelBufferFragment(Buffer::WatermarkBuffer& output_buffer) { - if (!flood_protection_) { - return; - } - // It's messy and complicated to try to tag the final write of an HTTP response for response - // tracking for flood protection. Instead, write an empty buffer fragment after the response, - // to allow for tracking. - // When the response is written out, the fragment will be deleted and the counter will be updated - // by ServerConnectionImpl::releaseOutboundResponse() - auto fragment = - Buffer::OwnedBufferFragmentImpl::create(absl::string_view("", 0), response_buffer_releasor_); - output_buffer.addBufferFragment(*fragment.release()); - ASSERT(outbound_responses_ < max_outbound_responses_); - outbound_responses_++; -} - -void ServerConnectionImpl::doFloodProtectionChecks() const { - if (!flood_protection_) { - return; - } - // Before processing another request, make sure that we are below the response flood protection - // threshold. - if (outbound_responses_ >= max_outbound_responses_) { - ENVOY_CONN_LOG(trace, "error accepting request: too many pending responses queued", - connection_); - stats_.response_flood_.inc(); - throw FrameFloodException("Too many responses queued."); - } -} - -void ConnectionImpl::flushOutput(bool end_encode) { - if (end_encode) { - // If this is an HTTP response in ServerConnectionImpl, track outbound responses for flood - // protection - maybeAddSentinelBufferFragment(output_buffer_); - } - connection().write(output_buffer_, false); - ASSERT(0UL == output_buffer_.length()); -} - -void ConnectionImpl::addToBuffer(absl::string_view data) { output_buffer_.add(data); } - -void ConnectionImpl::addCharToBuffer(char c) { output_buffer_.add(&c, 1); } - -void ConnectionImpl::addIntToBuffer(uint64_t i) { output_buffer_.add(absl::StrCat(i)); } - -void ConnectionImpl::copyToBuffer(const char* data, uint64_t length) { - output_buffer_.add(data, length); -} - -void StreamEncoderImpl::resetStream(StreamResetReason reason) { - connection_.onResetStreamBase(reason); -} - -void StreamEncoderImpl::readDisable(bool disable) { - if (disable) { - ++read_disable_calls_; - } else { - ASSERT(read_disable_calls_ != 0); - if (read_disable_calls_ != 0) { - --read_disable_calls_; - } - } - connection_.readDisable(disable); -} - -uint32_t StreamEncoderImpl::bufferLimit() { return connection_.bufferLimit(); } - -const Network::Address::InstanceConstSharedPtr& StreamEncoderImpl::connectionLocalAddress() { - return connection_.connection().localAddress(); -} - -static const char RESPONSE_PREFIX[] = "HTTP/1.1 "; -static const char HTTP_10_RESPONSE_PREFIX[] = "HTTP/1.0 "; - -void ResponseEncoderImpl::encodeHeaders(const ResponseHeaderMap& headers, bool end_stream) { - started_response_ = true; - - // The contract is that client codecs must ensure that :status is present. - ASSERT(headers.Status() != nullptr); - uint64_t numeric_status = Utility::getResponseStatus(headers); - - if (connection_.protocol() == Protocol::Http10 && connection_.supportsHttp10()) { - connection_.copyToBuffer(HTTP_10_RESPONSE_PREFIX, sizeof(HTTP_10_RESPONSE_PREFIX) - 1); - } else { - connection_.copyToBuffer(RESPONSE_PREFIX, sizeof(RESPONSE_PREFIX) - 1); - } - connection_.addIntToBuffer(numeric_status); - connection_.addCharToBuffer(' '); - - const char* status_string = CodeUtility::toString(static_cast(numeric_status)); - uint32_t status_string_len = strlen(status_string); - connection_.copyToBuffer(status_string, status_string_len); - - connection_.addCharToBuffer('\r'); - connection_.addCharToBuffer('\n'); - - if (numeric_status >= 300) { - // Don't do special CONNECT logic if the CONNECT was rejected. - is_response_to_connect_request_ = false; - } - - encodeHeadersBase(headers, absl::make_optional(numeric_status), end_stream); -} - -static const char REQUEST_POSTFIX[] = " HTTP/1.1\r\n"; - -void RequestEncoderImpl::encodeHeaders(const RequestHeaderMap& headers, bool end_stream) { - const HeaderEntry* method = headers.Method(); - const HeaderEntry* path = headers.Path(); - const HeaderEntry* host = headers.Host(); - bool is_connect = HeaderUtility::isConnect(headers); - - if (!method || (!path && !is_connect)) { - // TODO(#10878): This exception does not occur during dispatch and would not be triggered under - // normal circumstances since inputs would fail parsing at ingress. Replace with proper error - // handling when exceptions are removed. Include missing host header for CONNECT. - throw CodecClientException(":method and :path must be specified"); - } - if (method->value() == Headers::get().MethodValues.Head) { - head_request_ = true; - } else if (method->value() == Headers::get().MethodValues.Connect) { - disableChunkEncoding(); - connect_request_ = true; - } - if (Utility::isUpgrade(headers)) { - upgrade_request_ = true; - } - - connection_.copyToBuffer(method->value().getStringView().data(), method->value().size()); - connection_.addCharToBuffer(' '); - if (is_connect) { - connection_.copyToBuffer(host->value().getStringView().data(), host->value().size()); - } else { - connection_.copyToBuffer(path->value().getStringView().data(), path->value().size()); - } - connection_.copyToBuffer(REQUEST_POSTFIX, sizeof(REQUEST_POSTFIX) - 1); - - encodeHeadersBase(headers, absl::nullopt, end_stream); -} - -http_parser_settings ConnectionImpl::settings_{ - [](http_parser* parser) -> int { - static_cast(parser->data)->onMessageBeginBase(); - return 0; - }, - [](http_parser* parser, const char* at, size_t length) -> int { - static_cast(parser->data)->onUrl(at, length); - return 0; - }, - nullptr, // on_status - [](http_parser* parser, const char* at, size_t length) -> int { - static_cast(parser->data)->onHeaderField(at, length); - return 0; - }, - [](http_parser* parser, const char* at, size_t length) -> int { - static_cast(parser->data)->onHeaderValue(at, length); - return 0; - }, - [](http_parser* parser) -> int { - return static_cast(parser->data)->onHeadersCompleteBase(); - }, - [](http_parser* parser, const char* at, size_t length) -> int { - static_cast(parser->data)->bufferBody(at, length); - return 0; - }, - [](http_parser* parser) -> int { - static_cast(parser->data)->onMessageCompleteBase(); - return 0; - }, - [](http_parser* parser) -> int { - // A 0-byte chunk header is used to signal the end of the chunked body. - // When this function is called, http-parser holds the size of the chunk in - // parser->content_length. See - // https://github.com/nodejs/http-parser/blob/v2.9.3/http_parser.h#L336 - const bool is_final_chunk = (parser->content_length == 0); - static_cast(parser->data)->onChunkHeader(is_final_chunk); - return 0; - }, - nullptr // on_chunk_complete -}; - -ConnectionImpl::ConnectionImpl(Network::Connection& connection, CodecStats& stats, - http_parser_type type, uint32_t max_headers_kb, - const uint32_t max_headers_count, - HeaderKeyFormatterPtr&& header_key_formatter, bool enable_trailers) - : connection_(connection), stats_(stats), - header_key_formatter_(std::move(header_key_formatter)), processing_trailers_(false), - handling_upgrade_(false), reset_stream_called_(false), deferred_end_stream_headers_(false), - connection_header_sanitization_(Runtime::runtimeFeatureEnabled( - "envoy.reloadable_features.connection_header_sanitization")), - enable_trailers_(enable_trailers), - strict_1xx_and_204_headers_(Runtime::runtimeFeatureEnabled( - "envoy.reloadable_features.strict_1xx_and_204_response_headers")), - output_buffer_([&]() -> void { this->onBelowLowWatermark(); }, - [&]() -> void { this->onAboveHighWatermark(); }, - []() -> void { /* TODO(adisuissa): Handle overflow watermark */ }), - max_headers_kb_(max_headers_kb), max_headers_count_(max_headers_count) { - output_buffer_.setWatermarks(connection.bufferLimit()); - http_parser_init(&parser_, type); - parser_.data = this; -} - -void ConnectionImpl::completeLastHeader() { - ENVOY_CONN_LOG(trace, "completed header: key={} value={}", connection_, - current_header_field_.getStringView(), current_header_value_.getStringView()); - - checkHeaderNameForUnderscores(); - auto& headers_or_trailers = headersOrTrailers(); - if (!current_header_field_.empty()) { - current_header_field_.inlineTransform([](char c) { return absl::ascii_tolower(c); }); - // Strip trailing whitespace of the current header value if any. Leading whitespace was trimmed - // in ConnectionImpl::onHeaderValue. http_parser does not strip leading or trailing whitespace - // as the spec requires: https://tools.ietf.org/html/rfc7230#section-3.2.4 - current_header_value_.rtrim(); - headers_or_trailers.addViaMove(std::move(current_header_field_), - std::move(current_header_value_)); - } - - // Check if the number of headers exceeds the limit. - if (headers_or_trailers.size() > max_headers_count_) { - error_code_ = Http::Code::RequestHeaderFieldsTooLarge; - sendProtocolError(Http1ResponseCodeDetails::get().TooManyHeaders); - const absl::string_view header_type = - processing_trailers_ ? Http1HeaderTypes::get().Trailers : Http1HeaderTypes::get().Headers; - throw CodecProtocolException(absl::StrCat(header_type, " size exceeds limit")); - } - - header_parsing_state_ = HeaderParsingState::Field; - ASSERT(current_header_field_.empty()); - ASSERT(current_header_value_.empty()); -} - -uint32_t ConnectionImpl::getHeadersSize() { - return current_header_field_.size() + current_header_value_.size() + - headersOrTrailers().byteSize(); -} - -void ConnectionImpl::checkMaxHeadersSize() { - const uint32_t total = getHeadersSize(); - if (total > (max_headers_kb_ * 1024)) { - const absl::string_view header_type = - processing_trailers_ ? Http1HeaderTypes::get().Trailers : Http1HeaderTypes::get().Headers; - error_code_ = Http::Code::RequestHeaderFieldsTooLarge; - sendProtocolError(Http1ResponseCodeDetails::get().HeadersTooLarge); - throw CodecProtocolException(absl::StrCat(header_type, " size exceeds limit")); - } -} - -bool ConnectionImpl::maybeDirectDispatch(Buffer::Instance& data) { - if (!handling_upgrade_) { - // Only direct dispatch for Upgrade requests. - return false; - } - - ENVOY_CONN_LOG(trace, "direct-dispatched {} bytes", connection_, data.length()); - onBody(data); - data.drain(data.length()); - return true; -} - -Http::Status ConnectionImpl::dispatch(Buffer::Instance& data) { - // TODO(#10878): Remove this wrapper when exception removal is complete. innerDispatch may either - // throw an exception or return an error status. The utility wrapper catches exceptions and - // converts them to error statuses. - return Utility::exceptionToStatus( - [&](Buffer::Instance& data) -> Http::Status { return innerDispatch(data); }, data); -} - -Http::Status ConnectionImpl::innerDispatch(Buffer::Instance& data) { - ENVOY_CONN_LOG(trace, "parsing {} bytes", connection_, data.length()); - ASSERT(buffered_body_.length() == 0); - - if (maybeDirectDispatch(data)) { - return Http::okStatus(); - } - - // Always unpause before dispatch. - http_parser_pause(&parser_, 0); - - ssize_t total_parsed = 0; - if (data.length() > 0) { - for (const Buffer::RawSlice& slice : data.getRawSlices()) { - total_parsed += dispatchSlice(static_cast(slice.mem_), slice.len_); - if (HTTP_PARSER_ERRNO(&parser_) != HPE_OK) { - // Parse errors trigger an exception in dispatchSlice so we are guaranteed to be paused at - // this point. - ASSERT(HTTP_PARSER_ERRNO(&parser_) == HPE_PAUSED); - break; - } - } - dispatchBufferedBody(); - } else { - dispatchSlice(nullptr, 0); - } - ASSERT(buffered_body_.length() == 0); - - ENVOY_CONN_LOG(trace, "parsed {} bytes", connection_, total_parsed); - data.drain(total_parsed); - - // If an upgrade has been handled and there is body data or early upgrade - // payload to send on, send it on. - maybeDirectDispatch(data); - return Http::okStatus(); -} - -size_t ConnectionImpl::dispatchSlice(const char* slice, size_t len) { - ssize_t rc = http_parser_execute(&parser_, &settings_, slice, len); - if (HTTP_PARSER_ERRNO(&parser_) != HPE_OK && HTTP_PARSER_ERRNO(&parser_) != HPE_PAUSED) { - sendProtocolError(Http1ResponseCodeDetails::get().HttpCodecError); - throw CodecProtocolException("http/1.1 protocol error: " + - std::string(http_errno_name(HTTP_PARSER_ERRNO(&parser_)))); - } - - return rc; -} - -void ConnectionImpl::onHeaderField(const char* data, size_t length) { - // We previously already finished up the headers, these headers are - // now trailers. - if (header_parsing_state_ == HeaderParsingState::Done) { - if (!enable_trailers_) { - // Ignore trailers. - return; - } - processing_trailers_ = true; - header_parsing_state_ = HeaderParsingState::Field; - allocTrailers(); - } - if (header_parsing_state_ == HeaderParsingState::Value) { - completeLastHeader(); - } - - current_header_field_.append(data, length); - - checkMaxHeadersSize(); -} - -void ConnectionImpl::onHeaderValue(const char* data, size_t length) { - if (header_parsing_state_ == HeaderParsingState::Done && !enable_trailers_) { - // Ignore trailers. - return; - } - - absl::string_view header_value{data, length}; - if (!Http::HeaderUtility::headerValueIsValid(header_value)) { - ENVOY_CONN_LOG(debug, "invalid header value: {}", connection_, header_value); - error_code_ = Http::Code::BadRequest; - sendProtocolError(Http1ResponseCodeDetails::get().InvalidCharacters); - throw CodecProtocolException("http/1.1 protocol error: header value contains invalid chars"); - } - - header_parsing_state_ = HeaderParsingState::Value; - if (current_header_value_.empty()) { - // Strip leading whitespace if the current header value input contains the first bytes of the - // encoded header value. Trailing whitespace is stripped once the full header value is known in - // ConnectionImpl::completeLastHeader. http_parser does not strip leading or trailing whitespace - // as the spec requires: https://tools.ietf.org/html/rfc7230#section-3.2.4 . - header_value = StringUtil::ltrim(header_value); - } - current_header_value_.append(header_value.data(), header_value.length()); - - checkMaxHeadersSize(); -} - -int ConnectionImpl::onHeadersCompleteBase() { - ASSERT(!processing_trailers_); - ENVOY_CONN_LOG(trace, "onHeadersCompleteBase", connection_); - completeLastHeader(); - - if (!(parser_.http_major == 1 && parser_.http_minor == 1)) { - // This is not necessarily true, but it's good enough since higher layers only care if this is - // HTTP/1.1 or not. - protocol_ = Protocol::Http10; - } - RequestOrResponseHeaderMap& request_or_response_headers = requestOrResponseHeaders(); - if (Utility::isUpgrade(request_or_response_headers) && upgradeAllowed()) { - // Ignore h2c upgrade requests until we support them. - // See https://github.com/envoyproxy/envoy/issues/7161 for details. - if (absl::EqualsIgnoreCase(request_or_response_headers.getUpgradeValue(), - Http::Headers::get().UpgradeValues.H2c)) { - ENVOY_CONN_LOG(trace, "removing unsupported h2c upgrade headers.", connection_); - request_or_response_headers.removeUpgrade(); - if (request_or_response_headers.Connection()) { - const auto& tokens_to_remove = caseUnorderdSetContainingUpgradeAndHttp2Settings(); - std::string new_value = StringUtil::removeTokens( - request_or_response_headers.getConnectionValue(), ",", tokens_to_remove, ","); - if (new_value.empty()) { - request_or_response_headers.removeConnection(); - } else { - request_or_response_headers.setConnection(new_value); - } - } - request_or_response_headers.remove(Headers::get().Http2Settings); - } else { - ENVOY_CONN_LOG(trace, "codec entering upgrade mode.", connection_); - handling_upgrade_ = true; - } - } - if (parser_.method == HTTP_CONNECT) { - if (request_or_response_headers.ContentLength()) { - if (request_or_response_headers.getContentLengthValue() == "0") { - request_or_response_headers.removeContentLength(); - } else { - // Per https://tools.ietf.org/html/rfc7231#section-4.3.6 a payload with a - // CONNECT request has no defined semantics, and may be rejected. - error_code_ = Http::Code::BadRequest; - sendProtocolError(Http1ResponseCodeDetails::get().BodyDisallowed); - throw CodecProtocolException("http/1.1 protocol error: unsupported content length"); - } - } - ENVOY_CONN_LOG(trace, "codec entering upgrade mode for CONNECT request.", connection_); - handling_upgrade_ = true; - } - - // Per https://tools.ietf.org/html/rfc7230#section-3.3.1 Envoy should reject - // transfer-codings it does not understand. - // Per https://tools.ietf.org/html/rfc7231#section-4.3.6 a payload with a - // CONNECT request has no defined semantics, and may be rejected. - if (request_or_response_headers.TransferEncoding()) { - const absl::string_view encoding = request_or_response_headers.getTransferEncodingValue(); - if (!absl::EqualsIgnoreCase(encoding, Headers::get().TransferEncodingValues.Chunked) || - parser_.method == HTTP_CONNECT) { - error_code_ = Http::Code::NotImplemented; - sendProtocolError(Http1ResponseCodeDetails::get().InvalidTransferEncoding); - throw CodecProtocolException("http/1.1 protocol error: unsupported transfer encoding"); - } - } - - int rc = onHeadersComplete(); - header_parsing_state_ = HeaderParsingState::Done; - - // Returning 2 informs http_parser to not expect a body or further data on this connection. - return handling_upgrade_ ? 2 : rc; -} - -void ConnectionImpl::bufferBody(const char* data, size_t length) { - buffered_body_.add(data, length); -} - -void ConnectionImpl::dispatchBufferedBody() { - ASSERT(HTTP_PARSER_ERRNO(&parser_) == HPE_OK || HTTP_PARSER_ERRNO(&parser_) == HPE_PAUSED); - if (buffered_body_.length() > 0) { - onBody(buffered_body_); - buffered_body_.drain(buffered_body_.length()); - } -} - -void ConnectionImpl::onChunkHeader(bool is_final_chunk) { - if (is_final_chunk) { - // Dispatch body before parsing trailers, so body ends up dispatched even if an error is found - // while processing trailers. - dispatchBufferedBody(); - } -} - -void ConnectionImpl::onMessageCompleteBase() { - ENVOY_CONN_LOG(trace, "message complete", connection_); - - dispatchBufferedBody(); - - if (handling_upgrade_) { - // If this is an upgrade request, swallow the onMessageComplete. The - // upgrade payload will be treated as stream body. - ASSERT(!deferred_end_stream_headers_); - ENVOY_CONN_LOG(trace, "Pausing parser due to upgrade.", connection_); - http_parser_pause(&parser_, 1); - return; - } - - // If true, this indicates we were processing trailers and must - // move the last header into current_header_map_ - if (header_parsing_state_ == HeaderParsingState::Value) { - completeLastHeader(); - } - - onMessageComplete(); -} - -void ConnectionImpl::onMessageBeginBase() { - ENVOY_CONN_LOG(trace, "message begin", connection_); - // Make sure that if HTTP/1.0 and HTTP/1.1 requests share a connection Envoy correctly sets - // protocol for each request. Envoy defaults to 1.1 but sets the protocol to 1.0 where applicable - // in onHeadersCompleteBase - protocol_ = Protocol::Http11; - processing_trailers_ = false; - header_parsing_state_ = HeaderParsingState::Field; - allocHeaders(); - onMessageBegin(); -} - -void ConnectionImpl::onResetStreamBase(StreamResetReason reason) { - ASSERT(!reset_stream_called_); - reset_stream_called_ = true; - onResetStream(reason); -} - -ServerConnectionImpl::ServerConnectionImpl( - Network::Connection& connection, CodecStats& stats, ServerConnectionCallbacks& callbacks, - const Http1Settings& settings, uint32_t max_request_headers_kb, - const uint32_t max_request_headers_count, - envoy::config::core::v3::HttpProtocolOptions::HeadersWithUnderscoresAction - headers_with_underscores_action) - : ConnectionImpl(connection, stats, HTTP_REQUEST, max_request_headers_kb, - max_request_headers_count, formatter(settings), settings.enable_trailers_), - callbacks_(callbacks), codec_settings_(settings), - response_buffer_releasor_([this](const Buffer::OwnedBufferFragmentImpl* fragment) { - releaseOutboundResponse(fragment); - }), - // Pipelining is generally not well supported on the internet and has a series of dangerous - // overflow bugs. As such we are disabling it for now, and removing this temporary override if - // no one objects. If you use this integer to restore prior behavior, contact the - // maintainer team as it will otherwise be removed entirely soon. - max_outbound_responses_( - Runtime::getInteger("envoy.do_not_use_going_away_max_http2_outbound_responses", 2)), - flood_protection_( - Runtime::runtimeFeatureEnabled("envoy.reloadable_features.http1_flood_protection")), - headers_with_underscores_action_(headers_with_underscores_action) {} - -uint32_t ServerConnectionImpl::getHeadersSize() { - // Add in the the size of the request URL if processing request headers. - const uint32_t url_size = (!processing_trailers_ && active_request_.has_value()) - ? active_request_.value().request_url_.size() - : 0; - return url_size + ConnectionImpl::getHeadersSize(); -} - -void ServerConnectionImpl::onEncodeComplete() { - if (active_request_.value().remote_complete_) { - // Only do this if remote is complete. If we are replying before the request is complete the - // only logical thing to do is for higher level code to reset() / close the connection so we - // leave the request around so that it can fire reset callbacks. - active_request_.reset(); - } -} - -void ServerConnectionImpl::handlePath(RequestHeaderMap& headers, unsigned int method) { - HeaderString path(Headers::get().Path); - - bool is_connect = (method == HTTP_CONNECT); - - // The url is relative or a wildcard when the method is OPTIONS. Nothing to do here. - auto& active_request = active_request_.value(); - if (!is_connect && !active_request.request_url_.getStringView().empty() && - (active_request.request_url_.getStringView()[0] == '/' || - ((method == HTTP_OPTIONS) && active_request.request_url_.getStringView()[0] == '*'))) { - headers.addViaMove(std::move(path), std::move(active_request.request_url_)); - return; - } - - // If absolute_urls and/or connect are not going be handled, copy the url and return. - // This forces the behavior to be backwards compatible with the old codec behavior. - // CONNECT "urls" are actually host:port so look like absolute URLs to the above checks. - // Absolute URLS in CONNECT requests will be rejected below by the URL class validation. - if (!codec_settings_.allow_absolute_url_ && !is_connect) { - headers.addViaMove(std::move(path), std::move(active_request.request_url_)); - return; - } - - Utility::Url absolute_url; - if (!absolute_url.initialize(active_request.request_url_.getStringView(), is_connect)) { - sendProtocolError(Http1ResponseCodeDetails::get().InvalidUrl); - throw CodecProtocolException("http/1.1 protocol error: invalid url in request line"); - } - // RFC7230#5.7 - // When a proxy receives a request with an absolute-form of - // request-target, the proxy MUST ignore the received Host header field - // (if any) and instead replace it with the host information of the - // request-target. A proxy that forwards such a request MUST generate a - // new Host field-value based on the received request-target rather than - // forward the received Host field-value. - headers.setHost(absolute_url.hostAndPort()); - - if (!absolute_url.pathAndQueryParams().empty()) { - headers.setPath(absolute_url.pathAndQueryParams()); - } - active_request.request_url_.clear(); -} - -int ServerConnectionImpl::onHeadersComplete() { - // Handle the case where response happens prior to request complete. It's up to upper layer code - // to disconnect the connection but we shouldn't fire any more events since it doesn't make - // sense. - if (active_request_.has_value()) { - auto& active_request = active_request_.value(); - auto& headers = absl::get(headers_or_trailers_); - ENVOY_CONN_LOG(trace, "Server: onHeadersComplete size={}", connection_, headers->size()); - const char* method_string = http_method_str(static_cast(parser_.method)); - - if (!handling_upgrade_ && connection_header_sanitization_ && headers->Connection()) { - // If we fail to sanitize the request, return a 400 to the client - if (!Utility::sanitizeConnectionHeader(*headers)) { - absl::string_view header_value = headers->getConnectionValue(); - ENVOY_CONN_LOG(debug, "Invalid nominated headers in Connection: {}", connection_, - header_value); - error_code_ = Http::Code::BadRequest; - sendProtocolError(Http1ResponseCodeDetails::get().ConnectionHeaderSanitization); - throw CodecProtocolException("Invalid nominated headers in Connection."); - } - } - - // Inform the response encoder about any HEAD method, so it can set content - // length and transfer encoding headers correctly. - active_request.response_encoder_.setIsResponseToHeadRequest(parser_.method == HTTP_HEAD); - active_request.response_encoder_.setIsResponseToConnectRequest(parser_.method == HTTP_CONNECT); - - handlePath(*headers, parser_.method); - ASSERT(active_request.request_url_.empty()); - - headers->setMethod(method_string); - - // Make sure the host is valid. - auto details = HeaderUtility::requestHeadersValid(*headers); - if (details.has_value()) { - sendProtocolError(details.value().get()); - throw CodecProtocolException( - "http/1.1 protocol error: request headers failed spec compliance checks"); - } - - // Determine here whether we have a body or not. This uses the new RFC semantics where the - // presence of content-length or chunked transfer-encoding indicates a body vs. a particular - // method. If there is no body, we defer raising decodeHeaders() until the parser is flushed - // with message complete. This allows upper layers to behave like HTTP/2 and prevents a proxy - // scenario where the higher layers stream through and implicitly switch to chunked transfer - // encoding because end stream with zero body length has not yet been indicated. - if (parser_.flags & F_CHUNKED || - (parser_.content_length > 0 && parser_.content_length != ULLONG_MAX) || handling_upgrade_) { - active_request.request_decoder_->decodeHeaders(std::move(headers), false); - - // If the connection has been closed (or is closing) after decoding headers, pause the parser - // so we return control to the caller. - if (connection_.state() != Network::Connection::State::Open) { - http_parser_pause(&parser_, 1); - } - } else { - deferred_end_stream_headers_ = true; - } - } - - return 0; -} - -void ServerConnectionImpl::onMessageBegin() { - if (!resetStreamCalled()) { - ASSERT(!active_request_.has_value()); - active_request_.emplace(*this, header_key_formatter_.get()); - auto& active_request = active_request_.value(); - active_request.request_decoder_ = &callbacks_.newStream(active_request.response_encoder_); - - // Check for pipelined request flood as we prepare to accept a new request. - // Parse errors that happen prior to onMessageBegin result in stream termination, it is not - // possible to overflow output buffers with early parse errors. - doFloodProtectionChecks(); - } -} - -void ServerConnectionImpl::onUrl(const char* data, size_t length) { - if (active_request_.has_value()) { - active_request_.value().request_url_.append(data, length); - - checkMaxHeadersSize(); - } -} - -void ServerConnectionImpl::onBody(Buffer::Instance& data) { - ASSERT(!deferred_end_stream_headers_); - if (active_request_.has_value()) { - ENVOY_CONN_LOG(trace, "body size={}", connection_, data.length()); - active_request_.value().request_decoder_->decodeData(data, false); - } -} - -void ServerConnectionImpl::onMessageComplete() { - ASSERT(!handling_upgrade_); - if (active_request_.has_value()) { - auto& active_request = active_request_.value(); - - if (active_request.request_decoder_) { - active_request.response_encoder_.readDisable(true); - } - active_request.remote_complete_ = true; - if (deferred_end_stream_headers_) { - active_request.request_decoder_->decodeHeaders( - std::move(absl::get(headers_or_trailers_)), true); - deferred_end_stream_headers_ = false; - } else if (processing_trailers_) { - active_request.request_decoder_->decodeTrailers( - std::move(absl::get(headers_or_trailers_))); - } else { - Buffer::OwnedImpl buffer; - active_request.request_decoder_->decodeData(buffer, true); - } - - // Reset to ensure no information from one requests persists to the next. - headers_or_trailers_.emplace(nullptr); - } - - // Always pause the parser so that the calling code can process 1 request at a time and apply - // back pressure. However this means that the calling code needs to detect if there is more data - // in the buffer and dispatch it again. - http_parser_pause(&parser_, 1); -} - -void ServerConnectionImpl::onResetStream(StreamResetReason reason) { - active_request_.value().response_encoder_.runResetCallbacks(reason); - active_request_.reset(); -} - -void ServerConnectionImpl::sendProtocolErrorOld(absl::string_view details) { - if (active_request_.has_value()) { - active_request_.value().response_encoder_.setDetails(details); - } - // We do this here because we may get a protocol error before we have a logical stream. Higher - // layers can only operate on streams, so there is no coherent way to allow them to send an error - // "out of band." On one hand this is kind of a hack but on the other hand it normalizes HTTP/1.1 - // to look more like HTTP/2 to higher layers. - if (!active_request_.has_value() || - !active_request_.value().response_encoder_.startedResponse()) { - Buffer::OwnedImpl bad_request_response( - absl::StrCat("HTTP/1.1 ", error_code_, " ", CodeUtility::toString(error_code_), - "\r\ncontent-length: 0\r\nconnection: close\r\n\r\n")); - - connection_.write(bad_request_response, false); - } -} - -void ServerConnectionImpl::sendProtocolError(absl::string_view details) { - if (!Runtime::runtimeFeatureEnabled("envoy.reloadable_features.early_errors_via_hcm")) { - sendProtocolErrorOld(details); - return; - } - // We do this here because we may get a protocol error before we have a logical stream. - if (!active_request_.has_value()) { - onMessageBeginBase(); - } - ASSERT(active_request_.has_value()); - - active_request_.value().response_encoder_.setDetails(details); - if (!active_request_.value().response_encoder_.startedResponse()) { - // Note that the correctness of is_grpc_request and is_head_request is best-effort. - // If headers have not been fully parsed they may not be inferred correctly. - bool is_grpc_request = false; - if (absl::holds_alternative(headers_or_trailers_) && - absl::get(headers_or_trailers_) != nullptr) { - is_grpc_request = - Grpc::Common::isGrpcRequestHeaders(*absl::get(headers_or_trailers_)); - } - const bool is_head_request = parser_.method == HTTP_HEAD; - active_request_->request_decoder_->sendLocalReply(is_grpc_request, error_code_, - CodeUtility::toString(error_code_), nullptr, - is_head_request, absl::nullopt, details); - return; - } -} - -void ServerConnectionImpl::onAboveHighWatermark() { - if (active_request_.has_value()) { - active_request_.value().response_encoder_.runHighWatermarkCallbacks(); - } -} -void ServerConnectionImpl::onBelowLowWatermark() { - if (active_request_.has_value()) { - active_request_.value().response_encoder_.runLowWatermarkCallbacks(); - } -} - -void ServerConnectionImpl::releaseOutboundResponse( - const Buffer::OwnedBufferFragmentImpl* fragment) { - ASSERT(outbound_responses_ >= 1); - --outbound_responses_; - delete fragment; -} - -void ServerConnectionImpl::checkHeaderNameForUnderscores() { - if (headers_with_underscores_action_ != envoy::config::core::v3::HttpProtocolOptions::ALLOW && - Http::HeaderUtility::headerNameContainsUnderscore(current_header_field_.getStringView())) { - if (headers_with_underscores_action_ == - envoy::config::core::v3::HttpProtocolOptions::DROP_HEADER) { - ENVOY_CONN_LOG(debug, "Dropping header with invalid characters in its name: {}", connection_, - current_header_field_.getStringView()); - stats_.dropped_headers_with_underscores_.inc(); - current_header_field_.clear(); - current_header_value_.clear(); - } else { - ENVOY_CONN_LOG(debug, "Rejecting request due to header name with underscores: {}", - connection_, current_header_field_.getStringView()); - error_code_ = Http::Code::BadRequest; - sendProtocolError(Http1ResponseCodeDetails::get().InvalidCharacters); - stats_.requests_rejected_with_underscores_in_headers_.inc(); - throw CodecProtocolException("http/1.1 protocol error: header name contains underscores"); - } - } -} - -ClientConnectionImpl::ClientConnectionImpl(Network::Connection& connection, CodecStats& stats, - ConnectionCallbacks&, const Http1Settings& settings, - const uint32_t max_response_headers_count) - : ConnectionImpl(connection, stats, HTTP_RESPONSE, MAX_RESPONSE_HEADERS_KB, - max_response_headers_count, formatter(settings), settings.enable_trailers_) {} - -bool ClientConnectionImpl::cannotHaveBody() { - if (pending_response_.has_value() && pending_response_.value().encoder_.headRequest()) { - ASSERT(!pending_response_done_); - return true; - } else if (parser_.status_code == 204 || parser_.status_code == 304 || - (parser_.status_code >= 200 && parser_.content_length == 0)) { - return true; - } else { - return false; - } -} - -RequestEncoder& ClientConnectionImpl::newStream(ResponseDecoder& response_decoder) { - if (resetStreamCalled()) { - throw CodecClientException("cannot create new streams after calling reset"); - } - - // If reads were disabled due to flow control, we expect reads to always be enabled again before - // reusing this connection. This is done when the response is received. - ASSERT(connection_.readEnabled()); - - ASSERT(!pending_response_.has_value()); - ASSERT(pending_response_done_); - pending_response_.emplace(*this, header_key_formatter_.get(), &response_decoder); - pending_response_done_ = false; - return pending_response_.value().encoder_; -} - -int ClientConnectionImpl::onHeadersComplete() { - // Handle the case where the client is closing a kept alive connection (by sending a 408 - // with a 'Connection: close' header). In this case we just let response flush out followed - // by the remote close. - if (!pending_response_.has_value() && !resetStreamCalled()) { - throw PrematureResponseException(static_cast(parser_.status_code)); - } else if (pending_response_.has_value()) { - ASSERT(!pending_response_done_); - auto& headers = absl::get(headers_or_trailers_); - ENVOY_CONN_LOG(trace, "Client: onHeadersComplete size={}", connection_, headers->size()); - headers->setStatus(parser_.status_code); - - if (parser_.status_code >= 200 && parser_.status_code < 300 && - pending_response_.value().encoder_.connectRequest()) { - ENVOY_CONN_LOG(trace, "codec entering upgrade mode for CONNECT response.", connection_); - handling_upgrade_ = true; - - // For responses to connect requests, do not accept the chunked - // encoding header: https://tools.ietf.org/html/rfc7231#section-4.3.6 - if (headers->TransferEncoding() && - absl::EqualsIgnoreCase(headers->TransferEncoding()->value().getStringView(), - Headers::get().TransferEncodingValues.Chunked)) { - sendProtocolError(Http1ResponseCodeDetails::get().InvalidTransferEncoding); - throw CodecProtocolException("http/1.1 protocol error: unsupported transfer encoding"); - } - } - - if (strict_1xx_and_204_headers_ && (parser_.status_code < 200 || parser_.status_code == 204)) { - if (headers->TransferEncoding()) { - sendProtocolError(Http1ResponseCodeDetails::get().TransferEncodingNotAllowed); - throw CodecProtocolException( - "http/1.1 protocol error: transfer encoding not allowed in 1xx or 204"); - } - - if (headers->ContentLength()) { - // Report a protocol error for non-zero Content-Length, but paper over zero Content-Length. - if (headers->ContentLength()->value().getStringView() != "0") { - sendProtocolError(Http1ResponseCodeDetails::get().ContentLengthNotAllowed); - throw CodecProtocolException( - "http/1.1 protocol error: content length not allowed in 1xx or 204"); - } - - headers->removeContentLength(); - } - } - - if (parser_.status_code == 100) { - // http-parser treats 100 continue headers as their own complete response. - // Swallow the spurious onMessageComplete and continue processing. - ignore_message_complete_for_100_continue_ = true; - pending_response_.value().decoder_->decode100ContinueHeaders(std::move(headers)); - - // Reset to ensure no information from the continue headers is used for the response headers - // in case the callee does not move the headers out. - headers_or_trailers_.emplace(nullptr); - } else if (cannotHaveBody() && !handling_upgrade_) { - deferred_end_stream_headers_ = true; - } else { - pending_response_.value().decoder_->decodeHeaders(std::move(headers), false); - } - } - - // Here we deal with cases where the response cannot have a body, but http_parser does not deal - // with it for us. - return cannotHaveBody() ? 1 : 0; -} - -bool ClientConnectionImpl::upgradeAllowed() const { - if (pending_response_.has_value()) { - return pending_response_->encoder_.upgradeRequest(); - } - return false; -} - -void ClientConnectionImpl::onBody(Buffer::Instance& data) { - ASSERT(!deferred_end_stream_headers_); - if (pending_response_.has_value()) { - ASSERT(!pending_response_done_); - pending_response_.value().decoder_->decodeData(data, false); - } -} - -void ClientConnectionImpl::onMessageComplete() { - ENVOY_CONN_LOG(trace, "message complete", connection_); - if (ignore_message_complete_for_100_continue_) { - ignore_message_complete_for_100_continue_ = false; - return; - } - if (pending_response_.has_value()) { - ASSERT(!pending_response_done_); - // After calling decodeData() with end stream set to true, we should no longer be able to reset. - PendingResponse& response = pending_response_.value(); - // Encoder is used as part of decode* calls later in this function so pending_response_ can not - // be reset just yet. Preserve the state in pending_response_done_ instead. - pending_response_done_ = true; - - if (deferred_end_stream_headers_) { - response.decoder_->decodeHeaders( - std::move(absl::get(headers_or_trailers_)), true); - deferred_end_stream_headers_ = false; - } else if (processing_trailers_) { - response.decoder_->decodeTrailers( - std::move(absl::get(headers_or_trailers_))); - } else { - Buffer::OwnedImpl buffer; - response.decoder_->decodeData(buffer, true); - } - - // Reset to ensure no information from one requests persists to the next. - pending_response_.reset(); - headers_or_trailers_.emplace(nullptr); - } -} - -void ClientConnectionImpl::onResetStream(StreamResetReason reason) { - // Only raise reset if we did not already dispatch a complete response. - if (pending_response_.has_value() && !pending_response_done_) { - pending_response_.value().encoder_.runResetCallbacks(reason); - pending_response_done_ = true; - pending_response_.reset(); - } -} - -void ClientConnectionImpl::sendProtocolError(absl::string_view details) { - if (pending_response_.has_value()) { - ASSERT(!pending_response_done_); - pending_response_.value().encoder_.setDetails(details); - } -} - -void ClientConnectionImpl::onAboveHighWatermark() { - // This should never happen without an active stream/request. - pending_response_.value().encoder_.runHighWatermarkCallbacks(); -} - -void ClientConnectionImpl::onBelowLowWatermark() { - // This can get called without an active stream/request when the response completion causes us to - // close the connection, but in doing so go below low watermark. - if (pending_response_.has_value() && !pending_response_done_) { - pending_response_.value().encoder_.runLowWatermarkCallbacks(); - } -} - -} // namespace Http1 -} // namespace Legacy -} // namespace Http -} // namespace Envoy diff --git a/source/common/http/http1/codec_impl_legacy.h b/source/common/http/http1/codec_impl_legacy.h deleted file mode 100644 index f5e9811ede87..000000000000 --- a/source/common/http/http1/codec_impl_legacy.h +++ /dev/null @@ -1,607 +0,0 @@ -#pragma once - -#include - -#include -#include -#include -#include -#include - -#include "envoy/config/core/v3/protocol.pb.h" -#include "envoy/http/codec.h" -#include "envoy/network/connection.h" - -#include "common/buffer/watermark_buffer.h" -#include "common/common/assert.h" -#include "common/common/statusor.h" -#include "common/http/codec_helper.h" -#include "common/http/codes.h" -#include "common/http/header_map_impl.h" -#include "common/http/http1/codec_stats.h" -#include "common/http/http1/header_formatter.h" -#include "common/http/status.h" - -namespace Envoy { -namespace Http { -namespace Legacy { -namespace Http1 { - -class ConnectionImpl; - -/** - * Base class for HTTP/1.1 request and response encoders. - */ -class StreamEncoderImpl : public virtual StreamEncoder, - public Stream, - Logger::Loggable, - public StreamCallbackHelper, - public Http1StreamEncoderOptions { -public: - ~StreamEncoderImpl() override { - // When the stream goes away, undo any read blocks to resume reading. - while (read_disable_calls_ != 0) { - StreamEncoderImpl::readDisable(false); - } - } - // Http::StreamEncoder - void encodeData(Buffer::Instance& data, bool end_stream) override; - void encodeMetadata(const MetadataMapVector&) override; - Stream& getStream() override { return *this; } - Http1StreamEncoderOptionsOptRef http1StreamEncoderOptions() override { return *this; } - - // Http::Http1StreamEncoderOptions - void disableChunkEncoding() override { disable_chunk_encoding_ = true; } - - // Http::Stream - void addCallbacks(StreamCallbacks& callbacks) override { addCallbacksHelper(callbacks); } - void removeCallbacks(StreamCallbacks& callbacks) override { removeCallbacksHelper(callbacks); } - // After this is called, for the HTTP/1 codec, the connection should be closed, i.e. no further - // progress may be made with the codec. - void resetStream(StreamResetReason reason) override; - void readDisable(bool disable) override; - uint32_t bufferLimit() override; - absl::string_view responseDetails() override { return details_; } - const Network::Address::InstanceConstSharedPtr& connectionLocalAddress() override; - void setFlushTimeout(std::chrono::milliseconds) override { - // HTTP/1 has one stream per connection, thus any data encoded is immediately written to the - // connection, invoking any watermarks as necessary. There is no internal buffering that would - // require a flush timeout not already covered by other timeouts. - } - - void setIsResponseToHeadRequest(bool value) { is_response_to_head_request_ = value; } - void setIsResponseToConnectRequest(bool value) { is_response_to_connect_request_ = value; } - void setDetails(absl::string_view details) { details_ = details; } - - void clearReadDisableCallsForTests() { read_disable_calls_ = 0; } - -protected: - StreamEncoderImpl(ConnectionImpl& connection, - Http::Http1::HeaderKeyFormatter* header_key_formatter); - void encodeHeadersBase(const RequestOrResponseHeaderMap& headers, absl::optional status, - bool end_stream); - void encodeTrailersBase(const HeaderMap& headers); - - static const std::string CRLF; - static const std::string LAST_CHUNK; - - ConnectionImpl& connection_; - uint32_t read_disable_calls_{}; - bool disable_chunk_encoding_ : 1; - bool chunk_encoding_ : 1; - bool is_response_to_head_request_ : 1; - bool is_response_to_connect_request_ : 1; - -private: - /** - * Called to encode an individual header. - * @param key supplies the header to encode. - * @param key_size supplies the byte size of the key. - * @param value supplies the value to encode. - * @param value_size supplies the byte size of the value. - */ - void encodeHeader(const char* key, uint32_t key_size, const char* value, uint32_t value_size); - - /** - * Called to encode an individual header. - * @param key supplies the header to encode as a string_view. - * @param value supplies the value to encode as a string_view. - */ - void encodeHeader(absl::string_view key, absl::string_view value); - - /** - * Called to finalize a stream encode. - */ - void endEncode(); - - void encodeFormattedHeader(absl::string_view key, absl::string_view value); - - const Http::Http1::HeaderKeyFormatter* const header_key_formatter_; - absl::string_view details_; -}; - -/** - * HTTP/1.1 response encoder. - */ -class ResponseEncoderImpl : public StreamEncoderImpl, public ResponseEncoder { -public: - ResponseEncoderImpl(ConnectionImpl& connection, - Http::Http1::HeaderKeyFormatter* header_key_formatter) - : StreamEncoderImpl(connection, header_key_formatter) {} - - bool startedResponse() { return started_response_; } - - // Http::ResponseEncoder - void encode100ContinueHeaders(const ResponseHeaderMap& headers) override; - void encodeHeaders(const ResponseHeaderMap& headers, bool end_stream) override; - void encodeTrailers(const ResponseTrailerMap& trailers) override { encodeTrailersBase(trailers); } - -private: - bool started_response_{}; -}; - -/** - * HTTP/1.1 request encoder. - */ -class RequestEncoderImpl : public StreamEncoderImpl, public RequestEncoder { -public: - RequestEncoderImpl(ConnectionImpl& connection, - Http::Http1::HeaderKeyFormatter* header_key_formatter) - : StreamEncoderImpl(connection, header_key_formatter) {} - bool upgradeRequest() const { return upgrade_request_; } - bool headRequest() const { return head_request_; } - bool connectRequest() const { return connect_request_; } - - // Http::RequestEncoder - void encodeHeaders(const RequestHeaderMap& headers, bool end_stream) override; - void encodeTrailers(const RequestTrailerMap& trailers) override { encodeTrailersBase(trailers); } - -private: - bool upgrade_request_{}; - bool head_request_{}; - bool connect_request_{}; -}; - -/** - * Base class for HTTP/1.1 client and server connections. - * Handles the callbacks of http_parser with its own base routine and then - * virtual dispatches to its subclasses. - */ -class ConnectionImpl : public virtual Connection, protected Logger::Loggable { -public: - /** - * @return Network::Connection& the backing network connection. - */ - Network::Connection& connection() { return connection_; } - - /** - * Called when the active encoder has completed encoding the outbound half of the stream. - */ - virtual void onEncodeComplete() PURE; - - /** - * Called when resetStream() has been called on an active stream. In HTTP/1.1 the only - * valid operation after this point is for the connection to get blown away, but we will not - * fire any more callbacks in case some stack has to unwind. - */ - void onResetStreamBase(StreamResetReason reason); - - /** - * Flush all pending output from encoding. - */ - void flushOutput(bool end_encode = false); - - void addToBuffer(absl::string_view data); - void addCharToBuffer(char c); - void addIntToBuffer(uint64_t i); - Buffer::WatermarkBuffer& buffer() { return output_buffer_; } - uint64_t bufferRemainingSize(); - void copyToBuffer(const char* data, uint64_t length); - void reserveBuffer(uint64_t size); - void readDisable(bool disable) { - if (connection_.state() == Network::Connection::State::Open) { - connection_.readDisable(disable); - } - } - uint32_t bufferLimit() { return connection_.bufferLimit(); } - virtual bool supportsHttp10() { return false; } - bool maybeDirectDispatch(Buffer::Instance& data); - virtual void maybeAddSentinelBufferFragment(Buffer::WatermarkBuffer&) {} - Http::Http1::CodecStats& stats() { return stats_; } - bool enableTrailers() const { return enable_trailers_; } - - // Http::Connection - Http::Status dispatch(Buffer::Instance& data) override; - void goAway() override {} // Called during connection manager drain flow - Protocol protocol() override { return protocol_; } - void shutdownNotice() override {} // Called during connection manager drain flow - bool wantsToWrite() override { return false; } - void onUnderlyingConnectionAboveWriteBufferHighWatermark() override { onAboveHighWatermark(); } - void onUnderlyingConnectionBelowWriteBufferLowWatermark() override { onBelowLowWatermark(); } - - bool strict1xxAnd204Headers() { return strict_1xx_and_204_headers_; } - -protected: - ConnectionImpl(Network::Connection& connection, Http::Http1::CodecStats& stats, - http_parser_type type, uint32_t max_headers_kb, const uint32_t max_headers_count, - Http::Http1::HeaderKeyFormatterPtr&& header_key_formatter, bool enable_trailers); - - bool resetStreamCalled() { return reset_stream_called_; } - void onMessageBeginBase(); - - /** - * Get memory used to represent HTTP headers or trailers currently being parsed. - * Computed by adding the partial header field and value that is currently being parsed and the - * estimated header size for previous header lines provided by HeaderMap::byteSize(). - */ - virtual uint32_t getHeadersSize(); - - /** - * Called from onUrl, onHeaderField and onHeaderValue to verify that the headers do not exceed the - * configured max header size limit. Throws a CodecProtocolException if headers exceed the size - * limit. - */ - void checkMaxHeadersSize(); - - Network::Connection& connection_; - Http::Http1::CodecStats& stats_; - http_parser parser_; - Http::Code error_code_{Http::Code::BadRequest}; - const Http::Http1::HeaderKeyFormatterPtr header_key_formatter_; - HeaderString current_header_field_; - HeaderString current_header_value_; - bool processing_trailers_ : 1; - bool handling_upgrade_ : 1; - bool reset_stream_called_ : 1; - // Deferred end stream headers indicate that we are not going to raise headers until the full - // HTTP/1 message has been flushed from the parser. This allows raising an HTTP/2 style headers - // block with end stream set to true with no further protocol data remaining. - bool deferred_end_stream_headers_ : 1; - const bool connection_header_sanitization_ : 1; - const bool enable_trailers_ : 1; - const bool strict_1xx_and_204_headers_ : 1; - -private: - enum class HeaderParsingState { Field, Value, Done }; - - virtual HeaderMap& headersOrTrailers() PURE; - virtual RequestOrResponseHeaderMap& requestOrResponseHeaders() PURE; - virtual void allocHeaders() PURE; - virtual void allocTrailers() PURE; - - /** - * Called in order to complete an in progress header decode. - */ - void completeLastHeader(); - - /** - * Check if header name contains underscore character. - * Underscore character is allowed in header names by the RFC-7230 and this check is implemented - * as a security measure due to systems that treat '_' and '-' as interchangeable. - * The ServerConnectionImpl may drop header or reject request based on the - * `common_http_protocol_options.headers_with_underscores_action` configuration option in the - * HttpConnectionManager. - */ - virtual bool shouldDropHeaderWithUnderscoresInNames(absl::string_view /* header_name */) const { - return false; - } - - /** - * An inner dispatch call that executes the dispatching logic. While exception removal is in - * migration (#10878), this function may either throw an exception or return an error status. - * Exceptions are caught and translated to their corresponding statuses in the outer level - * dispatch. - * TODO(#10878): Remove this when exception removal is complete. - */ - Http::Status innerDispatch(Buffer::Instance& data); - - /** - * Dispatch a memory span. - * @param slice supplies the start address. - * @len supplies the length of the span. - */ - size_t dispatchSlice(const char* slice, size_t len); - - /** - * Called by the http_parser when body data is received. - * @param data supplies the start address. - * @param length supplies the length. - */ - void bufferBody(const char* data, size_t length); - - /** - * Push the accumulated body through the filter pipeline. - */ - void dispatchBufferedBody(); - - /** - * Called when a request/response is beginning. A base routine happens first then a virtual - * dispatch is invoked. - */ - virtual void onMessageBegin() PURE; - - /** - * Called when URL data is received. - * @param data supplies the start address. - * @param length supplies the length. - */ - virtual void onUrl(const char* data, size_t length) PURE; - - /** - * Called when header field data is received. - * @param data supplies the start address. - * @param length supplies the length. - */ - void onHeaderField(const char* data, size_t length); - - /** - * Called when header value data is received. - * @param data supplies the start address. - * @param length supplies the length. - */ - void onHeaderValue(const char* data, size_t length); - - /** - * Called when headers are complete. A base routine happens first then a virtual dispatch is - * invoked. Note that this only applies to headers and NOT trailers. End of - * trailers are signaled via onMessageCompleteBase(). - * @return 0 if no error, 1 if there should be no body. - */ - int onHeadersCompleteBase(); - virtual int onHeadersComplete() PURE; - - /** - * Called to see if upgrade transition is allowed. - */ - virtual bool upgradeAllowed() const PURE; - - /** - * Called with body data is available for processing when either: - * - There is an accumulated partial body after the parser is done processing bytes read from the - * socket - * - The parser encounters the last byte of the body - * - The codec does a direct dispatch from the read buffer - * For performance reasons there is at most one call to onBody per call to HTTP/1 - * ConnectionImpl::dispatch call. - * @param data supplies the body data - */ - virtual void onBody(Buffer::Instance& data) PURE; - - /** - * Called when the request/response is complete. - */ - void onMessageCompleteBase(); - virtual void onMessageComplete() PURE; - - /** - * Called when accepting a chunk header. - */ - void onChunkHeader(bool is_final_chunk); - - /** - * @see onResetStreamBase(). - */ - virtual void onResetStream(StreamResetReason reason) PURE; - - /** - * Send a protocol error response to remote. - */ - virtual void sendProtocolError(absl::string_view details) PURE; - - /** - * Called when output_buffer_ or the underlying connection go from below a low watermark to over - * a high watermark. - */ - virtual void onAboveHighWatermark() PURE; - - /** - * Called when output_buffer_ or the underlying connection go from above a high watermark to - * below a low watermark. - */ - virtual void onBelowLowWatermark() PURE; - - /** - * Check if header name contains underscore character. - * The ServerConnectionImpl may drop header or reject request based on configuration. - */ - virtual void checkHeaderNameForUnderscores() {} - - static http_parser_settings settings_; - - HeaderParsingState header_parsing_state_{HeaderParsingState::Field}; - // Used to accumulate the HTTP message body during the current dispatch call. The accumulated body - // is pushed through the filter pipeline either at the end of the current dispatch call, or when - // the last byte of the body is processed (whichever happens first). - Buffer::OwnedImpl buffered_body_; - Buffer::WatermarkBuffer output_buffer_; - Protocol protocol_{Protocol::Http11}; - const uint32_t max_headers_kb_; - const uint32_t max_headers_count_; -}; - -/** - * Implementation of Http::ServerConnection for HTTP/1.1. - */ -class ServerConnectionImpl : public ServerConnection, public ConnectionImpl { -public: - ServerConnectionImpl(Network::Connection& connection, Http::Http1::CodecStats& stats, - ServerConnectionCallbacks& callbacks, const Http1Settings& settings, - uint32_t max_request_headers_kb, const uint32_t max_request_headers_count, - envoy::config::core::v3::HttpProtocolOptions::HeadersWithUnderscoresAction - headers_with_underscores_action); - bool supportsHttp10() override { return codec_settings_.accept_http_10_; } - -protected: - /** - * An active HTTP/1.1 request. - */ - struct ActiveRequest { - ActiveRequest(ConnectionImpl& connection, Http::Http1::HeaderKeyFormatter* header_key_formatter) - : response_encoder_(connection, header_key_formatter) {} - - HeaderString request_url_; - RequestDecoder* request_decoder_{}; - ResponseEncoderImpl response_encoder_; - bool remote_complete_{}; - }; - absl::optional& activeRequest() { return active_request_; } - // ConnectionImpl - void onMessageComplete() override; - // Add the size of the request_url to the reported header size when processing request headers. - uint32_t getHeadersSize() override; - -private: - /** - * Manipulate the request's first line, parsing the url and converting to a relative path if - * necessary. Compute Host / :authority headers based on 7230#5.7 and 7230#6 - * - * @param is_connect true if the request has the CONNECT method - * @param headers the request's headers - * @throws CodecProtocolException on an invalid url in the request line - */ - void handlePath(RequestHeaderMap& headers, unsigned int method); - - // ConnectionImpl - void onEncodeComplete() override; - void onMessageBegin() override; - void onUrl(const char* data, size_t length) override; - int onHeadersComplete() override; - // If upgrade behavior is not allowed, the HCM will have sanitized the headers out. - bool upgradeAllowed() const override { return true; } - void onBody(Buffer::Instance& data) override; - void onResetStream(StreamResetReason reason) override; - void sendProtocolError(absl::string_view details) override; - void onAboveHighWatermark() override; - void onBelowLowWatermark() override; - HeaderMap& headersOrTrailers() override { - if (absl::holds_alternative(headers_or_trailers_)) { - return *absl::get(headers_or_trailers_); - } else { - return *absl::get(headers_or_trailers_); - } - } - RequestOrResponseHeaderMap& requestOrResponseHeaders() override { - return *absl::get(headers_or_trailers_); - } - void allocHeaders() override { - ASSERT(nullptr == absl::get(headers_or_trailers_)); - ASSERT(!processing_trailers_); - headers_or_trailers_.emplace(RequestHeaderMapImpl::create()); - } - void allocTrailers() override { - ASSERT(processing_trailers_); - if (!absl::holds_alternative(headers_or_trailers_)) { - headers_or_trailers_.emplace(RequestTrailerMapImpl::create()); - } - } - - void sendProtocolErrorOld(absl::string_view details); - - void releaseOutboundResponse(const Buffer::OwnedBufferFragmentImpl* fragment); - void maybeAddSentinelBufferFragment(Buffer::WatermarkBuffer& output_buffer) override; - void doFloodProtectionChecks() const; - void checkHeaderNameForUnderscores() override; - - ServerConnectionCallbacks& callbacks_; - absl::optional active_request_; - Http1Settings codec_settings_; - const Buffer::OwnedBufferFragmentImpl::Releasor response_buffer_releasor_; - uint32_t outbound_responses_{}; - // This defaults to 2, which functionally disables pipelining. If any users - // of Envoy wish to enable pipelining (which is dangerous and ill supported) - // we could make this configurable. - uint32_t max_outbound_responses_{}; - bool flood_protection_{}; - // TODO(mattklein123): This should be a member of ActiveRequest but this change needs dedicated - // thought as some of the reset and no header code paths make this difficult. Headers are - // populated on message begin. Trailers are populated on the first parsed trailer field (if - // trailers are enabled). The variant is reset to null headers on message complete for assertion - // purposes. - absl::variant headers_or_trailers_; - // The action to take when a request header name contains underscore characters. - const envoy::config::core::v3::HttpProtocolOptions::HeadersWithUnderscoresAction - headers_with_underscores_action_; -}; - -/** - * Implementation of Http::ClientConnection for HTTP/1.1. - */ -class ClientConnectionImpl : public ClientConnection, public ConnectionImpl { -public: - ClientConnectionImpl(Network::Connection& connection, Http::Http1::CodecStats& stats, - ConnectionCallbacks& callbacks, const Http1Settings& settings, - const uint32_t max_response_headers_count); - - // Http::ClientConnection - RequestEncoder& newStream(ResponseDecoder& response_decoder) override; - -private: - struct PendingResponse { - PendingResponse(ConnectionImpl& connection, - Http::Http1::HeaderKeyFormatter* header_key_formatter, ResponseDecoder* decoder) - : encoder_(connection, header_key_formatter), decoder_(decoder) {} - - RequestEncoderImpl encoder_; - ResponseDecoder* decoder_; - }; - - bool cannotHaveBody(); - - // ConnectionImpl - void onEncodeComplete() override {} - void onMessageBegin() override {} - void onUrl(const char*, size_t) override { NOT_IMPLEMENTED_GCOVR_EXCL_LINE; } - int onHeadersComplete() override; - bool upgradeAllowed() const override; - void onBody(Buffer::Instance& data) override; - void onMessageComplete() override; - void onResetStream(StreamResetReason reason) override; - void sendProtocolError(absl::string_view details) override; - void onAboveHighWatermark() override; - void onBelowLowWatermark() override; - HeaderMap& headersOrTrailers() override { - if (absl::holds_alternative(headers_or_trailers_)) { - return *absl::get(headers_or_trailers_); - } else { - return *absl::get(headers_or_trailers_); - } - } - RequestOrResponseHeaderMap& requestOrResponseHeaders() override { - return *absl::get(headers_or_trailers_); - } - void allocHeaders() override { - ASSERT(nullptr == absl::get(headers_or_trailers_)); - ASSERT(!processing_trailers_); - headers_or_trailers_.emplace(ResponseHeaderMapImpl::create()); - } - void allocTrailers() override { - ASSERT(processing_trailers_); - if (!absl::holds_alternative(headers_or_trailers_)) { - headers_or_trailers_.emplace(ResponseTrailerMapImpl::create()); - } - } - - absl::optional pending_response_; - // TODO(mattklein123): The following bool tracks whether a pending response is complete before - // dispatching callbacks. This is needed so that pending_response_ stays valid during callbacks - // in order to access the stream, but to avoid invoking callbacks that shouldn't be called once - // the response is complete. The existence of this variable is hard to reason about and it should - // be combined with pending_response_ somehow in a follow up cleanup. - bool pending_response_done_{true}; - // Set true between receiving 100-Continue headers and receiving the spurious onMessageComplete. - bool ignore_message_complete_for_100_continue_{}; - // TODO(mattklein123): This should be a member of PendingResponse but this change needs dedicated - // thought as some of the reset and no header code paths make this difficult. Headers are - // populated on message begin. Trailers are populated when the switch to trailer processing is - // detected while parsing the first trailer field (if trailers are enabled). The variant is reset - // to null headers on message complete for assertion purposes. - absl::variant headers_or_trailers_; - - // The default limit of 80 KiB is the vanilla http_parser behaviour. - static constexpr uint32_t MAX_RESPONSE_HEADERS_KB = 80; -}; - -} // namespace Http1 -} // namespace Legacy -} // namespace Http -} // namespace Envoy diff --git a/source/common/http/http2/BUILD b/source/common/http/http2/BUILD index d2e4ed011311..dd0333ffa847 100644 --- a/source/common/http/http2/BUILD +++ b/source/common/http/http2/BUILD @@ -18,36 +18,6 @@ envoy_cc_library( ], ) -CODEC_LIB_DEPS = [ - ":codec_stats_lib", - ":metadata_decoder_lib", - ":metadata_encoder_lib", - "//include/envoy/event:deferred_deletable", - "//include/envoy/event:dispatcher_interface", - "//include/envoy/http:codec_interface", - "//include/envoy/http:codes_interface", - "//include/envoy/http:header_map_interface", - "//include/envoy/network:connection_interface", - "//include/envoy/stats:stats_interface", - "//source/common/buffer:buffer_lib", - "//source/common/buffer:watermark_buffer_lib", - "//source/common/common:assert_lib", - "//source/common/common:enum_to_int", - "//source/common/common:linked_object", - "//source/common/common:minimal_logger_lib", - "//source/common/common:utility_lib", - "//source/common/http:codec_helper_lib", - "//source/common/http:codes_lib", - "//source/common/http:exception_lib", - "//source/common/http:header_map_lib", - "//source/common/http:header_utility_lib", - "//source/common/http:headers_lib", - "//source/common/http:status_lib", - "//source/common/http:utility_lib", - "//source/common/runtime:runtime_features_lib", - "@envoy_api//envoy/config/core/v3:pkg_cc_proto", -] - envoy_cc_library( name = "codec_lib", srcs = ["codec_impl.cc"], @@ -58,23 +28,35 @@ envoy_cc_library( "abseil_inlined_vector", "abseil_algorithm", ], - deps = CODEC_LIB_DEPS, -) - -envoy_cc_library( - name = "codec_legacy_lib", - srcs = ["codec_impl_legacy.cc"], - hdrs = [ - "codec_impl.h", - "codec_impl_legacy.h", - ], - external_deps = [ - "nghttp2", - "abseil_optional", - "abseil_inlined_vector", - "abseil_algorithm", + deps = [ + ":codec_stats_lib", + ":metadata_decoder_lib", + ":metadata_encoder_lib", + "//include/envoy/event:deferred_deletable", + "//include/envoy/event:dispatcher_interface", + "//include/envoy/http:codec_interface", + "//include/envoy/http:codes_interface", + "//include/envoy/http:header_map_interface", + "//include/envoy/network:connection_interface", + "//include/envoy/stats:stats_interface", + "//source/common/buffer:buffer_lib", + "//source/common/buffer:watermark_buffer_lib", + "//source/common/common:assert_lib", + "//source/common/common:enum_to_int", + "//source/common/common:linked_object", + "//source/common/common:minimal_logger_lib", + "//source/common/common:utility_lib", + "//source/common/http:codec_helper_lib", + "//source/common/http:codes_lib", + "//source/common/http:exception_lib", + "//source/common/http:header_map_lib", + "//source/common/http:header_utility_lib", + "//source/common/http:headers_lib", + "//source/common/http:status_lib", + "//source/common/http:utility_lib", + "//source/common/runtime:runtime_features_lib", + "@envoy_api//envoy/config/core/v3:pkg_cc_proto", ], - deps = CODEC_LIB_DEPS, ) # Separate library for some nghttp2 setup stuff to avoid having tests take a diff --git a/source/common/http/http2/codec_impl.cc b/source/common/http/http2/codec_impl.cc index 9c56c034cbd5..b25b8fa4bef5 100644 --- a/source/common/http/http2/codec_impl.cc +++ b/source/common/http/http2/codec_impl.cc @@ -87,7 +87,7 @@ void ProdNghttp2SessionFactory::init(nghttp2_session*, ConnectionImpl* connectio * Helper to remove const during a cast. nghttp2 takes non-const pointers for headers even though * it copies them. */ -template static T* removeConst(const void* object) { +template static T* remove_const(const void* object) { return const_cast(reinterpret_cast(object)); } @@ -120,8 +120,8 @@ static void insertHeader(std::vector& headers, const HeaderEntry& he } const absl::string_view header_key = header.key().getStringView(); const absl::string_view header_value = header.value().getStringView(); - headers.push_back({removeConst(header_key.data()), - removeConst(header_value.data()), header_key.size(), + headers.push_back({remove_const(header_key.data()), + remove_const(header_value.data()), header_key.size(), header_value.size(), flags}); } @@ -244,7 +244,7 @@ void ConnectionImpl::StreamImpl::readDisable(bool disable) { } else { ASSERT(read_disable_count_ > 0); --read_disable_count_; - if (!buffersOverrun()) { + if (!buffers_overrun()) { nghttp2_session_consume(parent_.session_, stream_id_, unconsumed_bytes_); unconsumed_bytes_ = 0; parent_.sendPendingFrames(); @@ -560,7 +560,7 @@ int ConnectionImpl::onData(int32_t stream_id, const uint8_t* data, size_t len) { stream->pending_recv_data_.add(data, len); // Update the window to the peer unless some consumer of this stream's data has hit a flow control // limit and disabled reads on this stream - if (!stream->buffersOverrun()) { + if (!stream->buffers_overrun()) { nghttp2_session_consume(session_, stream_id, len); } else { stream->unconsumed_bytes_ += len; diff --git a/source/common/http/http2/codec_impl.h b/source/common/http/http2/codec_impl.h index 8bd5d8b3d1fd..cf848599c800 100644 --- a/source/common/http/http2/codec_impl.h +++ b/source/common/http/http2/codec_impl.h @@ -54,16 +54,14 @@ class ConnectionImpl; // Abstract nghttp2_session factory. Used to enable injection of factories for testing. class Nghttp2SessionFactory { public: - using ConnectionImplType = ConnectionImpl; virtual ~Nghttp2SessionFactory() = default; // Returns a new nghttp2_session to be used with |connection|. virtual nghttp2_session* create(const nghttp2_session_callbacks* callbacks, - ConnectionImplType* connection, - const nghttp2_option* options) PURE; + ConnectionImpl* connection, const nghttp2_option* options) PURE; // Initializes the |session|. - virtual void init(nghttp2_session* session, ConnectionImplType* connection, + virtual void init(nghttp2_session* session, ConnectionImpl* connection, const envoy::config::core::v3::Http2ProtocolOptions& options) PURE; }; @@ -258,7 +256,7 @@ class ConnectionImpl : public virtual Connection, protected Logger::Loggable 0; } + bool buffers_overrun() const { return read_disable_count_ > 0; } ConnectionImpl& parent_; int32_t stream_id_{-1}; @@ -520,13 +518,12 @@ class ConnectionImpl : public virtual Connection, protected Logger::Loggable -#include -#include - -#include "envoy/event/dispatcher.h" -#include "envoy/http/codes.h" -#include "envoy/http/header_map.h" -#include "envoy/network/connection.h" - -#include "common/common/assert.h" -#include "common/common/cleanup.h" -#include "common/common/enum_to_int.h" -#include "common/common/fmt.h" -#include "common/common/utility.h" -#include "common/http/codes.h" -#include "common/http/exception.h" -#include "common/http/header_utility.h" -#include "common/http/headers.h" -#include "common/http/http2/codec_stats.h" -#include "common/http/utility.h" - -#include "absl/container/fixed_array.h" - -namespace Envoy { -namespace Http { -namespace Legacy { -namespace Http2 { - -class Http2ResponseCodeDetailValues { - // Invalid HTTP header field was received and stream is going to be - // closed. - const absl::string_view ng_http2_err_http_header_ = "http2.invalid.header.field"; - - // Violation in HTTP messaging rule. - const absl::string_view ng_http2_err_http_messaging_ = "http2.violation.of.messaging.rule"; - - // none of the above - const absl::string_view ng_http2_err_unknown_ = "http2.unknown.nghttp2.error"; - -public: - const absl::string_view errorDetails(int error_code) const { - switch (error_code) { - case NGHTTP2_ERR_HTTP_HEADER: - return ng_http2_err_http_header_; - case NGHTTP2_ERR_HTTP_MESSAGING: - return ng_http2_err_http_messaging_; - default: - return ng_http2_err_unknown_; - } - } -}; - -using Http2ResponseCodeDetails = ConstSingleton; -using Http::Http2::CodecStats; -using Http::Http2::MetadataDecoder; -using Http::Http2::MetadataEncoder; - -bool Utility::reconstituteCrumbledCookies(const HeaderString& key, const HeaderString& value, - HeaderString& cookies) { - if (key != Headers::get().Cookie.get().c_str()) { - return false; - } - - if (!cookies.empty()) { - cookies.append("; ", 2); - } - - const absl::string_view value_view = value.getStringView(); - cookies.append(value_view.data(), value_view.size()); - return true; -} - -ConnectionImpl::Http2Callbacks ConnectionImpl::http2_callbacks_; - -nghttp2_session* ProdNghttp2SessionFactory::create(const nghttp2_session_callbacks* callbacks, - ConnectionImpl* connection, - const nghttp2_option* options) { - nghttp2_session* session; - nghttp2_session_client_new2(&session, callbacks, connection, options); - return session; -} - -void ProdNghttp2SessionFactory::init(nghttp2_session*, ConnectionImpl* connection, - const envoy::config::core::v3::Http2ProtocolOptions& options) { - connection->sendSettings(options, true); -} - -/** - * Helper to remove const during a cast. nghttp2 takes non-const pointers for headers even though - * it copies them. - */ -template static T* removeConst(const void* object) { - return const_cast(reinterpret_cast(object)); -} - -ConnectionImpl::StreamImpl::StreamImpl(ConnectionImpl& parent, uint32_t buffer_limit) - : parent_(parent), local_end_stream_sent_(false), remote_end_stream_(false), - data_deferred_(false), waiting_for_non_informational_headers_(false), - pending_receive_buffer_high_watermark_called_(false), - pending_send_buffer_high_watermark_called_(false), reset_due_to_messaging_error_(false) { - parent_.stats_.streams_active_.inc(); - if (buffer_limit > 0) { - setWriteBufferWatermarks(buffer_limit / 2, buffer_limit); - } -} - -ConnectionImpl::StreamImpl::~StreamImpl() { ASSERT(stream_idle_timer_ == nullptr); } - -void ConnectionImpl::StreamImpl::destroy() { - disarmStreamIdleTimer(); - parent_.stats_.streams_active_.dec(); - parent_.stats_.pending_send_bytes_.sub(pending_send_data_.length()); -} - -static void insertHeader(std::vector& headers, const HeaderEntry& header) { - uint8_t flags = 0; - if (header.key().isReference()) { - flags |= NGHTTP2_NV_FLAG_NO_COPY_NAME; - } - if (header.value().isReference()) { - flags |= NGHTTP2_NV_FLAG_NO_COPY_VALUE; - } - const absl::string_view header_key = header.key().getStringView(); - const absl::string_view header_value = header.value().getStringView(); - headers.push_back({removeConst(header_key.data()), - removeConst(header_value.data()), header_key.size(), - header_value.size(), flags}); -} - -void ConnectionImpl::StreamImpl::buildHeaders(std::vector& final_headers, - const HeaderMap& headers) { - final_headers.reserve(headers.size()); - headers.iterate( - [](const HeaderEntry& header, void* context) -> HeaderMap::Iterate { - std::vector* final_headers = static_cast*>(context); - insertHeader(*final_headers, header); - return HeaderMap::Iterate::Continue; - }, - &final_headers); -} - -void ConnectionImpl::ServerStreamImpl::encode100ContinueHeaders(const ResponseHeaderMap& headers) { - ASSERT(headers.Status()->value() == "100"); - encodeHeaders(headers, false); -} - -void ConnectionImpl::StreamImpl::encodeHeadersBase(const std::vector& final_headers, - bool end_stream) { - nghttp2_data_provider provider; - if (!end_stream) { - provider.source.ptr = this; - provider.read_callback = [](nghttp2_session*, int32_t, uint8_t*, size_t length, - uint32_t* data_flags, nghttp2_data_source* source, - void*) -> ssize_t { - return static_cast(source->ptr)->onDataSourceRead(length, data_flags); - }; - } - - local_end_stream_ = end_stream; - submitHeaders(final_headers, end_stream ? nullptr : &provider); - parent_.sendPendingFrames(); -} - -void ConnectionImpl::ClientStreamImpl::encodeHeaders(const RequestHeaderMap& headers, - bool end_stream) { - // This must exist outside of the scope of isUpgrade as the underlying memory is - // needed until encodeHeadersBase has been called. - std::vector final_headers; - Http::RequestHeaderMapPtr modified_headers; - if (Http::Utility::isUpgrade(headers)) { - modified_headers = createHeaderMap(headers); - upgrade_type_ = std::string(headers.getUpgradeValue()); - Http::Utility::transformUpgradeRequestFromH1toH2(*modified_headers); - buildHeaders(final_headers, *modified_headers); - } else if (headers.Method() && headers.Method()->value() == "CONNECT") { - // If this is not an upgrade style connect (above branch) it is a bytestream - // connect and should have :path and :protocol set accordingly - // As HTTP/1.1 does not require a path for CONNECT, we may have to add one - // if shifting codecs. For now, default to "/" - this can be made - // configurable if necessary. - // https://tools.ietf.org/html/draft-kinnear-httpbis-http2-transport-02 - modified_headers = createHeaderMap(headers); - modified_headers->setProtocol(Headers::get().ProtocolValues.Bytestream); - if (!headers.Path()) { - modified_headers->setPath("/"); - } - buildHeaders(final_headers, *modified_headers); - } else { - buildHeaders(final_headers, headers); - } - encodeHeadersBase(final_headers, end_stream); -} - -void ConnectionImpl::ServerStreamImpl::encodeHeaders(const ResponseHeaderMap& headers, - bool end_stream) { - // The contract is that client codecs must ensure that :status is present. - ASSERT(headers.Status() != nullptr); - - // This must exist outside of the scope of isUpgrade as the underlying memory is - // needed until encodeHeadersBase has been called. - std::vector final_headers; - Http::ResponseHeaderMapPtr modified_headers; - if (Http::Utility::isUpgrade(headers)) { - modified_headers = createHeaderMap(headers); - Http::Utility::transformUpgradeResponseFromH1toH2(*modified_headers); - buildHeaders(final_headers, *modified_headers); - } else { - buildHeaders(final_headers, headers); - } - encodeHeadersBase(final_headers, end_stream); -} - -void ConnectionImpl::StreamImpl::encodeTrailersBase(const HeaderMap& trailers) { - ASSERT(!local_end_stream_); - local_end_stream_ = true; - if (pending_send_data_.length() > 0) { - // In this case we want trailers to come after we release all pending body data that is - // waiting on window updates. We need to save the trailers so that we can emit them later. - ASSERT(!pending_trailers_to_encode_); - pending_trailers_to_encode_ = cloneTrailers(trailers); - createPendingFlushTimer(); - } else { - submitTrailers(trailers); - parent_.sendPendingFrames(); - } -} - -void ConnectionImpl::StreamImpl::encodeMetadata(const MetadataMapVector& metadata_map_vector) { - ASSERT(parent_.allow_metadata_); - MetadataEncoder& metadata_encoder = getMetadataEncoder(); - if (!metadata_encoder.createPayload(metadata_map_vector)) { - return; - } - for (uint8_t flags : metadata_encoder.payloadFrameFlagBytes()) { - submitMetadata(flags); - } - parent_.sendPendingFrames(); -} - -void ConnectionImpl::StreamImpl::readDisable(bool disable) { - ENVOY_CONN_LOG(debug, "Stream {} {}, unconsumed_bytes {} read_disable_count {}", - parent_.connection_, stream_id_, (disable ? "disabled" : "enabled"), - unconsumed_bytes_, read_disable_count_); - if (disable) { - ++read_disable_count_; - } else { - ASSERT(read_disable_count_ > 0); - --read_disable_count_; - if (!buffersOverrun()) { - nghttp2_session_consume(parent_.session_, stream_id_, unconsumed_bytes_); - unconsumed_bytes_ = 0; - parent_.sendPendingFrames(); - } - } -} - -void ConnectionImpl::StreamImpl::pendingRecvBufferHighWatermark() { - ENVOY_CONN_LOG(debug, "recv buffer over limit ", parent_.connection_); - ASSERT(!pending_receive_buffer_high_watermark_called_); - pending_receive_buffer_high_watermark_called_ = true; - readDisable(true); -} - -void ConnectionImpl::StreamImpl::pendingRecvBufferLowWatermark() { - ENVOY_CONN_LOG(debug, "recv buffer under limit ", parent_.connection_); - ASSERT(pending_receive_buffer_high_watermark_called_); - pending_receive_buffer_high_watermark_called_ = false; - readDisable(false); -} - -void ConnectionImpl::ClientStreamImpl::decodeHeaders(bool allow_waiting_for_informational_headers) { - auto& headers = absl::get(headers_or_trailers_); - if (allow_waiting_for_informational_headers && - CodeUtility::is1xx(Http::Utility::getResponseStatus(*headers))) { - waiting_for_non_informational_headers_ = true; - } - - if (!upgrade_type_.empty() && headers->Status()) { - Http::Utility::transformUpgradeResponseFromH2toH1(*headers, upgrade_type_); - } - - if (headers->Status()->value() == "100") { - ASSERT(!remote_end_stream_); - response_decoder_.decode100ContinueHeaders(std::move(headers)); - } else { - response_decoder_.decodeHeaders(std::move(headers), remote_end_stream_); - } -} - -void ConnectionImpl::ClientStreamImpl::decodeTrailers() { - response_decoder_.decodeTrailers( - std::move(absl::get(headers_or_trailers_))); -} - -void ConnectionImpl::ServerStreamImpl::decodeHeaders(bool allow_waiting_for_informational_headers) { - ASSERT(!allow_waiting_for_informational_headers); - auto& headers = absl::get(headers_or_trailers_); - if (Http::Utility::isH2UpgradeRequest(*headers)) { - Http::Utility::transformUpgradeRequestFromH2toH1(*headers); - } - request_decoder_->decodeHeaders(std::move(headers), remote_end_stream_); -} - -void ConnectionImpl::ServerStreamImpl::decodeTrailers() { - request_decoder_->decodeTrailers( - std::move(absl::get(headers_or_trailers_))); -} - -void ConnectionImpl::StreamImpl::pendingSendBufferHighWatermark() { - ENVOY_CONN_LOG(debug, "send buffer over limit ", parent_.connection_); - ASSERT(!pending_send_buffer_high_watermark_called_); - pending_send_buffer_high_watermark_called_ = true; - runHighWatermarkCallbacks(); -} - -void ConnectionImpl::StreamImpl::pendingSendBufferLowWatermark() { - ENVOY_CONN_LOG(debug, "send buffer under limit ", parent_.connection_); - ASSERT(pending_send_buffer_high_watermark_called_); - pending_send_buffer_high_watermark_called_ = false; - runLowWatermarkCallbacks(); -} - -void ConnectionImpl::StreamImpl::saveHeader(HeaderString&& name, HeaderString&& value) { - if (!Utility::reconstituteCrumbledCookies(name, value, cookies_)) { - headers().addViaMove(std::move(name), std::move(value)); - } -} - -void ConnectionImpl::StreamImpl::submitTrailers(const HeaderMap& trailers) { - std::vector final_headers; - buildHeaders(final_headers, trailers); - int rc = nghttp2_submit_trailer(parent_.session_, stream_id_, final_headers.data(), - final_headers.size()); - ASSERT(rc == 0); -} - -void ConnectionImpl::StreamImpl::submitMetadata(uint8_t flags) { - ASSERT(stream_id_ > 0); - const int result = - nghttp2_submit_extension(parent_.session_, METADATA_FRAME_TYPE, flags, stream_id_, nullptr); - ASSERT(result == 0); -} - -ssize_t ConnectionImpl::StreamImpl::onDataSourceRead(uint64_t length, uint32_t* data_flags) { - if (pending_send_data_.length() == 0 && !local_end_stream_) { - ASSERT(!data_deferred_); - data_deferred_ = true; - return NGHTTP2_ERR_DEFERRED; - } else { - *data_flags |= NGHTTP2_DATA_FLAG_NO_COPY; - if (local_end_stream_ && pending_send_data_.length() <= length) { - *data_flags |= NGHTTP2_DATA_FLAG_EOF; - if (pending_trailers_to_encode_) { - // We need to tell the library to not set end stream so that we can emit the trailers. - *data_flags |= NGHTTP2_DATA_FLAG_NO_END_STREAM; - submitTrailers(*pending_trailers_to_encode_); - pending_trailers_to_encode_.reset(); - } - } - - return std::min(length, pending_send_data_.length()); - } -} - -int ConnectionImpl::StreamImpl::onDataSourceSend(const uint8_t* framehd, size_t length) { - // In this callback we are writing out a raw DATA frame without copying. nghttp2 assumes that we - // "just know" that the frame header is 9 bytes. - // https://nghttp2.org/documentation/types.html#c.nghttp2_send_data_callback - static const uint64_t FRAME_HEADER_SIZE = 9; - - parent_.outbound_data_frames_++; - - Buffer::OwnedImpl output; - if (!parent_.addOutboundFrameFragment(output, framehd, FRAME_HEADER_SIZE)) { - ENVOY_CONN_LOG(debug, "error sending data frame: Too many frames in the outbound queue", - parent_.connection_); - return NGHTTP2_ERR_FLOODED; - } - - parent_.stats_.pending_send_bytes_.sub(length); - output.move(pending_send_data_, length); - parent_.connection_.write(output, false); - return 0; -} - -void ConnectionImpl::ClientStreamImpl::submitHeaders(const std::vector& final_headers, - nghttp2_data_provider* provider) { - ASSERT(stream_id_ == -1); - stream_id_ = nghttp2_submit_request(parent_.session_, nullptr, final_headers.data(), - final_headers.size(), provider, base()); - ASSERT(stream_id_ > 0); -} - -void ConnectionImpl::ServerStreamImpl::submitHeaders(const std::vector& final_headers, - nghttp2_data_provider* provider) { - ASSERT(stream_id_ != -1); - int rc = nghttp2_submit_response(parent_.session_, stream_id_, final_headers.data(), - final_headers.size(), provider); - ASSERT(rc == 0); -} - -void ConnectionImpl::ServerStreamImpl::createPendingFlushTimer() { - ASSERT(stream_idle_timer_ == nullptr); - if (stream_idle_timeout_.count() > 0) { - stream_idle_timer_ = - parent_.connection_.dispatcher().createTimer([this] { onPendingFlushTimer(); }); - stream_idle_timer_->enableTimer(stream_idle_timeout_); - } -} - -void ConnectionImpl::StreamImpl::onPendingFlushTimer() { - ENVOY_CONN_LOG(debug, "pending stream flush timeout", parent_.connection_); - stream_idle_timer_.reset(); - parent_.stats_.tx_flush_timeout_.inc(); - ASSERT(local_end_stream_ && !local_end_stream_sent_); - // This will emit a reset frame for this stream and close the stream locally. No reset callbacks - // will be run because higher layers think the stream is already finished. - resetStreamWorker(StreamResetReason::LocalReset); - parent_.sendPendingFrames(); -} - -void ConnectionImpl::StreamImpl::encodeData(Buffer::Instance& data, bool end_stream) { - ASSERT(!local_end_stream_); - local_end_stream_ = end_stream; - parent_.stats_.pending_send_bytes_.add(data.length()); - pending_send_data_.move(data); - if (data_deferred_) { - int rc = nghttp2_session_resume_data(parent_.session_, stream_id_); - ASSERT(rc == 0); - - data_deferred_ = false; - } - - parent_.sendPendingFrames(); - if (local_end_stream_ && pending_send_data_.length() > 0) { - createPendingFlushTimer(); - } -} - -void ConnectionImpl::StreamImpl::resetStream(StreamResetReason reason) { - // Higher layers expect calling resetStream() to immediately raise reset callbacks. - runResetCallbacks(reason); - - // If we submit a reset, nghttp2 will cancel outbound frames that have not yet been sent. - // We want these frames to go out so we defer the reset until we send all of the frames that - // end the local stream. - if (local_end_stream_ && !local_end_stream_sent_) { - parent_.pending_deferred_reset_ = true; - deferred_reset_ = reason; - ENVOY_CONN_LOG(trace, "deferred reset stream", parent_.connection_); - } else { - resetStreamWorker(reason); - } - - // We must still call sendPendingFrames() in both the deferred and not deferred path. This forces - // the cleanup logic to run which will reset the stream in all cases if all data frames could not - // be sent. - parent_.sendPendingFrames(); -} - -void ConnectionImpl::StreamImpl::resetStreamWorker(StreamResetReason reason) { - int rc = nghttp2_submit_rst_stream(parent_.session_, NGHTTP2_FLAG_NONE, stream_id_, - reason == StreamResetReason::LocalRefusedStreamReset - ? NGHTTP2_REFUSED_STREAM - : NGHTTP2_NO_ERROR); - ASSERT(rc == 0); -} - -MetadataEncoder& ConnectionImpl::StreamImpl::getMetadataEncoder() { - if (metadata_encoder_ == nullptr) { - metadata_encoder_ = std::make_unique(); - } - return *metadata_encoder_; -} - -MetadataDecoder& ConnectionImpl::StreamImpl::getMetadataDecoder() { - if (metadata_decoder_ == nullptr) { - auto cb = [this](MetadataMapPtr&& metadata_map_ptr) { - this->onMetadataDecoded(std::move(metadata_map_ptr)); - }; - metadata_decoder_ = std::make_unique(cb); - } - return *metadata_decoder_; -} - -void ConnectionImpl::StreamImpl::onMetadataDecoded(MetadataMapPtr&& metadata_map_ptr) { - decoder().decodeMetadata(std::move(metadata_map_ptr)); -} - -ConnectionImpl::ConnectionImpl(Network::Connection& connection, CodecStats& stats, - const envoy::config::core::v3::Http2ProtocolOptions& http2_options, - const uint32_t max_headers_kb, const uint32_t max_headers_count) - : stats_(stats), connection_(connection), max_headers_kb_(max_headers_kb), - max_headers_count_(max_headers_count), - per_stream_buffer_limit_(http2_options.initial_stream_window_size().value()), - stream_error_on_invalid_http_messaging_( - http2_options.stream_error_on_invalid_http_messaging()), - flood_detected_(false), max_outbound_frames_(http2_options.max_outbound_frames().value()), - frame_buffer_releasor_([this]() { releaseOutboundFrame(); }), - max_outbound_control_frames_(http2_options.max_outbound_control_frames().value()), - control_frame_buffer_releasor_([this]() { releaseOutboundControlFrame(); }), - max_consecutive_inbound_frames_with_empty_payload_( - http2_options.max_consecutive_inbound_frames_with_empty_payload().value()), - max_inbound_priority_frames_per_stream_( - http2_options.max_inbound_priority_frames_per_stream().value()), - max_inbound_window_update_frames_per_data_frame_sent_( - http2_options.max_inbound_window_update_frames_per_data_frame_sent().value()), - dispatching_(false), raised_goaway_(false), pending_deferred_reset_(false) {} - -ConnectionImpl::~ConnectionImpl() { - for (const auto& stream : active_streams_) { - stream->destroy(); - } - nghttp2_session_del(session_); -} - -Http::Status ConnectionImpl::dispatch(Buffer::Instance& data) { - // TODO(#10878): Remove this wrapper when exception removal is complete. innerDispatch may either - // throw an exception or return an error status. The utility wrapper catches exceptions and - // converts them to error statuses. - return Http::Utility::exceptionToStatus( - [&](Buffer::Instance& data) -> Http::Status { return innerDispatch(data); }, data); -} - -Http::Status ConnectionImpl::innerDispatch(Buffer::Instance& data) { - ENVOY_CONN_LOG(trace, "dispatching {} bytes", connection_, data.length()); - // Make sure that dispatching_ is set to false after dispatching, even when - // ConnectionImpl::dispatch returns early or throws an exception (consider removing if there is a - // single return after exception removal (#10878)). - Cleanup cleanup([this]() { dispatching_ = false; }); - for (const Buffer::RawSlice& slice : data.getRawSlices()) { - dispatching_ = true; - ssize_t rc = - nghttp2_session_mem_recv(session_, static_cast(slice.mem_), slice.len_); - if (rc == NGHTTP2_ERR_FLOODED || flood_detected_) { - throw FrameFloodException( - "Flooding was detected in this HTTP/2 session, and it must be closed"); - } - if (rc != static_cast(slice.len_)) { - throw CodecProtocolException(fmt::format("{}", nghttp2_strerror(rc))); - } - - dispatching_ = false; - } - - ENVOY_CONN_LOG(trace, "dispatched {} bytes", connection_, data.length()); - data.drain(data.length()); - - // Decoding incoming frames can generate outbound frames so flush pending. - sendPendingFrames(); - return Http::okStatus(); -} - -ConnectionImpl::StreamImpl* ConnectionImpl::getStream(int32_t stream_id) { - return static_cast(nghttp2_session_get_stream_user_data(session_, stream_id)); -} - -int ConnectionImpl::onData(int32_t stream_id, const uint8_t* data, size_t len) { - StreamImpl* stream = getStream(stream_id); - // If this results in buffering too much data, the watermark buffer will call - // pendingRecvBufferHighWatermark, resulting in ++read_disable_count_ - stream->pending_recv_data_.add(data, len); - // Update the window to the peer unless some consumer of this stream's data has hit a flow control - // limit and disabled reads on this stream - if (!stream->buffersOverrun()) { - nghttp2_session_consume(session_, stream_id, len); - } else { - stream->unconsumed_bytes_ += len; - } - return 0; -} - -void ConnectionImpl::goAway() { - int rc = nghttp2_submit_goaway(session_, NGHTTP2_FLAG_NONE, - nghttp2_session_get_last_proc_stream_id(session_), - NGHTTP2_NO_ERROR, nullptr, 0); - ASSERT(rc == 0); - - sendPendingFrames(); -} - -void ConnectionImpl::shutdownNotice() { - int rc = nghttp2_submit_shutdown_notice(session_); - ASSERT(rc == 0); - - sendPendingFrames(); -} - -int ConnectionImpl::onBeforeFrameReceived(const nghttp2_frame_hd* hd) { - ENVOY_CONN_LOG(trace, "about to recv frame type={}, flags={}", connection_, - static_cast(hd->type), static_cast(hd->flags)); - - // Track all the frames without padding here, since this is the only callback we receive - // for some of them (e.g. CONTINUATION frame, frames sent on closed streams, etc.). - // HEADERS frame is tracked in onBeginHeaders(), DATA frame is tracked in onFrameReceived(). - if (hd->type != NGHTTP2_HEADERS && hd->type != NGHTTP2_DATA) { - if (!trackInboundFrames(hd, 0)) { - return NGHTTP2_ERR_FLOODED; - } - } - - return 0; -} - -ABSL_MUST_USE_RESULT -enum GoAwayErrorCode ngHttp2ErrorCodeToErrorCode(uint32_t code) noexcept { - switch (code) { - case NGHTTP2_NO_ERROR: - return GoAwayErrorCode::NoError; - default: - return GoAwayErrorCode::Other; - } -} - -int ConnectionImpl::onFrameReceived(const nghttp2_frame* frame) { - ENVOY_CONN_LOG(trace, "recv frame type={}", connection_, static_cast(frame->hd.type)); - - // onFrameReceived() is called with a complete HEADERS frame assembled from all the HEADERS - // and CONTINUATION frames, but we track them separately: HEADERS frames in onBeginHeaders() - // and CONTINUATION frames in onBeforeFrameReceived(). - ASSERT(frame->hd.type != NGHTTP2_CONTINUATION); - - if (frame->hd.type == NGHTTP2_DATA) { - if (!trackInboundFrames(&frame->hd, frame->data.padlen)) { - return NGHTTP2_ERR_FLOODED; - } - } - - // Only raise GOAWAY once, since we don't currently expose stream information. Shutdown - // notifications are the same as a normal GOAWAY. - // TODO: handle multiple GOAWAY frames. - if (frame->hd.type == NGHTTP2_GOAWAY && !raised_goaway_) { - ASSERT(frame->hd.stream_id == 0); - raised_goaway_ = true; - callbacks().onGoAway(ngHttp2ErrorCodeToErrorCode(frame->goaway.error_code)); - return 0; - } - - if (frame->hd.type == NGHTTP2_SETTINGS && frame->hd.flags == NGHTTP2_FLAG_NONE) { - onSettingsForTest(frame->settings); - } - - StreamImpl* stream = getStream(frame->hd.stream_id); - if (!stream) { - return 0; - } - - switch (frame->hd.type) { - case NGHTTP2_HEADERS: { - stream->remote_end_stream_ = frame->hd.flags & NGHTTP2_FLAG_END_STREAM; - if (!stream->cookies_.empty()) { - HeaderString key(Headers::get().Cookie); - stream->headers().addViaMove(std::move(key), std::move(stream->cookies_)); - } - - switch (frame->headers.cat) { - case NGHTTP2_HCAT_RESPONSE: - case NGHTTP2_HCAT_REQUEST: { - stream->decodeHeaders(frame->headers.cat == NGHTTP2_HCAT_RESPONSE); - break; - } - - case NGHTTP2_HCAT_HEADERS: { - // It's possible that we are waiting to send a deferred reset, so only raise headers/trailers - // if local is not complete. - if (!stream->deferred_reset_) { - if (!stream->waiting_for_non_informational_headers_) { - if (!stream->remote_end_stream_) { - // This indicates we have received more headers frames than Envoy - // supports. Even if this is valid HTTP (something like 103 early hints) fail here - // rather than trying to push unexpected headers through the Envoy pipeline as that - // will likely result in Envoy crashing. - // It would be cleaner to reset the stream rather than reset the/ entire connection but - // it's also slightly more dangerous so currently we err on the side of safety. - stats_.too_many_header_frames_.inc(); - throw CodecProtocolException("Unexpected 'trailers' with no end stream."); - } else { - stream->decodeTrailers(); - } - } else { - ASSERT(!nghttp2_session_check_server_session(session_)); - stream->waiting_for_non_informational_headers_ = false; - - // Even if we have :status 100 in the client case in a response, when - // we received a 1xx to start out with, nghttp2 message checking - // guarantees proper flow here. - stream->decodeHeaders(false); - } - } - - break; - } - - default: - // We do not currently support push. - NOT_IMPLEMENTED_GCOVR_EXCL_LINE; - } - - break; - } - case NGHTTP2_DATA: { - stream->remote_end_stream_ = frame->hd.flags & NGHTTP2_FLAG_END_STREAM; - - // It's possible that we are waiting to send a deferred reset, so only raise data if local - // is not complete. - if (!stream->deferred_reset_) { - stream->decoder().decodeData(stream->pending_recv_data_, stream->remote_end_stream_); - } - - stream->pending_recv_data_.drain(stream->pending_recv_data_.length()); - break; - } - case NGHTTP2_RST_STREAM: { - ENVOY_CONN_LOG(trace, "remote reset: {}", connection_, frame->rst_stream.error_code); - stats_.rx_reset_.inc(); - break; - } - } - - return 0; -} - -int ConnectionImpl::onFrameSend(const nghttp2_frame* frame) { - // The nghttp2 library does not cleanly give us a way to determine whether we received invalid - // data from our peer. Sometimes it raises the invalid frame callback, and sometimes it does not. - // In all cases however it will attempt to send a GOAWAY frame with an error status. If we see - // an outgoing frame of this type, we will return an error code so that we can abort execution. - ENVOY_CONN_LOG(trace, "sent frame type={}", connection_, static_cast(frame->hd.type)); - switch (frame->hd.type) { - case NGHTTP2_GOAWAY: { - ENVOY_CONN_LOG(debug, "sent goaway code={}", connection_, frame->goaway.error_code); - if (frame->goaway.error_code != NGHTTP2_NO_ERROR) { - // TODO(mattklein123): Returning this error code abandons standard nghttp2 frame accounting. - // As such, it is not reliable to call sendPendingFrames() again after this and we assume - // that the connection is going to get torn down immediately. One byproduct of this is that - // we need to cancel all pending flush stream timeouts since they can race with connection - // teardown. As part of the work to remove exceptions we should aim to clean up all of this - // error handling logic and only handle this type of case at the end of dispatch. - for (auto& stream : active_streams_) { - stream->disarmStreamIdleTimer(); - } - return NGHTTP2_ERR_CALLBACK_FAILURE; - } - break; - } - - case NGHTTP2_RST_STREAM: { - ENVOY_CONN_LOG(debug, "sent reset code={}", connection_, frame->rst_stream.error_code); - stats_.tx_reset_.inc(); - break; - } - - case NGHTTP2_HEADERS: - case NGHTTP2_DATA: { - StreamImpl* stream = getStream(frame->hd.stream_id); - stream->local_end_stream_sent_ = frame->hd.flags & NGHTTP2_FLAG_END_STREAM; - break; - } - } - - return 0; -} - -int ConnectionImpl::onError(absl::string_view error) { - ENVOY_CONN_LOG(debug, "invalid http2: {}", connection_, error); - return 0; -} - -int ConnectionImpl::onInvalidFrame(int32_t stream_id, int error_code) { - ENVOY_CONN_LOG(debug, "invalid frame: {} on stream {}", connection_, nghttp2_strerror(error_code), - stream_id); - - // Set details of error_code in the stream whenever we have one. - StreamImpl* stream = getStream(stream_id); - if (stream != nullptr) { - stream->setDetails(Http2ResponseCodeDetails::get().errorDetails(error_code)); - } - - if (error_code == NGHTTP2_ERR_HTTP_HEADER || error_code == NGHTTP2_ERR_HTTP_MESSAGING) { - stats_.rx_messaging_error_.inc(); - - if (stream_error_on_invalid_http_messaging_) { - // The stream is about to be closed due to an invalid header or messaging. Don't kill the - // entire connection if one stream has bad headers or messaging. - if (stream != nullptr) { - // See comment below in onStreamClose() for why we do this. - stream->reset_due_to_messaging_error_ = true; - } - return 0; - } - } - - // Cause dispatch to return with an error code. - return NGHTTP2_ERR_CALLBACK_FAILURE; -} - -int ConnectionImpl::onBeforeFrameSend(const nghttp2_frame* frame) { - ENVOY_CONN_LOG(trace, "about to send frame type={}, flags={}", connection_, - static_cast(frame->hd.type), static_cast(frame->hd.flags)); - ASSERT(!is_outbound_flood_monitored_control_frame_); - // Flag flood monitored outbound control frames. - is_outbound_flood_monitored_control_frame_ = - ((frame->hd.type == NGHTTP2_PING || frame->hd.type == NGHTTP2_SETTINGS) && - frame->hd.flags & NGHTTP2_FLAG_ACK) || - frame->hd.type == NGHTTP2_RST_STREAM; - return 0; -} - -void ConnectionImpl::incrementOutboundFrameCount(bool is_outbound_flood_monitored_control_frame) { - ++outbound_frames_; - if (is_outbound_flood_monitored_control_frame) { - ++outbound_control_frames_; - } - checkOutboundQueueLimits(); -} - -bool ConnectionImpl::addOutboundFrameFragment(Buffer::OwnedImpl& output, const uint8_t* data, - size_t length) { - // Reset the outbound frame type (set in the onBeforeFrameSend callback) since the - // onBeforeFrameSend callback is not called for DATA frames. - bool is_outbound_flood_monitored_control_frame = false; - std::swap(is_outbound_flood_monitored_control_frame, is_outbound_flood_monitored_control_frame_); - try { - incrementOutboundFrameCount(is_outbound_flood_monitored_control_frame); - } catch (const FrameFloodException&) { - return false; - } - - output.add(data, length); - output.addDrainTracker(is_outbound_flood_monitored_control_frame ? control_frame_buffer_releasor_ - : frame_buffer_releasor_); - return true; -} - -void ConnectionImpl::releaseOutboundFrame() { - ASSERT(outbound_frames_ >= 1); - --outbound_frames_; -} - -void ConnectionImpl::releaseOutboundControlFrame() { - ASSERT(outbound_control_frames_ >= 1); - --outbound_control_frames_; - releaseOutboundFrame(); -} - -ssize_t ConnectionImpl::onSend(const uint8_t* data, size_t length) { - ENVOY_CONN_LOG(trace, "send data: bytes={}", connection_, length); - Buffer::OwnedImpl buffer; - if (!addOutboundFrameFragment(buffer, data, length)) { - ENVOY_CONN_LOG(debug, "error sending frame: Too many frames in the outbound queue.", - connection_); - return NGHTTP2_ERR_FLOODED; - } - - // While the buffer is transient the fragment it contains will be moved into the - // write_buffer_ of the underlying connection_ by the write method below. - // This creates lifetime dependency between the write_buffer_ of the underlying connection - // and the codec object. Specifically the write_buffer_ MUST be either fully drained or - // deleted before the codec object is deleted. This is presently guaranteed by the - // destruction order of the Network::ConnectionImpl object where write_buffer_ is - // destroyed before the filter_manager_ which owns the codec through Http::ConnectionManagerImpl. - connection_.write(buffer, false); - return length; -} - -int ConnectionImpl::onStreamClose(int32_t stream_id, uint32_t error_code) { - StreamImpl* stream = getStream(stream_id); - if (stream) { - ENVOY_CONN_LOG(debug, "stream closed: {}", connection_, error_code); - if (!stream->remote_end_stream_ || !stream->local_end_stream_) { - StreamResetReason reason; - if (stream->reset_due_to_messaging_error_) { - // Unfortunately, the nghttp2 API makes it incredibly difficult to clearly understand - // the flow of resets. I.e., did the reset originate locally? Was it remote? Here, - // we attempt to track cases in which we sent a reset locally due to an invalid frame - // received from the remote. We only do that in two cases currently (HTTP messaging layer - // errors from https://tools.ietf.org/html/rfc7540#section-8 which nghttp2 is very strict - // about). In other cases we treat invalid frames as a protocol error and just kill - // the connection. - reason = StreamResetReason::LocalReset; - } else { - reason = error_code == NGHTTP2_REFUSED_STREAM ? StreamResetReason::RemoteRefusedStreamReset - : StreamResetReason::RemoteReset; - } - - stream->runResetCallbacks(reason); - } - - stream->destroy(); - connection_.dispatcher().deferredDelete(stream->removeFromList(active_streams_)); - // Any unconsumed data must be consumed before the stream is deleted. - // nghttp2 does not appear to track this internally, and any stream deleted - // with outstanding window will contribute to a slow connection-window leak. - nghttp2_session_consume(session_, stream_id, stream->unconsumed_bytes_); - stream->unconsumed_bytes_ = 0; - nghttp2_session_set_stream_user_data(session_, stream->stream_id_, nullptr); - } - - return 0; -} - -int ConnectionImpl::onMetadataReceived(int32_t stream_id, const uint8_t* data, size_t len) { - ENVOY_CONN_LOG(trace, "recv {} bytes METADATA", connection_, len); - - StreamImpl* stream = getStream(stream_id); - if (!stream) { - return 0; - } - - bool success = stream->getMetadataDecoder().receiveMetadata(data, len); - return success ? 0 : NGHTTP2_ERR_CALLBACK_FAILURE; -} - -int ConnectionImpl::onMetadataFrameComplete(int32_t stream_id, bool end_metadata) { - ENVOY_CONN_LOG(trace, "recv METADATA frame on stream {}, end_metadata: {}", connection_, - stream_id, end_metadata); - - StreamImpl* stream = getStream(stream_id); - if (stream == nullptr) { - return 0; - } - - bool result = stream->getMetadataDecoder().onMetadataFrameComplete(end_metadata); - return result ? 0 : NGHTTP2_ERR_CALLBACK_FAILURE; -} - -ssize_t ConnectionImpl::packMetadata(int32_t stream_id, uint8_t* buf, size_t len) { - ENVOY_CONN_LOG(trace, "pack METADATA frame on stream {}", connection_, stream_id); - - StreamImpl* stream = getStream(stream_id); - if (stream == nullptr) { - return 0; - } - - MetadataEncoder& encoder = stream->getMetadataEncoder(); - return encoder.packNextFramePayload(buf, len); -} - -int ConnectionImpl::saveHeader(const nghttp2_frame* frame, HeaderString&& name, - HeaderString&& value) { - StreamImpl* stream = getStream(frame->hd.stream_id); - if (!stream) { - // We have seen 1 or 2 crashes where we get a headers callback but there is no associated - // stream data. I honestly am not sure how this can happen. However, from reading the nghttp2 - // code it looks possible that inflate_header_block() can safely inflate headers for an already - // closed stream, but will still call the headers callback. Since that seems possible, we should - // ignore this case here. - // TODO(mattklein123): Figure out a test case that can hit this. - stats_.headers_cb_no_stream_.inc(); - return 0; - } - - auto should_return = checkHeaderNameForUnderscores(name.getStringView()); - if (should_return) { - name.clear(); - value.clear(); - return should_return.value(); - } - - stream->saveHeader(std::move(name), std::move(value)); - - if (stream->headers().byteSize() > max_headers_kb_ * 1024 || - stream->headers().size() > max_headers_count_) { - // This will cause the library to reset/close the stream. - stats_.header_overflow_.inc(); - return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE; - } else { - return 0; - } -} - -void ConnectionImpl::sendPendingFrames() { - if (dispatching_ || connection_.state() == Network::Connection::State::Closed) { - return; - } - - const int rc = nghttp2_session_send(session_); - if (rc != 0) { - ASSERT(rc == NGHTTP2_ERR_CALLBACK_FAILURE); - // For errors caused by the pending outbound frame flood the FrameFloodException has - // to be thrown. However the nghttp2 library returns only the generic error code for - // all failure types. Check queue limits and throw FrameFloodException if they were - // exceeded. - if (outbound_frames_ > max_outbound_frames_ || - outbound_control_frames_ > max_outbound_control_frames_) { - throw FrameFloodException("Too many frames in the outbound queue."); - } - - throw CodecProtocolException(std::string(nghttp2_strerror(rc))); - } - - // See ConnectionImpl::StreamImpl::resetStream() for why we do this. This is an uncommon event, - // so iterating through every stream to find the ones that have a deferred reset is not a big - // deal. Furthermore, queueing a reset frame does not actually invoke the close stream callback. - // This is only done when the reset frame is sent. Thus, it's safe to work directly with the - // stream map. - // NOTE: The way we handle deferred reset is essentially best effort. If we intend to do a - // deferred reset, we try to finish the stream, including writing any pending data frames. - // If we cannot do this (potentially due to not enough window), we just reset the stream. - // In general this behavior occurs only when we are trying to send immediate error messages - // to short circuit requests. In the best effort case, we complete the stream before - // resetting. In other cases, we just do the reset now which will blow away pending data - // frames and release any memory associated with the stream. - if (pending_deferred_reset_) { - pending_deferred_reset_ = false; - for (auto& stream : active_streams_) { - if (stream->deferred_reset_) { - stream->resetStreamWorker(stream->deferred_reset_.value()); - } - } - sendPendingFrames(); - } -} - -void ConnectionImpl::sendSettings( - const envoy::config::core::v3::Http2ProtocolOptions& http2_options, bool disable_push) { - absl::InlinedVector settings; - auto insertParameter = [&settings](const nghttp2_settings_entry& entry) mutable -> bool { - const auto it = std::find_if(settings.cbegin(), settings.cend(), - [&entry](const nghttp2_settings_entry& existing) { - return entry.settings_id == existing.settings_id; - }); - if (it != settings.end()) { - return false; - } - settings.push_back(entry); - return true; - }; - - // Universally disable receiving push promise frames as we don't currently support - // them. nghttp2 will fail the connection if the other side still sends them. - // TODO(mattklein123): Remove this when we correctly proxy push promise. - // NOTE: This is a special case with respect to custom parameter overrides in that server push is - // not supported and therefore not end user configurable. - if (disable_push) { - settings.push_back( - {static_cast(NGHTTP2_SETTINGS_ENABLE_PUSH), disable_push ? 0U : 1U}); - } - - for (const auto& it : http2_options.custom_settings_parameters()) { - ASSERT(it.identifier().value() <= std::numeric_limits::max()); - const bool result = - insertParameter({static_cast(it.identifier().value()), it.value().value()}); - ASSERT(result); - ENVOY_CONN_LOG(debug, "adding custom settings parameter with id {:#x} to {}", connection_, - it.identifier().value(), it.value().value()); - } - - // Insert named parameters. - settings.insert( - settings.end(), - {{NGHTTP2_SETTINGS_HEADER_TABLE_SIZE, http2_options.hpack_table_size().value()}, - {NGHTTP2_SETTINGS_ENABLE_CONNECT_PROTOCOL, http2_options.allow_connect()}, - {NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, http2_options.max_concurrent_streams().value()}, - {NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE, http2_options.initial_stream_window_size().value()}}); - if (!settings.empty()) { - int rc = nghttp2_submit_settings(session_, NGHTTP2_FLAG_NONE, settings.data(), settings.size()); - ASSERT(rc == 0); - } else { - // nghttp2_submit_settings need to be called at least once - int rc = nghttp2_submit_settings(session_, NGHTTP2_FLAG_NONE, nullptr, 0); - ASSERT(rc == 0); - } - - const uint32_t initial_connection_window_size = - http2_options.initial_connection_window_size().value(); - // Increase connection window size up to our default size. - if (initial_connection_window_size != NGHTTP2_INITIAL_CONNECTION_WINDOW_SIZE) { - ENVOY_CONN_LOG(debug, "updating connection-level initial window size to {}", connection_, - initial_connection_window_size); - int rc = nghttp2_submit_window_update(session_, NGHTTP2_FLAG_NONE, 0, - initial_connection_window_size - - NGHTTP2_INITIAL_CONNECTION_WINDOW_SIZE); - ASSERT(rc == 0); - } -} - -ConnectionImpl::Http2Callbacks::Http2Callbacks() { - nghttp2_session_callbacks_new(&callbacks_); - nghttp2_session_callbacks_set_send_callback( - callbacks_, - [](nghttp2_session*, const uint8_t* data, size_t length, int, void* user_data) -> ssize_t { - return static_cast(user_data)->onSend(data, length); - }); - - nghttp2_session_callbacks_set_send_data_callback( - callbacks_, - [](nghttp2_session*, nghttp2_frame* frame, const uint8_t* framehd, size_t length, - nghttp2_data_source* source, void*) -> int { - ASSERT(frame->data.padlen == 0); - return static_cast(source->ptr)->onDataSourceSend(framehd, length); - }); - - nghttp2_session_callbacks_set_on_begin_headers_callback( - callbacks_, [](nghttp2_session*, const nghttp2_frame* frame, void* user_data) -> int { - return static_cast(user_data)->onBeginHeaders(frame); - }); - - nghttp2_session_callbacks_set_on_header_callback( - callbacks_, - [](nghttp2_session*, const nghttp2_frame* frame, const uint8_t* raw_name, size_t name_length, - const uint8_t* raw_value, size_t value_length, uint8_t, void* user_data) -> int { - // TODO PERF: Can reference count here to avoid copies. - HeaderString name; - name.setCopy(reinterpret_cast(raw_name), name_length); - HeaderString value; - value.setCopy(reinterpret_cast(raw_value), value_length); - return static_cast(user_data)->onHeader(frame, std::move(name), - std::move(value)); - }); - - nghttp2_session_callbacks_set_on_data_chunk_recv_callback( - callbacks_, - [](nghttp2_session*, uint8_t, int32_t stream_id, const uint8_t* data, size_t len, - void* user_data) -> int { - return static_cast(user_data)->onData(stream_id, data, len); - }); - - nghttp2_session_callbacks_set_on_begin_frame_callback( - callbacks_, [](nghttp2_session*, const nghttp2_frame_hd* hd, void* user_data) -> int { - return static_cast(user_data)->onBeforeFrameReceived(hd); - }); - - nghttp2_session_callbacks_set_on_frame_recv_callback( - callbacks_, [](nghttp2_session*, const nghttp2_frame* frame, void* user_data) -> int { - return static_cast(user_data)->onFrameReceived(frame); - }); - - nghttp2_session_callbacks_set_on_stream_close_callback( - callbacks_, - [](nghttp2_session*, int32_t stream_id, uint32_t error_code, void* user_data) -> int { - return static_cast(user_data)->onStreamClose(stream_id, error_code); - }); - - nghttp2_session_callbacks_set_on_frame_send_callback( - callbacks_, [](nghttp2_session*, const nghttp2_frame* frame, void* user_data) -> int { - return static_cast(user_data)->onFrameSend(frame); - }); - - nghttp2_session_callbacks_set_before_frame_send_callback( - callbacks_, [](nghttp2_session*, const nghttp2_frame* frame, void* user_data) -> int { - return static_cast(user_data)->onBeforeFrameSend(frame); - }); - - nghttp2_session_callbacks_set_on_frame_not_send_callback( - callbacks_, [](nghttp2_session*, const nghttp2_frame*, int, void*) -> int { - // We used to always return failure here but it looks now this can get called if the other - // side sends GOAWAY and we are trying to send a SETTINGS ACK. Just ignore this for now. - return 0; - }); - - nghttp2_session_callbacks_set_on_invalid_frame_recv_callback( - callbacks_, - [](nghttp2_session*, const nghttp2_frame* frame, int error_code, void* user_data) -> int { - return static_cast(user_data)->onInvalidFrame(frame->hd.stream_id, - error_code); - }); - - nghttp2_session_callbacks_set_on_extension_chunk_recv_callback( - callbacks_, - [](nghttp2_session*, const nghttp2_frame_hd* hd, const uint8_t* data, size_t len, - void* user_data) -> int { - ASSERT(hd->length >= len); - return static_cast(user_data)->onMetadataReceived(hd->stream_id, data, - len); - }); - - nghttp2_session_callbacks_set_unpack_extension_callback( - callbacks_, [](nghttp2_session*, void**, const nghttp2_frame_hd* hd, void* user_data) -> int { - return static_cast(user_data)->onMetadataFrameComplete( - hd->stream_id, hd->flags == END_METADATA_FLAG); - }); - - nghttp2_session_callbacks_set_pack_extension_callback( - callbacks_, - [](nghttp2_session*, uint8_t* buf, size_t len, const nghttp2_frame* frame, - void* user_data) -> ssize_t { - ASSERT(frame->hd.length <= len); - return static_cast(user_data)->packMetadata(frame->hd.stream_id, buf, len); - }); - - nghttp2_session_callbacks_set_error_callback2( - callbacks_, [](nghttp2_session*, int, const char* msg, size_t len, void* user_data) -> int { - return static_cast(user_data)->onError(absl::string_view(msg, len)); - }); -} - -ConnectionImpl::Http2Callbacks::~Http2Callbacks() { nghttp2_session_callbacks_del(callbacks_); } - -ConnectionImpl::Http2Options::Http2Options( - const envoy::config::core::v3::Http2ProtocolOptions& http2_options) { - nghttp2_option_new(&options_); - // Currently we do not do anything with stream priority. Setting the following option prevents - // nghttp2 from keeping around closed streams for use during stream priority dependency graph - // calculations. This saves a tremendous amount of memory in cases where there are a large - // number of kept alive HTTP/2 connections. - nghttp2_option_set_no_closed_streams(options_, 1); - nghttp2_option_set_no_auto_window_update(options_, 1); - - // The max send header block length is configured to an arbitrarily high number so as to never - // trigger the check within nghttp2, as we check request headers length in - // codec_impl::saveHeader. - nghttp2_option_set_max_send_header_block_length(options_, 0x2000000); - - if (http2_options.hpack_table_size().value() != NGHTTP2_DEFAULT_HEADER_TABLE_SIZE) { - nghttp2_option_set_max_deflate_dynamic_table_size(options_, - http2_options.hpack_table_size().value()); - } - - if (http2_options.allow_metadata()) { - nghttp2_option_set_user_recv_extension_type(options_, METADATA_FRAME_TYPE); - } - - // nghttp2 v1.39.2 lowered the internal flood protection limit from 10K to 1K of ACK frames. - // This new limit may cause the internal nghttp2 mitigation to trigger more often (as it - // requires just 9K of incoming bytes for smallest 9 byte SETTINGS frame), bypassing the same - // mitigation and its associated behavior in the envoy HTTP/2 codec. Since envoy does not rely - // on this mitigation, set back to the old 10K number to avoid any changes in the HTTP/2 codec - // behavior. - nghttp2_option_set_max_outbound_ack(options_, 10000); -} - -ConnectionImpl::Http2Options::~Http2Options() { nghttp2_option_del(options_); } - -ConnectionImpl::ClientHttp2Options::ClientHttp2Options( - const envoy::config::core::v3::Http2ProtocolOptions& http2_options) - : Http2Options(http2_options) { - // Temporarily disable initial max streams limit/protection, since we might want to create - // more than 100 streams before receiving the HTTP/2 SETTINGS frame from the server. - // - // TODO(PiotrSikora): remove this once multiple upstream connections or queuing are implemented. - nghttp2_option_set_peer_max_concurrent_streams( - options_, ::Envoy::Http2::Utility::OptionsLimits::DEFAULT_MAX_CONCURRENT_STREAMS); -} - -ClientConnectionImpl::ClientConnectionImpl( - Network::Connection& connection, Http::ConnectionCallbacks& callbacks, CodecStats& stats, - const envoy::config::core::v3::Http2ProtocolOptions& http2_options, - const uint32_t max_response_headers_kb, const uint32_t max_response_headers_count, - Nghttp2SessionFactory& http2_session_factory) - : ConnectionImpl(connection, stats, http2_options, max_response_headers_kb, - max_response_headers_count), - callbacks_(callbacks) { - ClientHttp2Options client_http2_options(http2_options); - session_ = http2_session_factory.create(http2_callbacks_.callbacks(), base(), - client_http2_options.options()); - http2_session_factory.init(session_, base(), http2_options); - allow_metadata_ = http2_options.allow_metadata(); -} - -RequestEncoder& ClientConnectionImpl::newStream(ResponseDecoder& decoder) { - ClientStreamImplPtr stream(new ClientStreamImpl(*this, per_stream_buffer_limit_, decoder)); - // If the connection is currently above the high watermark, make sure to inform the new stream. - // The connection can not pass this on automatically as it has no awareness that a new stream is - // created. - if (connection_.aboveHighWatermark()) { - stream->runHighWatermarkCallbacks(); - } - ClientStreamImpl& stream_ref = *stream; - stream->moveIntoList(std::move(stream), active_streams_); - return stream_ref; -} - -int ClientConnectionImpl::onBeginHeaders(const nghttp2_frame* frame) { - // The client code explicitly does not currently support push promise. - RELEASE_ASSERT(frame->hd.type == NGHTTP2_HEADERS, ""); - RELEASE_ASSERT(frame->headers.cat == NGHTTP2_HCAT_RESPONSE || - frame->headers.cat == NGHTTP2_HCAT_HEADERS, - ""); - if (frame->headers.cat == NGHTTP2_HCAT_HEADERS) { - StreamImpl* stream = getStream(frame->hd.stream_id); - stream->allocTrailers(); - } - - return 0; -} - -int ClientConnectionImpl::onHeader(const nghttp2_frame* frame, HeaderString&& name, - HeaderString&& value) { - // The client code explicitly does not currently support push promise. - ASSERT(frame->hd.type == NGHTTP2_HEADERS); - ASSERT(frame->headers.cat == NGHTTP2_HCAT_RESPONSE || frame->headers.cat == NGHTTP2_HCAT_HEADERS); - return saveHeader(frame, std::move(name), std::move(value)); -} - -ServerConnectionImpl::ServerConnectionImpl( - Network::Connection& connection, Http::ServerConnectionCallbacks& callbacks, CodecStats& stats, - const envoy::config::core::v3::Http2ProtocolOptions& http2_options, - const uint32_t max_request_headers_kb, const uint32_t max_request_headers_count, - envoy::config::core::v3::HttpProtocolOptions::HeadersWithUnderscoresAction - headers_with_underscores_action) - : ConnectionImpl(connection, stats, http2_options, max_request_headers_kb, - max_request_headers_count), - callbacks_(callbacks), headers_with_underscores_action_(headers_with_underscores_action) { - Http2Options h2_options(http2_options); - - nghttp2_session_server_new2(&session_, http2_callbacks_.callbacks(), base(), - h2_options.options()); - sendSettings(http2_options, false); - allow_metadata_ = http2_options.allow_metadata(); -} - -int ServerConnectionImpl::onBeginHeaders(const nghttp2_frame* frame) { - // For a server connection, we should never get push promise frames. - ASSERT(frame->hd.type == NGHTTP2_HEADERS); - - if (!trackInboundFrames(&frame->hd, frame->headers.padlen)) { - return NGHTTP2_ERR_FLOODED; - } - - if (frame->headers.cat != NGHTTP2_HCAT_REQUEST) { - stats_.trailers_.inc(); - ASSERT(frame->headers.cat == NGHTTP2_HCAT_HEADERS); - - StreamImpl* stream = getStream(frame->hd.stream_id); - stream->allocTrailers(); - return 0; - } - - ServerStreamImplPtr stream(new ServerStreamImpl(*this, per_stream_buffer_limit_)); - if (connection_.aboveHighWatermark()) { - stream->runHighWatermarkCallbacks(); - } - stream->request_decoder_ = &callbacks_.newStream(*stream); - stream->stream_id_ = frame->hd.stream_id; - stream->moveIntoList(std::move(stream), active_streams_); - nghttp2_session_set_stream_user_data(session_, frame->hd.stream_id, - active_streams_.front().get()); - return 0; -} - -int ServerConnectionImpl::onHeader(const nghttp2_frame* frame, HeaderString&& name, - HeaderString&& value) { - // 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); - return saveHeader(frame, std::move(name), std::move(value)); -} - -bool ServerConnectionImpl::trackInboundFrames(const nghttp2_frame_hd* hd, uint32_t padding_length) { - ENVOY_CONN_LOG(trace, "track inbound frame type={} flags={} length={} padding_length={}", - connection_, static_cast(hd->type), static_cast(hd->flags), - static_cast(hd->length), padding_length); - switch (hd->type) { - case NGHTTP2_HEADERS: - case NGHTTP2_CONTINUATION: - // Track new streams. - if (hd->flags & NGHTTP2_FLAG_END_HEADERS) { - inbound_streams_++; - } - FALLTHRU; - case NGHTTP2_DATA: - // Track frames with an empty payload and no end stream flag. - if (hd->length - padding_length == 0 && !(hd->flags & NGHTTP2_FLAG_END_STREAM)) { - ENVOY_CONN_LOG(trace, "frame with an empty payload and no end stream flag.", connection_); - consecutive_inbound_frames_with_empty_payload_++; - } else { - consecutive_inbound_frames_with_empty_payload_ = 0; - } - break; - case NGHTTP2_PRIORITY: - inbound_priority_frames_++; - break; - case NGHTTP2_WINDOW_UPDATE: - inbound_window_update_frames_++; - break; - default: - break; - } - - if (!checkInboundFrameLimits()) { - // NGHTTP2_ERR_FLOODED is overridden within nghttp2 library and it doesn't propagate - // all the way to nghttp2_session_mem_recv() where we need it. - flood_detected_ = true; - return false; - } - - return true; -} - -bool ServerConnectionImpl::checkInboundFrameLimits() { - ASSERT(dispatching_downstream_data_); - - if (consecutive_inbound_frames_with_empty_payload_ > - max_consecutive_inbound_frames_with_empty_payload_) { - ENVOY_CONN_LOG(trace, - "error reading frame: Too many consecutive frames with an empty payload " - "received in this HTTP/2 session.", - connection_); - stats_.inbound_empty_frames_flood_.inc(); - return false; - } - - if (inbound_priority_frames_ > max_inbound_priority_frames_per_stream_ * (1 + inbound_streams_)) { - ENVOY_CONN_LOG(trace, - "error reading frame: Too many PRIORITY frames received in this HTTP/2 session.", - connection_); - stats_.inbound_priority_frames_flood_.inc(); - return false; - } - - if (inbound_window_update_frames_ > - 1 + 2 * (inbound_streams_ + - max_inbound_window_update_frames_per_data_frame_sent_ * outbound_data_frames_)) { - ENVOY_CONN_LOG( - trace, - "error reading frame: Too many WINDOW_UPDATE frames received in this HTTP/2 session.", - connection_); - stats_.inbound_window_update_frames_flood_.inc(); - return false; - } - - return true; -} - -void ServerConnectionImpl::checkOutboundQueueLimits() { - if (outbound_frames_ > max_outbound_frames_ && dispatching_downstream_data_) { - stats_.outbound_flood_.inc(); - throw FrameFloodException("Too many frames in the outbound queue."); - } - if (outbound_control_frames_ > max_outbound_control_frames_ && dispatching_downstream_data_) { - stats_.outbound_control_flood_.inc(); - throw FrameFloodException("Too many control frames in the outbound queue."); - } -} - -Http::Status ServerConnectionImpl::dispatch(Buffer::Instance& data) { - // TODO(#10878): Remove this wrapper when exception removal is complete. innerDispatch may either - // throw an exception or return an error status. The utility wrapper catches exceptions and - // converts them to error statuses. - return Http::Utility::exceptionToStatus( - [&](Buffer::Instance& data) -> Http::Status { return innerDispatch(data); }, data); -} - -Http::Status ServerConnectionImpl::innerDispatch(Buffer::Instance& data) { - ASSERT(!dispatching_downstream_data_); - dispatching_downstream_data_ = true; - - // Make sure the dispatching_downstream_data_ is set to false even - // when ConnectionImpl::dispatch throws an exception. - Cleanup cleanup([this]() { dispatching_downstream_data_ = false; }); - - // Make sure downstream outbound queue was not flooded by the upstream frames. - checkOutboundQueueLimits(); - - return ConnectionImpl::innerDispatch(data); -} - -absl::optional -ServerConnectionImpl::checkHeaderNameForUnderscores(absl::string_view header_name) { - if (headers_with_underscores_action_ != envoy::config::core::v3::HttpProtocolOptions::ALLOW && - Http::HeaderUtility::headerNameContainsUnderscore(header_name)) { - if (headers_with_underscores_action_ == - envoy::config::core::v3::HttpProtocolOptions::DROP_HEADER) { - ENVOY_CONN_LOG(debug, "Dropping header with invalid characters in its name: {}", connection_, - header_name); - stats_.dropped_headers_with_underscores_.inc(); - return 0; - } - ENVOY_CONN_LOG(debug, "Rejecting request due to header name with underscores: {}", connection_, - header_name); - stats_.requests_rejected_with_underscores_in_headers_.inc(); - return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE; - } - return absl::nullopt; -} - -} // namespace Http2 -} // namespace Legacy -} // namespace Http -} // namespace Envoy diff --git a/source/common/http/http2/codec_impl_legacy.h b/source/common/http/http2/codec_impl_legacy.h deleted file mode 100644 index ebb40b18d8a7..000000000000 --- a/source/common/http/http2/codec_impl_legacy.h +++ /dev/null @@ -1,602 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include -#include - -#include "envoy/config/core/v3/protocol.pb.h" -#include "envoy/event/deferred_deletable.h" -#include "envoy/http/codec.h" -#include "envoy/network/connection.h" - -#include "common/buffer/buffer_impl.h" -#include "common/buffer/watermark_buffer.h" -#include "common/common/linked_object.h" -#include "common/common/logger.h" -#include "common/common/thread.h" -#include "common/http/codec_helper.h" -#include "common/http/header_map_impl.h" -#include "common/http/http2/codec_stats.h" -#include "common/http/http2/metadata_decoder.h" -#include "common/http/http2/metadata_encoder.h" -#include "common/http/status.h" -#include "common/http/utility.h" - -#include "absl/types/optional.h" -#include "nghttp2/nghttp2.h" - -namespace Envoy { -namespace Http { -namespace Legacy { -namespace Http2 { - -// This is not the full client magic, but it's the smallest size that should be able to -// differentiate between HTTP/1 and HTTP/2. -const std::string CLIENT_MAGIC_PREFIX = "PRI * HTTP/2"; - -class Utility { -public: - /** - * Deal with https://tools.ietf.org/html/rfc7540#section-8.1.2.5 - * @param key supplies the incoming header key. - * @param value supplies the incoming header value. - * @param cookies supplies the header string to fill if this is a cookie header that needs to be - * rebuilt. - */ - static bool reconstituteCrumbledCookies(const HeaderString& key, const HeaderString& value, - HeaderString& cookies); -}; - -class ConnectionImpl; - -// Abstract nghttp2_session factory. Used to enable injection of factories for testing. -class Nghttp2SessionFactory { -public: - using ConnectionImplType = ConnectionImpl; - virtual ~Nghttp2SessionFactory() = default; - - // Returns a new nghttp2_session to be used with |connection|. - virtual nghttp2_session* create(const nghttp2_session_callbacks* callbacks, - ConnectionImplType* connection, - const nghttp2_option* options) PURE; - - // Initializes the |session|. - virtual void init(nghttp2_session* session, ConnectionImplType* connection, - const envoy::config::core::v3::Http2ProtocolOptions& options) PURE; -}; - -class ProdNghttp2SessionFactory : public Nghttp2SessionFactory { -public: - nghttp2_session* create(const nghttp2_session_callbacks* callbacks, ConnectionImpl* connection, - const nghttp2_option* options) override; - - void init(nghttp2_session* session, ConnectionImpl* connection, - const envoy::config::core::v3::Http2ProtocolOptions& options) override; - - // Returns a global factory instance. Note that this is possible because no internal state is - // maintained; the thread safety of create() and init()'s side effects is guaranteed by Envoy's - // worker based threading model. - static ProdNghttp2SessionFactory& get() { - static ProdNghttp2SessionFactory* instance = new ProdNghttp2SessionFactory(); - return *instance; - } -}; - -/** - * Base class for HTTP/2 client and server codecs. - */ -class ConnectionImpl : public virtual Connection, protected Logger::Loggable { -public: - ConnectionImpl(Network::Connection& connection, Http::Http2::CodecStats& stats, - const envoy::config::core::v3::Http2ProtocolOptions& http2_options, - const uint32_t max_headers_kb, const uint32_t max_headers_count); - - ~ConnectionImpl() override; - - // Http::Connection - // NOTE: the `dispatch` method is also overridden in the ServerConnectionImpl class - Http::Status dispatch(Buffer::Instance& data) override; - void goAway() override; - Protocol protocol() override { return Protocol::Http2; } - void shutdownNotice() override; - bool wantsToWrite() override { return nghttp2_session_want_write(session_); } - // Propagate network connection watermark events to each stream on the connection. - void onUnderlyingConnectionAboveWriteBufferHighWatermark() override { - for (auto& stream : active_streams_) { - stream->runHighWatermarkCallbacks(); - } - } - void onUnderlyingConnectionBelowWriteBufferLowWatermark() override { - for (auto& stream : active_streams_) { - stream->runLowWatermarkCallbacks(); - } - } - - /** - * An inner dispatch call that executes the dispatching logic. While exception removal is in - * migration (#10878), this function may either throw an exception or return an error status. - * Exceptions are caught and translated to their corresponding statuses in the outer level - * dispatch. - * This needs to be virtual so that ServerConnectionImpl can override. - * TODO(#10878): Remove this when exception removal is complete. - */ - virtual Http::Status innerDispatch(Buffer::Instance& data); - -protected: - friend class ProdNghttp2SessionFactory; - - /** - * Wrapper for static nghttp2 callback dispatchers. - */ - class Http2Callbacks { - public: - Http2Callbacks(); - ~Http2Callbacks(); - - const nghttp2_session_callbacks* callbacks() { return callbacks_; } - - private: - nghttp2_session_callbacks* callbacks_; - }; - - /** - * Wrapper for static nghttp2 session options. - */ - class Http2Options { - public: - Http2Options(const envoy::config::core::v3::Http2ProtocolOptions& http2_options); - ~Http2Options(); - - const nghttp2_option* options() { return options_; } - - protected: - nghttp2_option* options_; - }; - - class ClientHttp2Options : public Http2Options { - public: - ClientHttp2Options(const envoy::config::core::v3::Http2ProtocolOptions& http2_options); - }; - - /** - * Base class for client and server side streams. - */ - struct StreamImpl : public virtual StreamEncoder, - public Stream, - public LinkedObject, - public Event::DeferredDeletable, - public StreamCallbackHelper { - - StreamImpl(ConnectionImpl& parent, uint32_t buffer_limit); - ~StreamImpl() override; - // TODO(mattklein123): Optimally this would be done in the destructor but there are currently - // deferred delete lifetime issues that need sorting out if the destructor of the stream is - // going to be able to refer to the parent connection. - void destroy(); - void disarmStreamIdleTimer() { - if (stream_idle_timer_ != nullptr) { - // To ease testing and the destructor assertion. - stream_idle_timer_->disableTimer(); - stream_idle_timer_.reset(); - } - } - - StreamImpl* base() { return this; } - ssize_t onDataSourceRead(uint64_t length, uint32_t* data_flags); - int onDataSourceSend(const uint8_t* framehd, size_t length); - void resetStreamWorker(StreamResetReason reason); - static void buildHeaders(std::vector& final_headers, const HeaderMap& headers); - void saveHeader(HeaderString&& name, HeaderString&& value); - void encodeHeadersBase(const std::vector& final_headers, bool end_stream); - virtual void submitHeaders(const std::vector& final_headers, - nghttp2_data_provider* provider) PURE; - void encodeTrailersBase(const HeaderMap& headers); - void submitTrailers(const HeaderMap& trailers); - void submitMetadata(uint8_t flags); - virtual StreamDecoder& decoder() PURE; - virtual HeaderMap& headers() PURE; - virtual void allocTrailers() PURE; - virtual HeaderMapPtr cloneTrailers(const HeaderMap& trailers) PURE; - virtual void createPendingFlushTimer() PURE; - void onPendingFlushTimer(); - - // Http::StreamEncoder - void encodeData(Buffer::Instance& data, bool end_stream) override; - Stream& getStream() override { return *this; } - void encodeMetadata(const MetadataMapVector& metadata_map_vector) override; - Http1StreamEncoderOptionsOptRef http1StreamEncoderOptions() override { return absl::nullopt; } - - // Http::Stream - void addCallbacks(StreamCallbacks& callbacks) override { addCallbacksHelper(callbacks); } - void removeCallbacks(StreamCallbacks& callbacks) override { removeCallbacksHelper(callbacks); } - void resetStream(StreamResetReason reason) override; - void readDisable(bool disable) override; - uint32_t bufferLimit() override { return pending_recv_data_.highWatermark(); } - const Network::Address::InstanceConstSharedPtr& connectionLocalAddress() override { - return parent_.connection_.localAddress(); - } - absl::string_view responseDetails() override { return details_; } - void setFlushTimeout(std::chrono::milliseconds timeout) override { - stream_idle_timeout_ = timeout; - } - - // This code assumes that details is a static string, so that we - // can avoid copying it. - void setDetails(absl::string_view details) { - // It is probably a mistake to call setDetails() twice, so - // assert that details_ is empty. - ASSERT(details_.empty()); - - details_ = details; - } - - void setWriteBufferWatermarks(uint32_t low_watermark, uint32_t high_watermark) { - pending_recv_data_.setWatermarks(low_watermark, high_watermark); - pending_send_data_.setWatermarks(low_watermark, high_watermark); - } - - // If the receive buffer encounters watermark callbacks, enable/disable reads on this stream. - void pendingRecvBufferHighWatermark(); - void pendingRecvBufferLowWatermark(); - - // If the send buffer encounters watermark callbacks, propagate this information to the streams. - // The router and connection manager will propagate them on as appropriate. - void pendingSendBufferHighWatermark(); - void pendingSendBufferLowWatermark(); - - // Does any necessary WebSocket/Upgrade conversion, then passes the headers - // to the decoder_. - virtual void decodeHeaders(bool allow_waiting_for_informational_headers) PURE; - virtual void decodeTrailers() PURE; - - // Get MetadataEncoder for this stream. - Http::Http2::MetadataEncoder& getMetadataEncoder(); - // Get MetadataDecoder for this stream. - Http::Http2::MetadataDecoder& getMetadataDecoder(); - // Callback function for MetadataDecoder. - void onMetadataDecoded(MetadataMapPtr&& metadata_map_ptr); - - bool buffersOverrun() const { return read_disable_count_ > 0; } - - ConnectionImpl& parent_; - int32_t stream_id_{-1}; - uint32_t unconsumed_bytes_{0}; - uint32_t read_disable_count_{0}; - Buffer::WatermarkBuffer pending_recv_data_{ - [this]() -> void { this->pendingRecvBufferLowWatermark(); }, - [this]() -> void { this->pendingRecvBufferHighWatermark(); }, - []() -> void { /* TODO(adisuissa): Handle overflow watermark */ }}; - Buffer::WatermarkBuffer pending_send_data_{ - [this]() -> void { this->pendingSendBufferLowWatermark(); }, - [this]() -> void { this->pendingSendBufferHighWatermark(); }, - []() -> void { /* TODO(adisuissa): Handle overflow watermark */ }}; - HeaderMapPtr pending_trailers_to_encode_; - std::unique_ptr metadata_decoder_; - std::unique_ptr metadata_encoder_; - absl::optional deferred_reset_; - HeaderString cookies_; - bool local_end_stream_sent_ : 1; - bool remote_end_stream_ : 1; - bool data_deferred_ : 1; - bool waiting_for_non_informational_headers_ : 1; - bool pending_receive_buffer_high_watermark_called_ : 1; - bool pending_send_buffer_high_watermark_called_ : 1; - bool reset_due_to_messaging_error_ : 1; - absl::string_view details_; - // See HttpConnectionManager.stream_idle_timeout. - std::chrono::milliseconds stream_idle_timeout_{}; - Event::TimerPtr stream_idle_timer_; - }; - - using StreamImplPtr = std::unique_ptr; - - /** - * Client side stream (request). - */ - struct ClientStreamImpl : public StreamImpl, public RequestEncoder { - ClientStreamImpl(ConnectionImpl& parent, uint32_t buffer_limit, - ResponseDecoder& response_decoder) - : StreamImpl(parent, buffer_limit), response_decoder_(response_decoder), - headers_or_trailers_(ResponseHeaderMapImpl::create()) {} - - // StreamImpl - void submitHeaders(const std::vector& final_headers, - nghttp2_data_provider* provider) override; - StreamDecoder& decoder() override { return response_decoder_; } - void decodeHeaders(bool allow_waiting_for_informational_headers) override; - void decodeTrailers() override; - HeaderMap& headers() override { - if (absl::holds_alternative(headers_or_trailers_)) { - return *absl::get(headers_or_trailers_); - } else { - return *absl::get(headers_or_trailers_); - } - } - void allocTrailers() override { - // If we are waiting for informational headers, make a new response header map, otherwise - // we are about to receive trailers. The codec makes sure this is the only valid sequence. - if (waiting_for_non_informational_headers_) { - headers_or_trailers_.emplace(ResponseHeaderMapImpl::create()); - } else { - headers_or_trailers_.emplace(ResponseTrailerMapImpl::create()); - } - } - HeaderMapPtr cloneTrailers(const HeaderMap& trailers) override { - return createHeaderMap(trailers); - } - void createPendingFlushTimer() override { - // Client streams do not create a flush timer because we currently assume that any failure - // to flush would be covered by a request/stream/etc. timeout. - } - - // RequestEncoder - void encodeHeaders(const RequestHeaderMap& headers, bool end_stream) override; - void encodeTrailers(const RequestTrailerMap& trailers) override { - encodeTrailersBase(trailers); - } - - ResponseDecoder& response_decoder_; - absl::variant headers_or_trailers_; - std::string upgrade_type_; - }; - - using ClientStreamImplPtr = std::unique_ptr; - - /** - * Server side stream (response). - */ - struct ServerStreamImpl : public StreamImpl, public ResponseEncoder { - ServerStreamImpl(ConnectionImpl& parent, uint32_t buffer_limit) - : StreamImpl(parent, buffer_limit), headers_or_trailers_(RequestHeaderMapImpl::create()) {} - - // StreamImpl - void submitHeaders(const std::vector& final_headers, - nghttp2_data_provider* provider) override; - StreamDecoder& decoder() override { return *request_decoder_; } - void decodeHeaders(bool allow_waiting_for_informational_headers) override; - void decodeTrailers() override; - HeaderMap& headers() override { - if (absl::holds_alternative(headers_or_trailers_)) { - return *absl::get(headers_or_trailers_); - } else { - return *absl::get(headers_or_trailers_); - } - } - void allocTrailers() override { - headers_or_trailers_.emplace(RequestTrailerMapImpl::create()); - } - HeaderMapPtr cloneTrailers(const HeaderMap& trailers) override { - return createHeaderMap(trailers); - } - void createPendingFlushTimer() override; - - // ResponseEncoder - void encode100ContinueHeaders(const ResponseHeaderMap& headers) override; - void encodeHeaders(const ResponseHeaderMap& headers, bool end_stream) override; - void encodeTrailers(const ResponseTrailerMap& trailers) override { - encodeTrailersBase(trailers); - } - - RequestDecoder* request_decoder_{}; - absl::variant headers_or_trailers_; - }; - - using ServerStreamImplPtr = std::unique_ptr; - - ConnectionImpl* base() { return this; } - // NOTE: Always use non debug nullptr checks against the return value of this function. There are - // edge cases (such as for METADATA frames) where nghttp2 will issue a callback for a stream_id - // that is not associated with an existing stream. - StreamImpl* getStream(int32_t stream_id); - int saveHeader(const nghttp2_frame* frame, HeaderString&& name, HeaderString&& value); - void sendPendingFrames(); - void sendSettings(const envoy::config::core::v3::Http2ProtocolOptions& http2_options, - bool disable_push); - // Callback triggered when the peer's SETTINGS frame is received. - // NOTE: This is only used for tests. - virtual void onSettingsForTest(const nghttp2_settings&) {} - - /** - * Check if header name contains underscore character. - * Underscore character is allowed in header names by the RFC-7230 and this check is implemented - * as a security measure due to systems that treat '_' and '-' as interchangeable. - * The ServerConnectionImpl may drop header or reject request based on the - * `common_http_protocol_options.headers_with_underscores_action` configuration option in the - * HttpConnectionManager. - */ - virtual absl::optional checkHeaderNameForUnderscores(absl::string_view /* header_name */) { - return absl::nullopt; - } - - static Http2Callbacks http2_callbacks_; - - std::list active_streams_; - nghttp2_session* session_{}; - Http::Http2::CodecStats& stats_; - Network::Connection& connection_; - const uint32_t max_headers_kb_; - const uint32_t max_headers_count_; - uint32_t per_stream_buffer_limit_; - bool allow_metadata_; - const bool stream_error_on_invalid_http_messaging_; - bool flood_detected_; - - // Set if the type of frame that is about to be sent is PING or SETTINGS with the ACK flag set, or - // RST_STREAM. - bool is_outbound_flood_monitored_control_frame_ = 0; - // This counter keeps track of the number of outbound frames of all types (these that were - // buffered in the underlying connection but not yet written into the socket). If this counter - // exceeds the `max_outbound_frames_' value the connection is terminated. - uint32_t outbound_frames_ = 0; - // Maximum number of outbound frames. Initialized from corresponding http2_protocol_options. - // Default value is 10000. - const uint32_t max_outbound_frames_; - const std::function frame_buffer_releasor_; - // This counter keeps track of the number of outbound frames of types PING, SETTINGS and - // RST_STREAM (these that were buffered in the underlying connection but not yet written into the - // socket). If this counter exceeds the `max_outbound_control_frames_' value the connection is - // terminated. - uint32_t outbound_control_frames_ = 0; - // Maximum number of outbound frames of types PING, SETTINGS and RST_STREAM. Initialized from - // corresponding http2_protocol_options. Default value is 1000. - const uint32_t max_outbound_control_frames_; - const std::function control_frame_buffer_releasor_; - // This counter keeps track of the number of consecutive inbound frames of types HEADERS, - // CONTINUATION and DATA with an empty payload and no end stream flag. If this counter exceeds - // the `max_consecutive_inbound_frames_with_empty_payload_` value the connection is terminated. - uint32_t consecutive_inbound_frames_with_empty_payload_ = 0; - // Maximum number of consecutive inbound frames of types HEADERS, CONTINUATION and DATA without - // a payload. Initialized from corresponding http2_protocol_options. Default value is 1. - const uint32_t max_consecutive_inbound_frames_with_empty_payload_; - - // This counter keeps track of the number of inbound streams. - uint32_t inbound_streams_ = 0; - // This counter keeps track of the number of inbound PRIORITY frames. If this counter exceeds - // the value calculated using this formula: - // - // max_inbound_priority_frames_per_stream_ * (1 + inbound_streams_) - // - // the connection is terminated. - uint64_t inbound_priority_frames_ = 0; - // Maximum number of inbound PRIORITY frames per stream. Initialized from corresponding - // http2_protocol_options. Default value is 100. - const uint32_t max_inbound_priority_frames_per_stream_; - - // This counter keeps track of the number of inbound WINDOW_UPDATE frames. If this counter 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. - uint64_t inbound_window_update_frames_ = 0; - // This counter keeps track of the number of outbound DATA frames. - uint64_t outbound_data_frames_ = 0; - // Maximum number of inbound WINDOW_UPDATE frames per outbound DATA frame sent. Initialized - // from corresponding http2_protocol_options. Default value is 10. - const uint32_t max_inbound_window_update_frames_per_data_frame_sent_; - - // For the flood mitigation to work the onSend callback must be called once for each outbound - // frame. This is what the nghttp2 library is doing, however this is not documented. The - // Http2FloodMitigationTest.* tests in test/integration/http2_integration_test.cc will break if - // this changes in the future. Also it is important that onSend does not do partial writes, as the - // nghttp2 library will keep calling this callback to write the rest of the frame. - ssize_t onSend(const uint8_t* data, size_t length); - -private: - virtual ConnectionCallbacks& callbacks() PURE; - virtual int onBeginHeaders(const nghttp2_frame* frame) PURE; - int onData(int32_t stream_id, const uint8_t* data, size_t len); - int onBeforeFrameReceived(const nghttp2_frame_hd* hd); - int onFrameReceived(const nghttp2_frame* frame); - int onBeforeFrameSend(const nghttp2_frame* frame); - int onFrameSend(const nghttp2_frame* frame); - int onError(absl::string_view error); - virtual int onHeader(const nghttp2_frame* frame, HeaderString&& name, HeaderString&& value) PURE; - int onInvalidFrame(int32_t stream_id, int error_code); - int onStreamClose(int32_t stream_id, uint32_t error_code); - int onMetadataReceived(int32_t stream_id, const uint8_t* data, size_t len); - int onMetadataFrameComplete(int32_t stream_id, bool end_metadata); - ssize_t packMetadata(int32_t stream_id, uint8_t* buf, size_t len); - // Adds buffer fragment for a new outbound frame to the supplied Buffer::OwnedImpl. - // Returns true on success or false if outbound queue limits were exceeded. - bool addOutboundFrameFragment(Buffer::OwnedImpl& output, const uint8_t* data, size_t length); - virtual void checkOutboundQueueLimits() PURE; - void incrementOutboundFrameCount(bool is_outbound_flood_monitored_control_frame); - virtual bool trackInboundFrames(const nghttp2_frame_hd* hd, uint32_t padding_length) PURE; - virtual bool checkInboundFrameLimits() PURE; - void releaseOutboundFrame(); - void releaseOutboundControlFrame(); - - bool dispatching_ : 1; - bool raised_goaway_ : 1; - bool pending_deferred_reset_ : 1; -}; - -/** - * HTTP/2 client connection codec. - */ -class ClientConnectionImpl : public ClientConnection, public ConnectionImpl { -public: - using SessionFactory = Nghttp2SessionFactory; - ClientConnectionImpl(Network::Connection& connection, ConnectionCallbacks& callbacks, - Http::Http2::CodecStats& stats, - const envoy::config::core::v3::Http2ProtocolOptions& http2_options, - const uint32_t max_response_headers_kb, - const uint32_t max_response_headers_count, - SessionFactory& http2_session_factory); - - // Http::ClientConnection - RequestEncoder& newStream(ResponseDecoder& response_decoder) override; - -private: - // ConnectionImpl - ConnectionCallbacks& callbacks() override { return callbacks_; } - int onBeginHeaders(const nghttp2_frame* frame) override; - int onHeader(const nghttp2_frame* frame, HeaderString&& name, HeaderString&& value) override; - - // Presently client connections only perform accounting of outbound frames and do not - // terminate connections when queue limits are exceeded. The primary reason is the complexity of - // the clean-up of upstream connections. The clean-up of upstream connection causes RST_STREAM - // messages to be sent on corresponding downstream connections. This may actually trigger flood - // mitigation on the downstream connections, which causes an exception to be thrown in the middle - // of the clean-up loop, leaving resources in a half cleaned up state. - // TODO(yanavlasov): add flood mitigation for upstream connections as well. - void checkOutboundQueueLimits() override {} - bool trackInboundFrames(const nghttp2_frame_hd*, uint32_t) override { return true; } - bool checkInboundFrameLimits() override { return true; } - - Http::ConnectionCallbacks& callbacks_; -}; - -/** - * HTTP/2 server connection codec. - */ -class ServerConnectionImpl : public ServerConnection, public ConnectionImpl { -public: - ServerConnectionImpl(Network::Connection& connection, ServerConnectionCallbacks& callbacks, - Http::Http2::CodecStats& stats, - const envoy::config::core::v3::Http2ProtocolOptions& http2_options, - const uint32_t max_request_headers_kb, - const uint32_t max_request_headers_count, - envoy::config::core::v3::HttpProtocolOptions::HeadersWithUnderscoresAction - headers_with_underscores_action); - -private: - // ConnectionImpl - ConnectionCallbacks& callbacks() override { return callbacks_; } - int onBeginHeaders(const nghttp2_frame* frame) override; - int onHeader(const nghttp2_frame* frame, HeaderString&& name, HeaderString&& value) override; - void checkOutboundQueueLimits() override; - bool trackInboundFrames(const nghttp2_frame_hd* hd, uint32_t padding_length) override; - bool checkInboundFrameLimits() override; - absl::optional checkHeaderNameForUnderscores(absl::string_view header_name) override; - - // Http::Connection - // The reason for overriding the dispatch method is to do flood mitigation only when - // processing data from downstream client. Doing flood mitigation when processing upstream - // responses makes clean-up tricky, which needs to be improved (see comments for the - // ClientConnectionImpl::checkOutboundQueueLimits method). The dispatch method on the - // ServerConnectionImpl objects is called only when processing data from the downstream client in - // the ConnectionManagerImpl::onData method. - Http::Status dispatch(Buffer::Instance& data) override; - Http::Status innerDispatch(Buffer::Instance& data) override; - - ServerConnectionCallbacks& callbacks_; - - // This flag indicates that downstream data is being dispatched and turns on flood mitigation - // in the checkMaxOutbound*Framed methods. - bool dispatching_downstream_data_{false}; - - // The action to take when a request header name contains underscore characters. - envoy::config::core::v3::HttpProtocolOptions::HeadersWithUnderscoresAction - headers_with_underscores_action_; -}; - -} // namespace Http2 -} // namespace Legacy -} // namespace Http -} // namespace Envoy diff --git a/source/common/runtime/runtime_features.cc b/source/common/runtime/runtime_features.cc index 35b990cd39b7..ba4817e19a53 100644 --- a/source/common/runtime/runtime_features.cc +++ b/source/common/runtime/runtime_features.cc @@ -70,7 +70,6 @@ constexpr const char* runtime_features[] = { "envoy.reloadable_features.fixed_connection_close", "envoy.reloadable_features.http_default_alpn", "envoy.reloadable_features.listener_in_place_filterchain_update", - "envoy.reloadable_features.new_codec_behavior", "envoy.reloadable_features.preserve_query_string_in_path_redirects", "envoy.reloadable_features.preserve_upstream_date", "envoy.reloadable_features.stop_faking_paths", diff --git a/source/extensions/filters/network/http_connection_manager/BUILD b/source/extensions/filters/network/http_connection_manager/BUILD index 7ab7817d80fd..ec576a8abac9 100644 --- a/source/extensions/filters/network/http_connection_manager/BUILD +++ b/source/extensions/filters/network/http_connection_manager/BUILD @@ -35,9 +35,7 @@ envoy_cc_extension( "//source/common/http:default_server_string_lib", "//source/common/http:request_id_extension_lib", "//source/common/http:utility_lib", - "//source/common/http/http1:codec_legacy_lib", "//source/common/http/http1:codec_lib", - "//source/common/http/http2:codec_legacy_lib", "//source/common/http/http2:codec_lib", "//source/common/json:json_loader_lib", "//source/common/local_reply:local_reply_lib", diff --git a/source/extensions/filters/network/http_connection_manager/config.cc b/source/extensions/filters/network/http_connection_manager/config.cc index e1d374526c58..190db7f475b4 100644 --- a/source/extensions/filters/network/http_connection_manager/config.cc +++ b/source/extensions/filters/network/http_connection_manager/config.cc @@ -20,9 +20,7 @@ #include "common/http/conn_manager_utility.h" #include "common/http/default_server_string.h" #include "common/http/http1/codec_impl.h" -#include "common/http/http1/codec_impl_legacy.h" #include "common/http/http2/codec_impl.h" -#include "common/http/http2/codec_impl_legacy.h" #include "common/http/http3/quic_codec_factory.h" #include "common/http/http3/well_known_names.h" #include "common/http/request_id_extension_impl.h" @@ -482,33 +480,16 @@ HttpConnectionManagerConfig::createCodec(Network::Connection& connection, const Buffer::Instance& data, Http::ServerConnectionCallbacks& callbacks) { switch (codec_type_) { - case CodecType::HTTP1: { - if (context_.runtime().snapshot().runtimeFeatureEnabled( - "envoy.reloadable_features.new_codec_behavior")) { - return std::make_unique( - connection, Http::Http1::CodecStats::atomicGet(http1_codec_stats_, context_.scope()), - callbacks, http1_settings_, maxRequestHeadersKb(), maxRequestHeadersCount(), - headersWithUnderscoresAction()); - } else { - return std::make_unique( - connection, Http::Http1::CodecStats::atomicGet(http1_codec_stats_, context_.scope()), - callbacks, http1_settings_, maxRequestHeadersKb(), maxRequestHeadersCount(), - headersWithUnderscoresAction()); - } - } + case CodecType::HTTP1: + return std::make_unique( + connection, Http::Http1::CodecStats::atomicGet(http1_codec_stats_, context_.scope()), + callbacks, http1_settings_, maxRequestHeadersKb(), maxRequestHeadersCount(), + headersWithUnderscoresAction()); case CodecType::HTTP2: { - if (context_.runtime().snapshot().runtimeFeatureEnabled( - "envoy.reloadable_features.new_codec_behavior")) { - return std::make_unique( - connection, callbacks, - Http::Http2::CodecStats::atomicGet(http2_codec_stats_, context_.scope()), http2_options_, - maxRequestHeadersKb(), maxRequestHeadersCount(), headersWithUnderscoresAction()); - } else { - return std::make_unique( - connection, callbacks, - Http::Http2::CodecStats::atomicGet(http2_codec_stats_, context_.scope()), http2_options_, - maxRequestHeadersKb(), maxRequestHeadersCount(), headersWithUnderscoresAction()); - } + return std::make_unique( + connection, callbacks, + Http::Http2::CodecStats::atomicGet(http2_codec_stats_, context_.scope()), http2_options_, + maxRequestHeadersKb(), maxRequestHeadersCount(), headersWithUnderscoresAction()); } case CodecType::HTTP3: // Hard code Quiche factory name here to instantiate a QUIC codec implemented. diff --git a/test/common/http/codec_impl_fuzz_test.cc b/test/common/http/codec_impl_fuzz_test.cc index 492d2889aa8f..16e4eee2c960 100644 --- a/test/common/http/codec_impl_fuzz_test.cc +++ b/test/common/http/codec_impl_fuzz_test.cc @@ -462,34 +462,32 @@ void codecFuzz(const test::common::http::CodecImplFuzzTestCase& input, HttpVersi const envoy::config::core::v3::HttpProtocolOptions::HeadersWithUnderscoresAction headers_with_underscores_action = envoy::config::core::v3::HttpProtocolOptions::ALLOW; - Http1::CodecStats::AtomicPtr http1_stats; - Http2::CodecStats::AtomicPtr http2_stats; ClientConnectionPtr client; ServerConnectionPtr server; const bool http2 = http_version == HttpVersion::Http2; + Http1::CodecStats::AtomicPtr stats; if (http2) { - client = std::make_unique( - client_connection, client_callbacks, Http2::CodecStats::atomicGet(http2_stats, stats_store), - client_http2_options, max_request_headers_kb, max_response_headers_count, + client = std::make_unique( + client_connection, client_callbacks, stats_store, client_http2_options, + max_request_headers_kb, max_response_headers_count, Http2::ProdNghttp2SessionFactory::get()); } else { client = std::make_unique( - client_connection, Http1::CodecStats::atomicGet(http1_stats, stats_store), client_callbacks, + client_connection, Http1::CodecStats::atomicGet(stats, stats_store), client_callbacks, client_http1settings, max_response_headers_count); } if (http2) { const envoy::config::core::v3::Http2ProtocolOptions server_http2_options{ fromHttp2Settings(input.h2_settings().server())}; - server = std::make_unique( - server_connection, server_callbacks, Http2::CodecStats::atomicGet(http2_stats, stats_store), - server_http2_options, max_request_headers_kb, max_request_headers_count, - headers_with_underscores_action); + server = std::make_unique( + server_connection, server_callbacks, stats_store, server_http2_options, + max_request_headers_kb, max_request_headers_count, headers_with_underscores_action); } else { const Http1Settings server_http1settings{fromHttp1Settings(input.h1_settings().server())}; server = std::make_unique( - server_connection, Http1::CodecStats::atomicGet(http1_stats, stats_store), server_callbacks, + server_connection, Http1::CodecStats::atomicGet(stats, stats_store), server_callbacks, server_http1settings, max_request_headers_kb, max_request_headers_count, headers_with_underscores_action); } @@ -645,8 +643,8 @@ void codecFuzz(const test::common::http::CodecImplFuzzTestCase& input, HttpVersi } } if (!codec_error && http2) { - dynamic_cast(*client).goAway(); - dynamic_cast(*server).goAway(); + dynamic_cast(*client).goAway(); + dynamic_cast(*server).goAway(); } } diff --git a/test/common/http/http1/BUILD b/test/common/http/http1/BUILD index dbcdcd4d4c8b..715e6dbf0c23 100644 --- a/test/common/http/http1/BUILD +++ b/test/common/http/http1/BUILD @@ -26,7 +26,6 @@ envoy_cc_test( "//source/common/event:dispatcher_lib", "//source/common/http:exception_lib", "//source/common/http:header_map_lib", - "//source/common/http/http1:codec_legacy_lib", "//source/common/http/http1:codec_lib", "//test/common/stats:stat_test_utility_lib", "//test/mocks/buffer:buffer_mocks", diff --git a/test/common/http/http1/codec_impl_test.cc b/test/common/http/http1/codec_impl_test.cc index 77565550dc26..fade286cfb44 100644 --- a/test/common/http/http1/codec_impl_test.cc +++ b/test/common/http/http1/codec_impl_test.cc @@ -9,7 +9,6 @@ #include "common/http/exception.h" #include "common/http/header_map_impl.h" #include "common/http/http1/codec_impl.h" -#include "common/http/http1/codec_impl_legacy.h" #include "common/runtime/runtime_impl.h" #include "test/common/stats/stat_test_utility.h" @@ -34,6 +33,7 @@ using testing::StrictMock; namespace Envoy { namespace Http { +namespace Http1 { namespace { std::string createHeaderFragment(int num_headers) { // Create a header field with num_headers headers. @@ -55,7 +55,7 @@ Buffer::OwnedImpl createBufferWithNByteSlices(absl::string_view input, size_t ma } } // namespace -class Http1CodecTestBase { +class Http1CodecTestBase : public testing::Test { protected: Http::Http1::CodecStats& http1CodecStats() { return Http::Http1::CodecStats::atomicGet(http1_codec_stats_, store_); @@ -65,19 +65,12 @@ class Http1CodecTestBase { Http::Http1::CodecStats::AtomicPtr http1_codec_stats_; }; -class Http1ServerConnectionImplTest : public Http1CodecTestBase, - public testing::TestWithParam { +class Http1ServerConnectionImplTest : public Http1CodecTestBase { public: void initialize() { - if (GetParam()) { - codec_ = std::make_unique( - connection_, http1CodecStats(), callbacks_, codec_settings_, max_request_headers_kb_, - max_request_headers_count_, headers_with_underscores_action_); - } else { - codec_ = std::make_unique( - connection_, http1CodecStats(), callbacks_, codec_settings_, max_request_headers_kb_, - max_request_headers_count_, headers_with_underscores_action_); - } + codec_ = std::make_unique( + connection_, http1CodecStats(), callbacks_, codec_settings_, max_request_headers_kb_, + max_request_headers_count_, headers_with_underscores_action_); } NiceMock connection_; @@ -135,15 +128,9 @@ void Http1ServerConnectionImplTest::expect400(Protocol p, bool allow_absolute_ur if (allow_absolute_url) { codec_settings_.allow_absolute_url_ = allow_absolute_url; - if (GetParam()) { - codec_ = std::make_unique( - connection_, http1CodecStats(), callbacks_, codec_settings_, max_request_headers_kb_, - max_request_headers_count_, envoy::config::core::v3::HttpProtocolOptions::ALLOW); - } else { - codec_ = std::make_unique( - connection_, http1CodecStats(), callbacks_, codec_settings_, max_request_headers_kb_, - max_request_headers_count_, envoy::config::core::v3::HttpProtocolOptions::ALLOW); - } + codec_ = std::make_unique( + connection_, http1CodecStats(), callbacks_, codec_settings_, max_request_headers_kb_, + max_request_headers_count_, envoy::config::core::v3::HttpProtocolOptions::ALLOW); } MockRequestDecoder decoder; @@ -171,15 +158,9 @@ void Http1ServerConnectionImplTest::expectHeadersTest(Protocol p, bool allow_abs // Make a new 'codec' with the right settings if (allow_absolute_url) { codec_settings_.allow_absolute_url_ = allow_absolute_url; - if (GetParam()) { - codec_ = std::make_unique( - connection_, http1CodecStats(), callbacks_, codec_settings_, max_request_headers_kb_, - max_request_headers_count_, envoy::config::core::v3::HttpProtocolOptions::ALLOW); - } else { - codec_ = std::make_unique( - connection_, http1CodecStats(), callbacks_, codec_settings_, max_request_headers_kb_, - max_request_headers_count_, envoy::config::core::v3::HttpProtocolOptions::ALLOW); - } + codec_ = std::make_unique( + connection_, http1CodecStats(), callbacks_, codec_settings_, max_request_headers_kb_, + max_request_headers_count_, envoy::config::core::v3::HttpProtocolOptions::ALLOW); } MockRequestDecoder decoder; @@ -198,15 +179,9 @@ void Http1ServerConnectionImplTest::expectTrailersTest(bool enable_trailers) { // Make a new 'codec' with the right settings if (enable_trailers) { codec_settings_.enable_trailers_ = enable_trailers; - if (GetParam()) { - codec_ = std::make_unique( - connection_, http1CodecStats(), callbacks_, codec_settings_, max_request_headers_kb_, - max_request_headers_count_, envoy::config::core::v3::HttpProtocolOptions::ALLOW); - } else { - codec_ = std::make_unique( - connection_, http1CodecStats(), callbacks_, codec_settings_, max_request_headers_kb_, - max_request_headers_count_, envoy::config::core::v3::HttpProtocolOptions::ALLOW); - } + codec_ = std::make_unique( + connection_, http1CodecStats(), callbacks_, codec_settings_, max_request_headers_kb_, + max_request_headers_count_, envoy::config::core::v3::HttpProtocolOptions::ALLOW); } InSequence sequence; @@ -240,15 +215,9 @@ void Http1ServerConnectionImplTest::testTrailersExceedLimit(std::string trailer_ initialize(); // Make a new 'codec' with the right settings codec_settings_.enable_trailers_ = enable_trailers; - if (GetParam()) { - codec_ = std::make_unique( - connection_, http1CodecStats(), callbacks_, codec_settings_, max_request_headers_kb_, - max_request_headers_count_, envoy::config::core::v3::HttpProtocolOptions::ALLOW); - } else { - codec_ = std::make_unique( - connection_, http1CodecStats(), callbacks_, codec_settings_, max_request_headers_kb_, - max_request_headers_count_, envoy::config::core::v3::HttpProtocolOptions::ALLOW); - } + codec_ = std::make_unique( + connection_, http1CodecStats(), callbacks_, codec_settings_, max_request_headers_kb_, + max_request_headers_count_, envoy::config::core::v3::HttpProtocolOptions::ALLOW); std::string exception_reason; NiceMock decoder; EXPECT_CALL(callbacks_, newStream(_, _)) @@ -326,12 +295,7 @@ void Http1ServerConnectionImplTest::testRequestHeadersAccepted(std::string heade EXPECT_TRUE(status.ok()); } -INSTANTIATE_TEST_SUITE_P(Codecs, Http1ServerConnectionImplTest, testing::Bool(), - [](const testing::TestParamInfo& param) { - return param.param ? "New" : "Legacy"; - }); - -TEST_P(Http1ServerConnectionImplTest, EmptyHeader) { +TEST_F(Http1ServerConnectionImplTest, EmptyHeader) { initialize(); InSequence sequence; @@ -355,7 +319,7 @@ TEST_P(Http1ServerConnectionImplTest, EmptyHeader) { // We support the identity encoding, but because it does not end in chunked encoding we reject it // per RFC 7230 Section 3.3.3 -TEST_P(Http1ServerConnectionImplTest, IdentityEncodingNoChunked) { +TEST_F(Http1ServerConnectionImplTest, IdentityEncodingNoChunked) { initialize(); InSequence sequence; @@ -370,7 +334,7 @@ TEST_P(Http1ServerConnectionImplTest, IdentityEncodingNoChunked) { EXPECT_EQ(status.message(), "http/1.1 protocol error: unsupported transfer encoding"); } -TEST_P(Http1ServerConnectionImplTest, UnsupportedEncoding) { +TEST_F(Http1ServerConnectionImplTest, UnsupportedEncoding) { initialize(); InSequence sequence; @@ -386,7 +350,7 @@ TEST_P(Http1ServerConnectionImplTest, UnsupportedEncoding) { } // Verify that data in the two body chunks is merged before the call to decodeData. -TEST_P(Http1ServerConnectionImplTest, ChunkedBody) { +TEST_F(Http1ServerConnectionImplTest, ChunkedBody) { initialize(); InSequence sequence; @@ -417,7 +381,7 @@ TEST_P(Http1ServerConnectionImplTest, ChunkedBody) { // Verify dispatch behavior when dispatching an incomplete chunk, and resumption of the parse via a // second dispatch. -TEST_P(Http1ServerConnectionImplTest, ChunkedBodySplitOverTwoDispatches) { +TEST_F(Http1ServerConnectionImplTest, ChunkedBodySplitOverTwoDispatches) { initialize(); InSequence sequence; @@ -455,7 +419,7 @@ TEST_P(Http1ServerConnectionImplTest, ChunkedBodySplitOverTwoDispatches) { // Verify that headers and chunked body are processed correctly and data is merged before the // decodeData call even if delivered in a buffer that holds 1 byte per slice. -TEST_P(Http1ServerConnectionImplTest, ChunkedBodyFragmentedBuffer) { +TEST_F(Http1ServerConnectionImplTest, ChunkedBodyFragmentedBuffer) { initialize(); InSequence sequence; @@ -484,7 +448,7 @@ TEST_P(Http1ServerConnectionImplTest, ChunkedBodyFragmentedBuffer) { EXPECT_EQ(0U, buffer.length()); } -TEST_P(Http1ServerConnectionImplTest, ChunkedBodyCase) { +TEST_F(Http1ServerConnectionImplTest, ChunkedBodyCase) { initialize(); InSequence sequence; @@ -511,7 +475,7 @@ TEST_P(Http1ServerConnectionImplTest, ChunkedBodyCase) { // Verify that body dispatch does not happen after detecting a parse error processing a chunk // header. -TEST_P(Http1ServerConnectionImplTest, InvalidChunkHeader) { +TEST_F(Http1ServerConnectionImplTest, InvalidChunkHeader) { initialize(); InSequence sequence; @@ -537,7 +501,7 @@ TEST_P(Http1ServerConnectionImplTest, InvalidChunkHeader) { EXPECT_EQ(status.message(), "http/1.1 protocol error: HPE_INVALID_CHUNK_SIZE"); } -TEST_P(Http1ServerConnectionImplTest, IdentityAndChunkedBody) { +TEST_F(Http1ServerConnectionImplTest, IdentityAndChunkedBody) { initialize(); InSequence sequence; @@ -554,7 +518,7 @@ TEST_P(Http1ServerConnectionImplTest, IdentityAndChunkedBody) { EXPECT_EQ(status.message(), "http/1.1 protocol error: unsupported transfer encoding"); } -TEST_P(Http1ServerConnectionImplTest, HostWithLWS) { +TEST_F(Http1ServerConnectionImplTest, HostWithLWS) { initialize(); TestRequestHeaderMapImpl expected_headers{ @@ -575,7 +539,7 @@ TEST_P(Http1ServerConnectionImplTest, HostWithLWS) { // Regression test for https://github.com/envoyproxy/envoy/issues/10270. Linear whitespace at the // beginning and end of a header value should be stripped. Whitespace in the middle should be // preserved. -TEST_P(Http1ServerConnectionImplTest, InnerLWSIsPreserved) { +TEST_F(Http1ServerConnectionImplTest, InnerLWSIsPreserved) { initialize(); // Header with many spaces surrounded by non-whitespace characters to ensure that dispatching is @@ -608,7 +572,7 @@ TEST_P(Http1ServerConnectionImplTest, InnerLWSIsPreserved) { } } -TEST_P(Http1ServerConnectionImplTest, Http10) { +TEST_F(Http1ServerConnectionImplTest, Http10) { initialize(); InSequence sequence; @@ -626,7 +590,7 @@ TEST_P(Http1ServerConnectionImplTest, Http10) { EXPECT_EQ(Protocol::Http10, codec_->protocol()); } -TEST_P(Http1ServerConnectionImplTest, Http10AbsoluteNoOp) { +TEST_F(Http1ServerConnectionImplTest, Http10AbsoluteNoOp) { initialize(); TestRequestHeaderMapImpl expected_headers{{":path", "/"}, {":method", "GET"}}; @@ -634,7 +598,7 @@ TEST_P(Http1ServerConnectionImplTest, Http10AbsoluteNoOp) { expectHeadersTest(Protocol::Http10, true, buffer, expected_headers); } -TEST_P(Http1ServerConnectionImplTest, Http10Absolute) { +TEST_F(Http1ServerConnectionImplTest, Http10Absolute) { initialize(); TestRequestHeaderMapImpl expected_headers{ @@ -643,7 +607,7 @@ TEST_P(Http1ServerConnectionImplTest, Http10Absolute) { expectHeadersTest(Protocol::Http10, true, buffer, expected_headers); } -TEST_P(Http1ServerConnectionImplTest, Http10MultipleResponses) { +TEST_F(Http1ServerConnectionImplTest, Http10MultipleResponses) { initialize(); MockRequestDecoder decoder; @@ -689,7 +653,7 @@ TEST_P(Http1ServerConnectionImplTest, Http10MultipleResponses) { } } -TEST_P(Http1ServerConnectionImplTest, Http11AbsolutePath1) { +TEST_F(Http1ServerConnectionImplTest, Http11AbsolutePath1) { initialize(); TestRequestHeaderMapImpl expected_headers{ @@ -698,7 +662,7 @@ TEST_P(Http1ServerConnectionImplTest, Http11AbsolutePath1) { expectHeadersTest(Protocol::Http11, true, buffer, expected_headers); } -TEST_P(Http1ServerConnectionImplTest, Http11AbsolutePath2) { +TEST_F(Http1ServerConnectionImplTest, Http11AbsolutePath2) { initialize(); TestRequestHeaderMapImpl expected_headers{ @@ -707,7 +671,7 @@ TEST_P(Http1ServerConnectionImplTest, Http11AbsolutePath2) { expectHeadersTest(Protocol::Http11, true, buffer, expected_headers); } -TEST_P(Http1ServerConnectionImplTest, Http11AbsolutePathWithPort) { +TEST_F(Http1ServerConnectionImplTest, Http11AbsolutePathWithPort) { initialize(); TestRequestHeaderMapImpl expected_headers{ @@ -717,7 +681,7 @@ TEST_P(Http1ServerConnectionImplTest, Http11AbsolutePathWithPort) { expectHeadersTest(Protocol::Http11, true, buffer, expected_headers); } -TEST_P(Http1ServerConnectionImplTest, Http11AbsoluteEnabledNoOp) { +TEST_F(Http1ServerConnectionImplTest, Http11AbsoluteEnabledNoOp) { initialize(); TestRequestHeaderMapImpl expected_headers{ @@ -726,7 +690,7 @@ TEST_P(Http1ServerConnectionImplTest, Http11AbsoluteEnabledNoOp) { expectHeadersTest(Protocol::Http11, true, buffer, expected_headers); } -TEST_P(Http1ServerConnectionImplTest, Http11InvalidRequest) { +TEST_F(Http1ServerConnectionImplTest, Http11InvalidRequest) { initialize(); // Invalid because www.somewhere.com is not an absolute path nor an absolute url @@ -734,7 +698,7 @@ TEST_P(Http1ServerConnectionImplTest, Http11InvalidRequest) { expect400(Protocol::Http11, true, buffer, "http1.codec_error"); } -TEST_P(Http1ServerConnectionImplTest, Http11InvalidTrailerPost) { +TEST_F(Http1ServerConnectionImplTest, Http11InvalidTrailerPost) { initialize(); MockRequestDecoder decoder; @@ -759,7 +723,7 @@ TEST_P(Http1ServerConnectionImplTest, Http11InvalidTrailerPost) { EXPECT_TRUE(isCodecProtocolError(status)); } -TEST_P(Http1ServerConnectionImplTest, Http11AbsolutePathNoSlash) { +TEST_F(Http1ServerConnectionImplTest, Http11AbsolutePathNoSlash) { initialize(); TestRequestHeaderMapImpl expected_headers{ @@ -768,21 +732,21 @@ TEST_P(Http1ServerConnectionImplTest, Http11AbsolutePathNoSlash) { expectHeadersTest(Protocol::Http11, true, buffer, expected_headers); } -TEST_P(Http1ServerConnectionImplTest, Http11AbsolutePathBad) { +TEST_F(Http1ServerConnectionImplTest, Http11AbsolutePathBad) { initialize(); Buffer::OwnedImpl buffer("GET * HTTP/1.1\r\nHost: bah\r\n\r\n"); expect400(Protocol::Http11, true, buffer, "http1.invalid_url"); } -TEST_P(Http1ServerConnectionImplTest, Http11AbsolutePortTooLarge) { +TEST_F(Http1ServerConnectionImplTest, Http11AbsolutePortTooLarge) { initialize(); Buffer::OwnedImpl buffer("GET http://foobar.com:1000000 HTTP/1.1\r\nHost: bah\r\n\r\n"); expect400(Protocol::Http11, true, buffer); } -TEST_P(Http1ServerConnectionImplTest, SketchyConnectionHeader) { +TEST_F(Http1ServerConnectionImplTest, SketchyConnectionHeader) { initialize(); Buffer::OwnedImpl buffer( @@ -790,7 +754,7 @@ TEST_P(Http1ServerConnectionImplTest, SketchyConnectionHeader) { expect400(Protocol::Http11, true, buffer, "http1.connection_header_rejected"); } -TEST_P(Http1ServerConnectionImplTest, Http11RelativeOnly) { +TEST_F(Http1ServerConnectionImplTest, Http11RelativeOnly) { initialize(); TestRequestHeaderMapImpl expected_headers{ @@ -799,7 +763,7 @@ TEST_P(Http1ServerConnectionImplTest, Http11RelativeOnly) { expectHeadersTest(Protocol::Http11, false, buffer, expected_headers); } -TEST_P(Http1ServerConnectionImplTest, Http11Options) { +TEST_F(Http1ServerConnectionImplTest, Http11Options) { initialize(); TestRequestHeaderMapImpl expected_headers{ @@ -808,7 +772,7 @@ TEST_P(Http1ServerConnectionImplTest, Http11Options) { expectHeadersTest(Protocol::Http11, true, buffer, expected_headers); } -TEST_P(Http1ServerConnectionImplTest, SimpleGet) { +TEST_F(Http1ServerConnectionImplTest, SimpleGet) { initialize(); InSequence sequence; @@ -825,7 +789,7 @@ TEST_P(Http1ServerConnectionImplTest, SimpleGet) { EXPECT_EQ(0U, buffer.length()); } -TEST_P(Http1ServerConnectionImplTest, BadRequestNoStreamLegacy) { +TEST_F(Http1ServerConnectionImplTest, BadRequestNoStreamLegacy) { TestScopedRuntime scoped_runtime; Runtime::LoaderSingleton::getExisting()->mergeValues( {{"envoy.reloadable_features.early_errors_via_hcm", "false"}}); @@ -845,7 +809,7 @@ TEST_P(Http1ServerConnectionImplTest, BadRequestNoStreamLegacy) { // Test that if the stream is not created at the time an error is detected, it // is created as part of sending the protocol error. -TEST_P(Http1ServerConnectionImplTest, BadRequestNoStream) { +TEST_F(Http1ServerConnectionImplTest, BadRequestNoStream) { initialize(); MockRequestDecoder decoder; @@ -864,7 +828,7 @@ TEST_P(Http1ServerConnectionImplTest, BadRequestNoStream) { } // Make sure that if the first line is parsed, that sendLocalReply tracks HEAD requests correctly. -TEST_P(Http1ServerConnectionImplTest, BadHeadRequest) { +TEST_F(Http1ServerConnectionImplTest, BadHeadRequest) { initialize(); MockRequestDecoder decoder; @@ -884,7 +848,7 @@ TEST_P(Http1ServerConnectionImplTest, BadHeadRequest) { } // Make sure that if gRPC headers are parsed, they are tracked by sendLocalReply. -TEST_P(Http1ServerConnectionImplTest, BadGrpcRequest) { +TEST_F(Http1ServerConnectionImplTest, BadGrpcRequest) { initialize(); MockRequestDecoder decoder; @@ -905,7 +869,7 @@ TEST_P(Http1ServerConnectionImplTest, BadGrpcRequest) { // This behavior was observed during CVE-2019-18801 and helped to limit the // scope of affected Envoy configurations. -TEST_P(Http1ServerConnectionImplTest, RejectInvalidMethod) { +TEST_F(Http1ServerConnectionImplTest, RejectInvalidMethod) { initialize(); MockRequestDecoder decoder; @@ -917,7 +881,7 @@ TEST_P(Http1ServerConnectionImplTest, RejectInvalidMethod) { EXPECT_TRUE(isCodecProtocolError(status)); } -TEST_P(Http1ServerConnectionImplTest, BadRequestStartedStream) { +TEST_F(Http1ServerConnectionImplTest, BadRequestStartedStream) { initialize(); MockRequestDecoder decoder; @@ -933,7 +897,7 @@ TEST_P(Http1ServerConnectionImplTest, BadRequestStartedStream) { EXPECT_TRUE(isCodecProtocolError(status)); } -TEST_P(Http1ServerConnectionImplTest, FloodProtection) { +TEST_F(Http1ServerConnectionImplTest, FloodProtection) { initialize(); NiceMock decoder; @@ -984,7 +948,7 @@ TEST_P(Http1ServerConnectionImplTest, FloodProtection) { } } -TEST_P(Http1ServerConnectionImplTest, FloodProtectionOff) { +TEST_F(Http1ServerConnectionImplTest, FloodProtectionOff) { TestScopedRuntime scoped_runtime; Runtime::LoaderSingleton::getExisting()->mergeValues( {{"envoy.reloadable_features.http1_flood_protection", "false"}}); @@ -1020,7 +984,7 @@ TEST_P(Http1ServerConnectionImplTest, FloodProtectionOff) { } } -TEST_P(Http1ServerConnectionImplTest, HostHeaderTranslation) { +TEST_F(Http1ServerConnectionImplTest, HostHeaderTranslation) { initialize(); InSequence sequence; @@ -1039,8 +1003,7 @@ TEST_P(Http1ServerConnectionImplTest, HostHeaderTranslation) { } // Ensures that requests with invalid HTTP header values are properly rejected -// when the runtime guard is enabled for the feature. -TEST_P(Http1ServerConnectionImplTest, HeaderInvalidCharsRejection) { +TEST_F(Http1ServerConnectionImplTest, HeaderInvalidCharsRejection) { TestScopedRuntime scoped_runtime; // When the runtime-guarded feature is enabled, invalid header values // should result in a rejection. @@ -1065,7 +1028,7 @@ TEST_P(Http1ServerConnectionImplTest, HeaderInvalidCharsRejection) { // Ensures that request headers with names containing the underscore character are allowed // when the option is set to allow. -TEST_P(Http1ServerConnectionImplTest, HeaderNameWithUnderscoreAllowed) { +TEST_F(Http1ServerConnectionImplTest, HeaderNameWithUnderscoreAllowed) { headers_with_underscores_action_ = envoy::config::core::v3::HttpProtocolOptions::ALLOW; initialize(); @@ -1089,7 +1052,7 @@ TEST_P(Http1ServerConnectionImplTest, HeaderNameWithUnderscoreAllowed) { // Ensures that request headers with names containing the underscore character are dropped // when the option is set to drop headers. -TEST_P(Http1ServerConnectionImplTest, HeaderNameWithUnderscoreAreDropped) { +TEST_F(Http1ServerConnectionImplTest, HeaderNameWithUnderscoreAreDropped) { headers_with_underscores_action_ = envoy::config::core::v3::HttpProtocolOptions::DROP_HEADER; initialize(); @@ -1112,7 +1075,7 @@ TEST_P(Http1ServerConnectionImplTest, HeaderNameWithUnderscoreAreDropped) { // Ensures that request with header names containing the underscore character are rejected // when the option is set to reject request. -TEST_P(Http1ServerConnectionImplTest, HeaderNameWithUnderscoreCauseRequestRejected) { +TEST_F(Http1ServerConnectionImplTest, HeaderNameWithUnderscoreCauseRequestRejected) { headers_with_underscores_action_ = envoy::config::core::v3::HttpProtocolOptions::REJECT_REQUEST; initialize(); @@ -1133,7 +1096,7 @@ TEST_P(Http1ServerConnectionImplTest, HeaderNameWithUnderscoreCauseRequestReject EXPECT_EQ(1, store_.counter("http1.requests_rejected_with_underscores_in_headers").value()); } -TEST_P(Http1ServerConnectionImplTest, HeaderInvalidAuthority) { +TEST_F(Http1ServerConnectionImplTest, HeaderInvalidAuthority) { TestScopedRuntime scoped_runtime; initialize(); @@ -1156,7 +1119,7 @@ TEST_P(Http1ServerConnectionImplTest, HeaderInvalidAuthority) { // Mutate an HTTP GET with embedded NULs, this should always be rejected in some // way (not necessarily with "head value contains NUL" though). -TEST_P(Http1ServerConnectionImplTest, HeaderMutateEmbeddedNul) { +TEST_F(Http1ServerConnectionImplTest, HeaderMutateEmbeddedNul) { const std::string example_input = "GET / HTTP/1.1\r\nHOST: h.com\r\nfoo: barbaz\r\n"; for (size_t n = 1; n < example_input.size(); ++n) { @@ -1180,7 +1143,7 @@ TEST_P(Http1ServerConnectionImplTest, HeaderMutateEmbeddedNul) { // Mutate an HTTP GET with CR or LF. These can cause an error status or maybe // result in a valid decodeHeaders(). In any case, the validHeaderString() // ASSERTs should validate we never have any embedded CR or LF. -TEST_P(Http1ServerConnectionImplTest, HeaderMutateEmbeddedCRLF) { +TEST_F(Http1ServerConnectionImplTest, HeaderMutateEmbeddedCRLF) { const std::string example_input = "GET / HTTP/1.1\r\nHOST: h.com\r\nfoo: barbaz\r\n"; for (const char c : {'\r', '\n'}) { @@ -1200,7 +1163,7 @@ TEST_P(Http1ServerConnectionImplTest, HeaderMutateEmbeddedCRLF) { } } -TEST_P(Http1ServerConnectionImplTest, CloseDuringHeadersComplete) { +TEST_F(Http1ServerConnectionImplTest, CloseDuringHeadersComplete) { initialize(); InSequence sequence; @@ -1222,7 +1185,7 @@ TEST_P(Http1ServerConnectionImplTest, CloseDuringHeadersComplete) { EXPECT_NE(0U, buffer.length()); } -TEST_P(Http1ServerConnectionImplTest, PostWithContentLength) { +TEST_F(Http1ServerConnectionImplTest, PostWithContentLength) { initialize(); InSequence sequence; @@ -1248,7 +1211,7 @@ TEST_P(Http1ServerConnectionImplTest, PostWithContentLength) { // Verify that headers and body with content length are processed correctly and data is merged // before the decodeData call even if delivered in a buffer that holds 1 byte per slice. -TEST_P(Http1ServerConnectionImplTest, PostWithContentLengthFragmentedBuffer) { +TEST_F(Http1ServerConnectionImplTest, PostWithContentLengthFragmentedBuffer) { initialize(); InSequence sequence; @@ -1273,7 +1236,7 @@ TEST_P(Http1ServerConnectionImplTest, PostWithContentLengthFragmentedBuffer) { EXPECT_EQ(0U, buffer.length()); } -TEST_P(Http1ServerConnectionImplTest, HeaderOnlyResponse) { +TEST_F(Http1ServerConnectionImplTest, HeaderOnlyResponse) { initialize(); NiceMock decoder; @@ -1299,7 +1262,7 @@ TEST_P(Http1ServerConnectionImplTest, HeaderOnlyResponse) { // As with Http1ClientConnectionImplTest.LargeHeaderRequestEncode but validate // the response encoder instead of request encoder. -TEST_P(Http1ServerConnectionImplTest, LargeHeaderResponseEncode) { +TEST_F(Http1ServerConnectionImplTest, LargeHeaderResponseEncode) { initialize(); NiceMock decoder; @@ -1325,7 +1288,7 @@ TEST_P(Http1ServerConnectionImplTest, LargeHeaderResponseEncode) { output); } -TEST_P(Http1ServerConnectionImplTest, HeaderOnlyResponseTrainProperHeaders) { +TEST_F(Http1ServerConnectionImplTest, HeaderOnlyResponseTrainProperHeaders) { codec_settings_.header_key_format_ = Http1Settings::HeaderKeyFormat::ProperCase; initialize(); @@ -1352,7 +1315,7 @@ TEST_P(Http1ServerConnectionImplTest, HeaderOnlyResponseTrainProperHeaders) { output); } -TEST_P(Http1ServerConnectionImplTest, HeaderOnlyResponseWith204) { +TEST_F(Http1ServerConnectionImplTest, HeaderOnlyResponseWith204) { initialize(); NiceMock decoder; @@ -1376,7 +1339,7 @@ TEST_P(Http1ServerConnectionImplTest, HeaderOnlyResponseWith204) { EXPECT_EQ("HTTP/1.1 204 No Content\r\n\r\n", output); } -TEST_P(Http1ServerConnectionImplTest, HeaderOnlyResponseWith100Then200) { +TEST_F(Http1ServerConnectionImplTest, HeaderOnlyResponseWith100Then200) { initialize(); NiceMock decoder; @@ -1407,7 +1370,7 @@ TEST_P(Http1ServerConnectionImplTest, HeaderOnlyResponseWith100Then200) { EXPECT_EQ("HTTP/1.1 200 OK\r\ncontent-length: 0\r\n\r\n", output); } -TEST_P(Http1ServerConnectionImplTest, MetadataTest) { +TEST_F(Http1ServerConnectionImplTest, MetadataTest) { initialize(); NiceMock decoder; @@ -1430,7 +1393,7 @@ TEST_P(Http1ServerConnectionImplTest, MetadataTest) { EXPECT_EQ(1, store_.counter("http1.metadata_not_supported_error").value()); } -TEST_P(Http1ServerConnectionImplTest, ChunkedResponse) { +TEST_F(Http1ServerConnectionImplTest, ChunkedResponse) { initialize(); NiceMock decoder; @@ -1466,7 +1429,7 @@ TEST_P(Http1ServerConnectionImplTest, ChunkedResponse) { output); } -TEST_P(Http1ServerConnectionImplTest, ChunkedResponseWithTrailers) { +TEST_F(Http1ServerConnectionImplTest, ChunkedResponseWithTrailers) { codec_settings_.enable_trailers_ = true; initialize(); NiceMock decoder; @@ -1499,7 +1462,7 @@ TEST_P(Http1ServerConnectionImplTest, ChunkedResponseWithTrailers) { output); } -TEST_P(Http1ServerConnectionImplTest, ContentLengthResponse) { +TEST_F(Http1ServerConnectionImplTest, ContentLengthResponse) { initialize(); NiceMock decoder; @@ -1526,7 +1489,7 @@ TEST_P(Http1ServerConnectionImplTest, ContentLengthResponse) { EXPECT_EQ("HTTP/1.1 200 OK\r\ncontent-length: 11\r\n\r\nHello World", output); } -TEST_P(Http1ServerConnectionImplTest, HeadRequestResponse) { +TEST_F(Http1ServerConnectionImplTest, HeadRequestResponse) { initialize(); NiceMock decoder; @@ -1550,7 +1513,7 @@ TEST_P(Http1ServerConnectionImplTest, HeadRequestResponse) { EXPECT_EQ("HTTP/1.1 200 OK\r\ncontent-length: 5\r\n\r\n", output); } -TEST_P(Http1ServerConnectionImplTest, HeadChunkedRequestResponse) { +TEST_F(Http1ServerConnectionImplTest, HeadChunkedRequestResponse) { initialize(); NiceMock decoder; @@ -1574,7 +1537,7 @@ TEST_P(Http1ServerConnectionImplTest, HeadChunkedRequestResponse) { EXPECT_EQ("HTTP/1.1 200 OK\r\ntransfer-encoding: chunked\r\n\r\n", output); } -TEST_P(Http1ServerConnectionImplTest, DoubleRequest) { +TEST_F(Http1ServerConnectionImplTest, DoubleRequest) { initialize(); NiceMock decoder; @@ -1600,11 +1563,11 @@ TEST_P(Http1ServerConnectionImplTest, DoubleRequest) { EXPECT_EQ(0U, buffer.length()); } -TEST_P(Http1ServerConnectionImplTest, RequestWithTrailersDropped) { expectTrailersTest(false); } +TEST_F(Http1ServerConnectionImplTest, RequestWithTrailersDropped) { expectTrailersTest(false); } -TEST_P(Http1ServerConnectionImplTest, RequestWithTrailersKept) { expectTrailersTest(true); } +TEST_F(Http1ServerConnectionImplTest, RequestWithTrailersKept) { expectTrailersTest(true); } -TEST_P(Http1ServerConnectionImplTest, IgnoreUpgradeH2c) { +TEST_F(Http1ServerConnectionImplTest, IgnoreUpgradeH2c) { initialize(); TestRequestHeaderMapImpl expected_headers{ @@ -1615,7 +1578,7 @@ TEST_P(Http1ServerConnectionImplTest, IgnoreUpgradeH2c) { expectHeadersTest(Protocol::Http11, true, buffer, expected_headers); } -TEST_P(Http1ServerConnectionImplTest, IgnoreUpgradeH2cClose) { +TEST_F(Http1ServerConnectionImplTest, IgnoreUpgradeH2cClose) { initialize(); TestRequestHeaderMapImpl expected_headers{{":authority", "www.somewhere.com"}, @@ -1628,7 +1591,7 @@ TEST_P(Http1ServerConnectionImplTest, IgnoreUpgradeH2cClose) { expectHeadersTest(Protocol::Http11, true, buffer, expected_headers); } -TEST_P(Http1ServerConnectionImplTest, IgnoreUpgradeH2cCloseEtc) { +TEST_F(Http1ServerConnectionImplTest, IgnoreUpgradeH2cCloseEtc) { initialize(); TestRequestHeaderMapImpl expected_headers{{":authority", "www.somewhere.com"}, @@ -1641,7 +1604,7 @@ TEST_P(Http1ServerConnectionImplTest, IgnoreUpgradeH2cCloseEtc) { expectHeadersTest(Protocol::Http11, true, buffer, expected_headers); } -TEST_P(Http1ServerConnectionImplTest, UpgradeRequest) { +TEST_F(Http1ServerConnectionImplTest, UpgradeRequest) { initialize(); InSequence sequence; @@ -1665,7 +1628,7 @@ TEST_P(Http1ServerConnectionImplTest, UpgradeRequest) { EXPECT_TRUE(status.ok()); } -TEST_P(Http1ServerConnectionImplTest, UpgradeRequestWithEarlyData) { +TEST_F(Http1ServerConnectionImplTest, UpgradeRequestWithEarlyData) { initialize(); InSequence sequence; @@ -1681,7 +1644,7 @@ TEST_P(Http1ServerConnectionImplTest, UpgradeRequestWithEarlyData) { EXPECT_TRUE(status.ok()); } -TEST_P(Http1ServerConnectionImplTest, UpgradeRequestWithTEChunked) { +TEST_F(Http1ServerConnectionImplTest, UpgradeRequestWithTEChunked) { initialize(); InSequence sequence; @@ -1699,7 +1662,7 @@ TEST_P(Http1ServerConnectionImplTest, UpgradeRequestWithTEChunked) { EXPECT_TRUE(status.ok()); } -TEST_P(Http1ServerConnectionImplTest, UpgradeRequestWithNoBody) { +TEST_F(Http1ServerConnectionImplTest, UpgradeRequestWithNoBody) { initialize(); InSequence sequence; @@ -1718,7 +1681,7 @@ TEST_P(Http1ServerConnectionImplTest, UpgradeRequestWithNoBody) { } // Test that 101 upgrade responses do not contain content-length or transfer-encoding headers. -TEST_P(Http1ServerConnectionImplTest, UpgradeRequestResponseHeaders) { +TEST_F(Http1ServerConnectionImplTest, UpgradeRequestResponseHeaders) { initialize(); NiceMock decoder; @@ -1742,7 +1705,7 @@ TEST_P(Http1ServerConnectionImplTest, UpgradeRequestResponseHeaders) { EXPECT_EQ("HTTP/1.1 101 Switching Protocols\r\n\r\n", output); } -TEST_P(Http1ServerConnectionImplTest, ConnectRequestNoContentLength) { +TEST_F(Http1ServerConnectionImplTest, ConnectRequestNoContentLength) { initialize(); InSequence sequence; @@ -1766,7 +1729,7 @@ TEST_P(Http1ServerConnectionImplTest, ConnectRequestNoContentLength) { // We use the absolute URL parsing code for CONNECT requests, but it does not // actually allow absolute URLs. -TEST_P(Http1ServerConnectionImplTest, ConnectRequestAbsoluteURLNotallowed) { +TEST_F(Http1ServerConnectionImplTest, ConnectRequestAbsoluteURLNotallowed) { initialize(); InSequence sequence; @@ -1779,7 +1742,7 @@ TEST_P(Http1ServerConnectionImplTest, ConnectRequestAbsoluteURLNotallowed) { EXPECT_TRUE(isCodecProtocolError(status)); } -TEST_P(Http1ServerConnectionImplTest, ConnectRequestWithEarlyData) { +TEST_F(Http1ServerConnectionImplTest, ConnectRequestWithEarlyData) { initialize(); InSequence sequence; @@ -1794,7 +1757,7 @@ TEST_P(Http1ServerConnectionImplTest, ConnectRequestWithEarlyData) { EXPECT_TRUE(status.ok()); } -TEST_P(Http1ServerConnectionImplTest, ConnectRequestWithTEChunked) { +TEST_F(Http1ServerConnectionImplTest, ConnectRequestWithTEChunked) { initialize(); InSequence sequence; @@ -1811,7 +1774,7 @@ TEST_P(Http1ServerConnectionImplTest, ConnectRequestWithTEChunked) { EXPECT_EQ(status.message(), "http/1.1 protocol error: unsupported transfer encoding"); } -TEST_P(Http1ServerConnectionImplTest, ConnectRequestWithNonZeroContentLength) { +TEST_F(Http1ServerConnectionImplTest, ConnectRequestWithNonZeroContentLength) { initialize(); InSequence sequence; @@ -1827,7 +1790,7 @@ TEST_P(Http1ServerConnectionImplTest, ConnectRequestWithNonZeroContentLength) { EXPECT_EQ(status.message(), "http/1.1 protocol error: unsupported content length"); } -TEST_P(Http1ServerConnectionImplTest, ConnectRequestWithZeroContentLength) { +TEST_F(Http1ServerConnectionImplTest, ConnectRequestWithZeroContentLength) { initialize(); InSequence sequence; @@ -1844,7 +1807,7 @@ TEST_P(Http1ServerConnectionImplTest, ConnectRequestWithZeroContentLength) { EXPECT_TRUE(status.ok()); } -TEST_P(Http1ServerConnectionImplTest, WatermarkTest) { +TEST_F(Http1ServerConnectionImplTest, WatermarkTest) { EXPECT_CALL(connection_, bufferLimit()).WillOnce(Return(10)); initialize(); @@ -1878,43 +1841,24 @@ TEST_P(Http1ServerConnectionImplTest, WatermarkTest) { ->onUnderlyingConnectionBelowWriteBufferLowWatermark(); } -class Http1ClientConnectionImplTest : public Http1CodecTestBase, - public testing::TestWithParam { +class Http1ClientConnectionImplTest : public Http1CodecTestBase { public: void initialize() { - if (GetParam()) { - codec_ = std::make_unique( - connection_, http1CodecStats(), callbacks_, codec_settings_, max_response_headers_count_); - } else { - codec_ = std::make_unique( - connection_, http1CodecStats(), callbacks_, codec_settings_, max_response_headers_count_); - } - } - - void readDisableOnRequestEncoder(RequestEncoder* request_encoder, bool disable) { - if (GetParam()) { - dynamic_cast(request_encoder)->readDisable(disable); - } else { - dynamic_cast(request_encoder)->readDisable(disable); - } + codec_ = std::make_unique(connection_, http1CodecStats(), callbacks_, + codec_settings_, max_response_headers_count_); } NiceMock connection_; NiceMock callbacks_; NiceMock codec_settings_; - Http::ClientConnectionPtr codec_; + std::unique_ptr codec_; protected: Stats::TestUtil::TestStore store_; uint32_t max_response_headers_count_{Http::DEFAULT_MAX_HEADERS_COUNT}; }; -INSTANTIATE_TEST_SUITE_P(Codecs, Http1ClientConnectionImplTest, testing::Bool(), - [](const testing::TestParamInfo& param) { - return param.param ? "New" : "Legacy"; - }); - -TEST_P(Http1ClientConnectionImplTest, SimpleGet) { +TEST_F(Http1ClientConnectionImplTest, SimpleGet) { initialize(); MockResponseDecoder response_decoder; @@ -1928,7 +1872,7 @@ TEST_P(Http1ClientConnectionImplTest, SimpleGet) { EXPECT_EQ("GET / HTTP/1.1\r\ncontent-length: 0\r\n\r\n", output); } -TEST_P(Http1ClientConnectionImplTest, SimpleGetWithHeaderCasing) { +TEST_F(Http1ClientConnectionImplTest, SimpleGetWithHeaderCasing) { codec_settings_.header_key_format_ = Http1Settings::HeaderKeyFormat::ProperCase; initialize(); @@ -1944,7 +1888,7 @@ TEST_P(Http1ClientConnectionImplTest, SimpleGetWithHeaderCasing) { EXPECT_EQ("GET / HTTP/1.1\r\nMy-Custom-Header: hey\r\nContent-Length: 0\r\n\r\n", output); } -TEST_P(Http1ClientConnectionImplTest, HostHeaderTranslate) { +TEST_F(Http1ClientConnectionImplTest, HostHeaderTranslate) { initialize(); MockResponseDecoder response_decoder; @@ -1958,7 +1902,7 @@ TEST_P(Http1ClientConnectionImplTest, HostHeaderTranslate) { EXPECT_EQ("GET / HTTP/1.1\r\nhost: host\r\ncontent-length: 0\r\n\r\n", output); } -TEST_P(Http1ClientConnectionImplTest, Reset) { +TEST_F(Http1ClientConnectionImplTest, Reset) { initialize(); MockResponseDecoder response_decoder; @@ -1972,15 +1916,16 @@ TEST_P(Http1ClientConnectionImplTest, Reset) { // Verify that we correctly enable reads on the connection when the final response is // received. -TEST_P(Http1ClientConnectionImplTest, FlowControlReadDisabledReenable) { +TEST_F(Http1ClientConnectionImplTest, FlowControlReadDisabledReenable) { initialize(); MockResponseDecoder response_decoder; - auto* request_encoder = &codec_->newStream(response_decoder); + Http::RequestEncoder* request_encoder = &codec_->newStream(response_decoder); // Manually read disable. EXPECT_CALL(connection_, readDisable(true)).Times(2); - readDisableOnRequestEncoder(request_encoder, true); - readDisableOnRequestEncoder(request_encoder, true); + RequestEncoderImpl* encoder = dynamic_cast(request_encoder); + encoder->readDisable(true); + encoder->readDisable(true); std::string output; ON_CALL(connection_, write(_, _)).WillByDefault(AddBufferToString(&output)); @@ -2001,7 +1946,7 @@ TEST_P(Http1ClientConnectionImplTest, FlowControlReadDisabledReenable) { EXPECT_TRUE(status.ok()); } -TEST_P(Http1ClientConnectionImplTest, PrematureResponse) { +TEST_F(Http1ClientConnectionImplTest, PrematureResponse) { initialize(); Buffer::OwnedImpl response("HTTP/1.1 408 Request Timeout\r\nConnection: Close\r\n\r\n"); @@ -2009,7 +1954,7 @@ TEST_P(Http1ClientConnectionImplTest, PrematureResponse) { EXPECT_TRUE(isPrematureResponseError(status)); } -TEST_P(Http1ClientConnectionImplTest, EmptyBodyResponse503) { +TEST_F(Http1ClientConnectionImplTest, EmptyBodyResponse503) { initialize(); NiceMock response_decoder; @@ -2023,7 +1968,7 @@ TEST_P(Http1ClientConnectionImplTest, EmptyBodyResponse503) { EXPECT_TRUE(status.ok()); } -TEST_P(Http1ClientConnectionImplTest, EmptyBodyResponse200) { +TEST_F(Http1ClientConnectionImplTest, EmptyBodyResponse200) { initialize(); NiceMock response_decoder; @@ -2037,7 +1982,7 @@ TEST_P(Http1ClientConnectionImplTest, EmptyBodyResponse200) { EXPECT_TRUE(status.ok()); } -TEST_P(Http1ClientConnectionImplTest, HeadRequest) { +TEST_F(Http1ClientConnectionImplTest, HeadRequest) { initialize(); NiceMock response_decoder; @@ -2051,7 +1996,7 @@ TEST_P(Http1ClientConnectionImplTest, HeadRequest) { EXPECT_TRUE(status.ok()); } -TEST_P(Http1ClientConnectionImplTest, 204Response) { +TEST_F(Http1ClientConnectionImplTest, 204Response) { initialize(); NiceMock response_decoder; @@ -2066,7 +2011,7 @@ TEST_P(Http1ClientConnectionImplTest, 204Response) { } // 204 No Content with Content-Length is barred by RFC 7230, Section 3.3.2. -TEST_P(Http1ClientConnectionImplTest, 204ResponseContentLengthNotAllowed) { +TEST_F(Http1ClientConnectionImplTest, 204ResponseContentLengthNotAllowed) { // By default, content-length is barred. { initialize(); @@ -2102,7 +2047,7 @@ TEST_P(Http1ClientConnectionImplTest, 204ResponseContentLengthNotAllowed) { // 204 No Content with Content-Length: 0 is technically barred by RFC 7230, Section 3.3.2, but we // allow it. -TEST_P(Http1ClientConnectionImplTest, 204ResponseWithContentLength0) { +TEST_F(Http1ClientConnectionImplTest, 204ResponseWithContentLength0) { { initialize(); @@ -2136,7 +2081,7 @@ TEST_P(Http1ClientConnectionImplTest, 204ResponseWithContentLength0) { } // 204 No Content with Transfer-Encoding headers is barred by RFC 7230, Section 3.3.1. -TEST_P(Http1ClientConnectionImplTest, 204ResponseTransferEncodingNotAllowed) { +TEST_F(Http1ClientConnectionImplTest, 204ResponseTransferEncodingNotAllowed) { // By default, transfer-encoding is barred. { initialize(); @@ -2170,7 +2115,7 @@ TEST_P(Http1ClientConnectionImplTest, 204ResponseTransferEncodingNotAllowed) { } } -TEST_P(Http1ClientConnectionImplTest, 100Response) { +TEST_F(Http1ClientConnectionImplTest, 100Response) { initialize(); NiceMock response_decoder; @@ -2191,7 +2136,7 @@ TEST_P(Http1ClientConnectionImplTest, 100Response) { } // 101 Switching Protocol with Transfer-Encoding headers is barred by RFC 7230, Section 3.3.1. -TEST_P(Http1ClientConnectionImplTest, 101ResponseTransferEncodingNotAllowed) { +TEST_F(Http1ClientConnectionImplTest, 101ResponseTransferEncodingNotAllowed) { // By default, transfer-encoding is barred. { initialize(); @@ -2227,7 +2172,7 @@ TEST_P(Http1ClientConnectionImplTest, 101ResponseTransferEncodingNotAllowed) { } } -TEST_P(Http1ClientConnectionImplTest, BadEncodeParams) { +TEST_F(Http1ClientConnectionImplTest, BadEncodeParams) { initialize(); NiceMock response_decoder; @@ -2240,7 +2185,7 @@ TEST_P(Http1ClientConnectionImplTest, BadEncodeParams) { CodecClientException); } -TEST_P(Http1ClientConnectionImplTest, NoContentLengthResponse) { +TEST_F(Http1ClientConnectionImplTest, NoContentLengthResponse) { initialize(); NiceMock response_decoder; @@ -2262,7 +2207,7 @@ TEST_P(Http1ClientConnectionImplTest, NoContentLengthResponse) { EXPECT_TRUE(status.ok()); } -TEST_P(Http1ClientConnectionImplTest, ResponseWithTrailers) { +TEST_F(Http1ClientConnectionImplTest, ResponseWithTrailers) { initialize(); NiceMock response_decoder; @@ -2277,7 +2222,7 @@ TEST_P(Http1ClientConnectionImplTest, ResponseWithTrailers) { EXPECT_TRUE(status.ok()); } -TEST_P(Http1ClientConnectionImplTest, GiantPath) { +TEST_F(Http1ClientConnectionImplTest, GiantPath) { initialize(); NiceMock response_decoder; @@ -2292,7 +2237,7 @@ TEST_P(Http1ClientConnectionImplTest, GiantPath) { EXPECT_TRUE(status.ok()); } -TEST_P(Http1ClientConnectionImplTest, PrematureUpgradeResponse) { +TEST_F(Http1ClientConnectionImplTest, PrematureUpgradeResponse) { initialize(); // make sure upgradeAllowed doesn't cause crashes if run with no pending response. @@ -2302,7 +2247,7 @@ TEST_P(Http1ClientConnectionImplTest, PrematureUpgradeResponse) { EXPECT_TRUE(isPrematureResponseError(status)); } -TEST_P(Http1ClientConnectionImplTest, UpgradeResponse) { +TEST_F(Http1ClientConnectionImplTest, UpgradeResponse) { initialize(); InSequence s; @@ -2338,7 +2283,7 @@ TEST_P(Http1ClientConnectionImplTest, UpgradeResponse) { // Same data as above, but make sure directDispatch immediately hands off any // outstanding data. -TEST_P(Http1ClientConnectionImplTest, UpgradeResponseWithEarlyData) { +TEST_F(Http1ClientConnectionImplTest, UpgradeResponseWithEarlyData) { initialize(); InSequence s; @@ -2362,7 +2307,7 @@ TEST_P(Http1ClientConnectionImplTest, UpgradeResponseWithEarlyData) { EXPECT_TRUE(status.ok()); } -TEST_P(Http1ClientConnectionImplTest, ConnectResponse) { +TEST_F(Http1ClientConnectionImplTest, ConnectResponse) { initialize(); InSequence s; @@ -2393,7 +2338,7 @@ TEST_P(Http1ClientConnectionImplTest, ConnectResponse) { // Same data as above, but make sure directDispatch immediately hands off any // outstanding data. -TEST_P(Http1ClientConnectionImplTest, ConnectResponseWithEarlyData) { +TEST_F(Http1ClientConnectionImplTest, ConnectResponseWithEarlyData) { initialize(); InSequence s; @@ -2412,7 +2357,7 @@ TEST_P(Http1ClientConnectionImplTest, ConnectResponseWithEarlyData) { EXPECT_TRUE(status.ok()); } -TEST_P(Http1ClientConnectionImplTest, ConnectRejected) { +TEST_F(Http1ClientConnectionImplTest, ConnectRejected) { initialize(); InSequence s; @@ -2430,7 +2375,7 @@ TEST_P(Http1ClientConnectionImplTest, ConnectRejected) { EXPECT_TRUE(status.ok()); } -TEST_P(Http1ClientConnectionImplTest, WatermarkTest) { +TEST_F(Http1ClientConnectionImplTest, WatermarkTest) { EXPECT_CALL(connection_, bufferLimit()).WillOnce(Return(10)); initialize(); @@ -2465,7 +2410,7 @@ TEST_P(Http1ClientConnectionImplTest, WatermarkTest) { // caller attempts to close the connection. This causes the network connection to attempt to write // pending data, even in the no flush scenario, which can cause us to go below low watermark // which then raises callbacks for a stream that no longer exists. -TEST_P(Http1ClientConnectionImplTest, HighwatermarkMultipleResponses) { +TEST_F(Http1ClientConnectionImplTest, HighwatermarkMultipleResponses) { initialize(); InSequence s; @@ -2499,7 +2444,7 @@ TEST_P(Http1ClientConnectionImplTest, HighwatermarkMultipleResponses) { // Regression test for https://github.com/envoyproxy/envoy/issues/10655. Make sure we correctly // handle going below low watermark when closing the connection during a completion callback. -TEST_P(Http1ClientConnectionImplTest, LowWatermarkDuringClose) { +TEST_F(Http1ClientConnectionImplTest, LowWatermarkDuringClose) { initialize(); InSequence s; @@ -2529,43 +2474,43 @@ TEST_P(Http1ClientConnectionImplTest, LowWatermarkDuringClose) { EXPECT_TRUE(status.ok()); } -TEST_P(Http1ServerConnectionImplTest, LargeTrailersRejected) { +TEST_F(Http1ServerConnectionImplTest, LargeTrailersRejected) { // Default limit of 60 KiB std::string long_string = "big: " + std::string(60 * 1024, 'q') + "\r\n\r\n\r\n"; testTrailersExceedLimit(long_string, true); } -TEST_P(Http1ServerConnectionImplTest, LargeTrailerFieldRejected) { +TEST_F(Http1ServerConnectionImplTest, LargeTrailerFieldRejected) { // Construct partial headers with a long field name that exceeds the default limit of 60KiB. std::string long_string = "bigfield" + std::string(60 * 1024, 'q'); testTrailersExceedLimit(long_string, true); } // Tests that the default limit for the number of request headers is 100. -TEST_P(Http1ServerConnectionImplTest, ManyTrailersRejected) { +TEST_F(Http1ServerConnectionImplTest, ManyTrailersRejected) { // Send a request with 101 headers. testTrailersExceedLimit(createHeaderFragment(101) + "\r\n\r\n", true); } -TEST_P(Http1ServerConnectionImplTest, LargeTrailersRejectedIgnored) { +TEST_F(Http1ServerConnectionImplTest, LargeTrailersRejectedIgnored) { // Default limit of 60 KiB std::string long_string = "big: " + std::string(60 * 1024, 'q') + "\r\n\r\n\r\n"; testTrailersExceedLimit(long_string, false); } -TEST_P(Http1ServerConnectionImplTest, LargeTrailerFieldRejectedIgnored) { +TEST_F(Http1ServerConnectionImplTest, LargeTrailerFieldRejectedIgnored) { // Default limit of 60 KiB std::string long_string = "bigfield" + std::string(60 * 1024, 'q') + ": value\r\n\r\n\r\n"; testTrailersExceedLimit(long_string, false); } // Tests that the default limit for the number of request headers is 100. -TEST_P(Http1ServerConnectionImplTest, ManyTrailersIgnored) { +TEST_F(Http1ServerConnectionImplTest, ManyTrailersIgnored) { // Send a request with 101 headers. testTrailersExceedLimit(createHeaderFragment(101) + "\r\n\r\n", false); } -TEST_P(Http1ServerConnectionImplTest, LargeRequestUrlRejected) { +TEST_F(Http1ServerConnectionImplTest, LargeRequestUrlRejected) { initialize(); std::string exception_reason; @@ -2587,19 +2532,19 @@ TEST_P(Http1ServerConnectionImplTest, LargeRequestUrlRejected) { EXPECT_EQ("http1.headers_too_large", response_encoder->getStream().responseDetails()); } -TEST_P(Http1ServerConnectionImplTest, LargeRequestHeadersRejected) { +TEST_F(Http1ServerConnectionImplTest, LargeRequestHeadersRejected) { // Default limit of 60 KiB std::string long_string = "big: " + std::string(60 * 1024, 'q') + "\r\n"; testRequestHeadersExceedLimit(long_string, ""); } // Tests that the default limit for the number of request headers is 100. -TEST_P(Http1ServerConnectionImplTest, ManyRequestHeadersRejected) { +TEST_F(Http1ServerConnectionImplTest, ManyRequestHeadersRejected) { // Send a request with 101 headers. testRequestHeadersExceedLimit(createHeaderFragment(101), "http1.too_many_headers"); } -TEST_P(Http1ServerConnectionImplTest, LargeRequestHeadersSplitRejected) { +TEST_F(Http1ServerConnectionImplTest, LargeRequestHeadersSplitRejected) { // Default limit of 60 KiB initialize(); @@ -2630,7 +2575,7 @@ TEST_P(Http1ServerConnectionImplTest, LargeRequestHeadersSplitRejected) { // Tests that the 101th request header causes overflow with the default max number of request // headers. -TEST_P(Http1ServerConnectionImplTest, ManyRequestHeadersSplitRejected) { +TEST_F(Http1ServerConnectionImplTest, ManyRequestHeadersSplitRejected) { // Default limit of 100. initialize(); @@ -2657,27 +2602,27 @@ TEST_P(Http1ServerConnectionImplTest, ManyRequestHeadersSplitRejected) { EXPECT_EQ(status.message(), "headers size exceeds limit"); } -TEST_P(Http1ServerConnectionImplTest, LargeRequestHeadersAccepted) { +TEST_F(Http1ServerConnectionImplTest, LargeRequestHeadersAccepted) { max_request_headers_kb_ = 65; std::string long_string = "big: " + std::string(64 * 1024, 'q') + "\r\n"; testRequestHeadersAccepted(long_string); } -TEST_P(Http1ServerConnectionImplTest, LargeRequestHeadersAcceptedMaxConfigurable) { +TEST_F(Http1ServerConnectionImplTest, LargeRequestHeadersAcceptedMaxConfigurable) { max_request_headers_kb_ = 96; std::string long_string = "big: " + std::string(95 * 1024, 'q') + "\r\n"; testRequestHeadersAccepted(long_string); } // Tests that the number of request headers is configurable. -TEST_P(Http1ServerConnectionImplTest, ManyRequestHeadersAccepted) { +TEST_F(Http1ServerConnectionImplTest, ManyRequestHeadersAccepted) { max_request_headers_count_ = 150; // Create a request with 150 headers. testRequestHeadersAccepted(createHeaderFragment(150)); } // Tests that incomplete response headers of 80 kB header value fails. -TEST_P(Http1ClientConnectionImplTest, ResponseHeadersWithLargeValueRejected) { +TEST_F(Http1ClientConnectionImplTest, ResponseHeadersWithLargeValueRejected) { initialize(); NiceMock response_decoder; @@ -2696,7 +2641,7 @@ TEST_P(Http1ClientConnectionImplTest, ResponseHeadersWithLargeValueRejected) { } // Tests that incomplete response headers with a 80 kB header field fails. -TEST_P(Http1ClientConnectionImplTest, ResponseHeadersWithLargeFieldRejected) { +TEST_F(Http1ClientConnectionImplTest, ResponseHeadersWithLargeFieldRejected) { initialize(); NiceMock decoder; @@ -2716,7 +2661,7 @@ TEST_P(Http1ClientConnectionImplTest, ResponseHeadersWithLargeFieldRejected) { } // Tests that the size of response headers for HTTP/1 must be under 80 kB. -TEST_P(Http1ClientConnectionImplTest, LargeResponseHeadersAccepted) { +TEST_F(Http1ClientConnectionImplTest, LargeResponseHeadersAccepted) { initialize(); NiceMock response_decoder; @@ -2734,7 +2679,7 @@ TEST_P(Http1ClientConnectionImplTest, LargeResponseHeadersAccepted) { // Regression test for CVE-2019-18801. Large method headers should not trigger // ASSERTs or ASAN, which they previously did. -TEST_P(Http1ClientConnectionImplTest, LargeMethodRequestEncode) { +TEST_F(Http1ClientConnectionImplTest, LargeMethodRequestEncode) { initialize(); NiceMock response_decoder; @@ -2752,7 +2697,7 @@ TEST_P(Http1ClientConnectionImplTest, LargeMethodRequestEncode) { // in CVE-2019-18801, but the related code does explicit size calculations on // both path and method (these are the two distinguished headers). So, // belt-and-braces. -TEST_P(Http1ClientConnectionImplTest, LargePathRequestEncode) { +TEST_F(Http1ClientConnectionImplTest, LargePathRequestEncode) { initialize(); NiceMock response_decoder; @@ -2768,7 +2713,7 @@ TEST_P(Http1ClientConnectionImplTest, LargePathRequestEncode) { // As with LargeMethodEncode, but for an arbitrary header. This was not an issue // in CVE-2019-18801. -TEST_P(Http1ClientConnectionImplTest, LargeHeaderRequestEncode) { +TEST_F(Http1ClientConnectionImplTest, LargeHeaderRequestEncode) { initialize(); NiceMock response_decoder; @@ -2785,7 +2730,7 @@ TEST_P(Http1ClientConnectionImplTest, LargeHeaderRequestEncode) { } // Exception called when the number of response headers exceeds the default value of 100. -TEST_P(Http1ClientConnectionImplTest, ManyResponseHeadersRejected) { +TEST_F(Http1ClientConnectionImplTest, ManyResponseHeadersRejected) { initialize(); NiceMock response_decoder; @@ -2803,7 +2748,7 @@ TEST_P(Http1ClientConnectionImplTest, ManyResponseHeadersRejected) { } // Tests that the number of response headers is configurable. -TEST_P(Http1ClientConnectionImplTest, ManyResponseHeadersAccepted) { +TEST_F(Http1ClientConnectionImplTest, ManyResponseHeadersAccepted) { max_response_headers_count_ = 152; initialize(); @@ -2820,5 +2765,6 @@ TEST_P(Http1ClientConnectionImplTest, ManyResponseHeadersAccepted) { status = codec_->dispatch(buffer); } +} // namespace Http1 } // namespace Http } // namespace Envoy diff --git a/test/common/http/http2/BUILD b/test/common/http/http2/BUILD index 9d64120d5dd7..82627e08201d 100644 --- a/test/common/http/http2/BUILD +++ b/test/common/http/http2/BUILD @@ -10,50 +10,35 @@ licenses(["notice"]) # Apache 2 envoy_package() -CODEC_TEST_DEPS = [ - ":codec_impl_test_util", - "//source/common/event:dispatcher_lib", - "//source/common/http:exception_lib", - "//source/common/http:header_map_lib", - "//source/common/http:header_utility_lib", - "//source/common/http/http2:codec_legacy_lib", - "//source/common/http/http2:codec_lib", - "//source/common/runtime:runtime_lib", - "//source/common/stats:stats_lib", - "//test/common/http:common_lib", - "//test/common/http/http2:http2_frame", - "//test/common/stats:stat_test_utility_lib", - "//test/mocks/http:http_mocks", - "//test/mocks/init:init_mocks", - "//test/mocks/local_info:local_info_mocks", - "//test/mocks/network:network_mocks", - "//test/mocks/protobuf:protobuf_mocks", - "//test/mocks/thread_local:thread_local_mocks", - "//test/mocks/upstream:transport_socket_match_mocks", - "//test/mocks/upstream:upstream_mocks", - "//test/test_common:logging_lib", - "//test/test_common:registry_lib", - "//test/test_common:test_runtime_lib", - "//test/test_common:utility_lib", -] - envoy_cc_test( name = "codec_impl_test", srcs = ["codec_impl_test.cc"], shard_count = 5, tags = ["fails_on_windows"], - deps = CODEC_TEST_DEPS, -) - -envoy_cc_test( - name = "codec_impl_legacy_test", - srcs = ["codec_impl_test.cc"], - args = [ - "--runtime-feature-disable-for-tests=envoy.reloadable_features.new_codec_behavior", + deps = [ + ":codec_impl_test_util", + "//source/common/event:dispatcher_lib", + "//source/common/http:exception_lib", + "//source/common/http:header_map_lib", + "//source/common/http:header_utility_lib", + "//source/common/http/http2:codec_lib", + "//source/common/stats:stats_lib", + "//test/common/http:common_lib", + "//test/common/http/http2:http2_frame", + "//test/common/stats:stat_test_utility_lib", + "//test/mocks/http:http_mocks", + "//test/mocks/init:init_mocks", + "//test/mocks/local_info:local_info_mocks", + "//test/mocks/network:network_mocks", + "//test/mocks/protobuf:protobuf_mocks", + "//test/mocks/thread_local:thread_local_mocks", + "//test/mocks/upstream:transport_socket_match_mocks", + "//test/mocks/upstream:upstream_mocks", + "//test/test_common:logging_lib", + "//test/test_common:registry_lib", + "//test/test_common:test_runtime_lib", + "//test/test_common:utility_lib", ], - shard_count = 5, - tags = ["fails_on_windows"], - deps = CODEC_TEST_DEPS, ) envoy_cc_test_library( @@ -61,7 +46,6 @@ envoy_cc_test_library( hdrs = ["codec_impl_test_util.h"], external_deps = ["abseil_optional"], deps = [ - "//source/common/http/http2:codec_legacy_lib", "//source/common/http/http2:codec_lib", ], ) diff --git a/test/common/http/http2/codec_impl_legacy_test.cc b/test/common/http/http2/codec_impl_legacy_test.cc deleted file mode 100644 index fef22ab9c492..000000000000 --- a/test/common/http/http2/codec_impl_legacy_test.cc +++ /dev/null @@ -1,2163 +0,0 @@ -#include -#include - -#include "envoy/http/codec.h" -#include "envoy/stats/scope.h" - -#include "common/http/exception.h" -#include "common/http/header_map_impl.h" -#include "common/http/http2/codec_impl_legacy.h" - -#include "test/common/http/common.h" -#include "test/common/http/http2/http2_frame.h" -#include "test/common/stats/stat_test_utility.h" -#include "test/mocks/http/mocks.h" -#include "test/mocks/init/mocks.h" -#include "test/mocks/local_info/mocks.h" -#include "test/mocks/network/mocks.h" -#include "test/mocks/protobuf/mocks.h" -#include "test/mocks/thread_local/mocks.h" -#include "test/test_common/logging.h" -#include "test/test_common/printers.h" -#include "test/test_common/registry.h" -#include "test/test_common/test_runtime.h" -#include "test/test_common/utility.h" - -#include "codec_impl_legacy_test_util.h" -#include "gmock/gmock.h" -#include "gtest/gtest.h" - -using testing::_; -using testing::AnyNumber; -using testing::AtLeast; -using testing::InSequence; -using testing::Invoke; -using testing::InvokeWithoutArgs; -using testing::NiceMock; -using testing::Return; - -namespace Envoy { -namespace Http { -namespace Legacy { -namespace Http2 { - -using Http2SettingsTuple = ::testing::tuple; -using Http2SettingsTestParam = ::testing::tuple; -using Http::Http2::Http2Frame; -namespace CommonUtility = ::Envoy::Http2::Utility; - -class Http2CodecImplTestFixture { -public: - // The Http::Connection::dispatch method does not throw (any more). However unit tests in this - // file use codecs for sending test data through mock network connections to the codec under test. - // It is infeasible to plumb error codes returned by the dispatch() method of the codecs under - // test, through mock connections and sending codec. As a result error returned by the dispatch - // method of the codec under test invoked by the ConnectionWrapper is thrown as an exception. Note - // that exception goes only through the mock network connection and sending codec, i.e. it is - // thrown only through the test harness code. Specific exception types are to distinguish error - // codes returned when processing requests or responses. - // TODO(yanavlasov): modify the code to verify test expectations at the point of calling codec - // under test through the ON_CALL expectations in the - // setupDefaultConnectionMocks() method. This will make the exceptions below - // unnecessary. - struct ClientCodecError : public std::runtime_error { - ClientCodecError(Http::Status&& status) - : std::runtime_error(std::string(status.message())), status_(std::move(status)) {} - const char* what() const noexcept override { return status_.message().data(); } - const Http::Status status_; - }; - - struct ServerCodecError : public std::runtime_error { - ServerCodecError(Http::Status&& status) - : std::runtime_error(std::string(status.message())), status_(std::move(status)) {} - const char* what() const noexcept override { return status_.message().data(); } - const Http::Status status_; - }; - - struct ConnectionWrapper { - Http::Status dispatch(const Buffer::Instance& data, ConnectionImpl& connection) { - Http::Status status = Http::okStatus(); - buffer_.add(data); - if (!dispatching_) { - while (buffer_.length() > 0) { - dispatching_ = true; - status = connection.dispatch(buffer_); - if (!status.ok()) { - // Exit early if we hit an error status. - return status; - } - dispatching_ = false; - } - } - return status; - } - - bool dispatching_{}; - Buffer::OwnedImpl buffer_; - }; - - enum SettingsTupleIndex { - HpackTableSize = 0, - MaxConcurrentStreams, - InitialStreamWindowSize, - InitialConnectionWindowSize - }; - - Http2CodecImplTestFixture() = default; - Http2CodecImplTestFixture(Http2SettingsTuple client_settings, Http2SettingsTuple server_settings) - : client_settings_(client_settings), server_settings_(server_settings) { - // Make sure we explicitly test for stream flush timer creation. - EXPECT_CALL(client_connection_.dispatcher_, createTimer_(_)).Times(0); - EXPECT_CALL(server_connection_.dispatcher_, createTimer_(_)).Times(0); - } - virtual ~Http2CodecImplTestFixture() { - client_connection_.dispatcher_.clearDeferredDeleteList(); - if (client_ != nullptr) { - client_.reset(); - EXPECT_EQ(0, TestUtility::findGauge(client_stats_store_, "http2.streams_active")->value()); - EXPECT_EQ(0, - TestUtility::findGauge(client_stats_store_, "http2.pending_send_bytes")->value()); - } - server_connection_.dispatcher_.clearDeferredDeleteList(); - if (server_ != nullptr) { - server_.reset(); - EXPECT_EQ(0, TestUtility::findGauge(server_stats_store_, "http2.streams_active")->value()); - EXPECT_EQ(0, - TestUtility::findGauge(server_stats_store_, "http2.pending_send_bytes")->value()); - } - } - - virtual void initialize() { - http2OptionsFromTuple(client_http2_options_, client_settings_); - http2OptionsFromTuple(server_http2_options_, server_settings_); - client_ = std::make_unique( - client_connection_, client_callbacks_, client_stats_store_, client_http2_options_, - max_request_headers_kb_, max_response_headers_count_, ProdNghttp2SessionFactory::get()); - server_ = std::make_unique( - server_connection_, server_callbacks_, server_stats_store_, server_http2_options_, - max_request_headers_kb_, max_request_headers_count_, headers_with_underscores_action_); - - request_encoder_ = &client_->newStream(response_decoder_); - setupDefaultConnectionMocks(); - - EXPECT_CALL(server_callbacks_, newStream(_, _)) - .WillRepeatedly(Invoke([&](ResponseEncoder& encoder, bool) -> RequestDecoder& { - response_encoder_ = &encoder; - encoder.getStream().addCallbacks(server_stream_callbacks_); - encoder.getStream().setFlushTimeout(std::chrono::milliseconds(30000)); - return request_decoder_; - })); - } - - void setupDefaultConnectionMocks() { - ON_CALL(client_connection_, write(_, _)) - .WillByDefault(Invoke([&](Buffer::Instance& data, bool) -> void { - if (corrupt_metadata_frame_) { - corruptMetadataFramePayload(data); - } - auto status = server_wrapper_.dispatch(data, *server_); - if (!status.ok()) { - throw ServerCodecError(std::move(status)); - } - })); - ON_CALL(server_connection_, write(_, _)) - .WillByDefault(Invoke([&](Buffer::Instance& data, bool) -> void { - auto status = client_wrapper_.dispatch(data, *client_); - if (!status.ok()) { - throw ClientCodecError(std::move(status)); - } - })); - } - - void http2OptionsFromTuple(envoy::config::core::v3::Http2ProtocolOptions& options, - const absl::optional& tp) { - options.mutable_hpack_table_size()->set_value( - (tp.has_value()) ? ::testing::get(*tp) - : CommonUtility::OptionsLimits::DEFAULT_HPACK_TABLE_SIZE); - options.mutable_max_concurrent_streams()->set_value( - (tp.has_value()) ? ::testing::get(*tp) - : CommonUtility::OptionsLimits::DEFAULT_MAX_CONCURRENT_STREAMS); - options.mutable_initial_stream_window_size()->set_value( - (tp.has_value()) ? ::testing::get(*tp) - : CommonUtility::OptionsLimits::DEFAULT_INITIAL_STREAM_WINDOW_SIZE); - options.mutable_initial_connection_window_size()->set_value( - (tp.has_value()) ? ::testing::get(*tp) - : CommonUtility::OptionsLimits::DEFAULT_INITIAL_CONNECTION_WINDOW_SIZE); - options.set_allow_metadata(allow_metadata_); - options.set_stream_error_on_invalid_http_messaging(stream_error_on_invalid_http_messaging_); - options.mutable_max_outbound_frames()->set_value(max_outbound_frames_); - options.mutable_max_outbound_control_frames()->set_value(max_outbound_control_frames_); - options.mutable_max_consecutive_inbound_frames_with_empty_payload()->set_value( - max_consecutive_inbound_frames_with_empty_payload_); - options.mutable_max_inbound_priority_frames_per_stream()->set_value( - max_inbound_priority_frames_per_stream_); - options.mutable_max_inbound_window_update_frames_per_data_frame_sent()->set_value( - max_inbound_window_update_frames_per_data_frame_sent_); - } - - // corruptMetadataFramePayload assumes data contains at least 10 bytes of the beginning of a - // frame. - void corruptMetadataFramePayload(Buffer::Instance& data) { - const size_t length = data.length(); - const size_t corrupt_start = 10; - if (length < corrupt_start || length > METADATA_MAX_PAYLOAD_SIZE) { - ENVOY_LOG_MISC(error, "data size too big or too small"); - return; - } - corruptAtOffset(data, corrupt_start, 0xff); - } - - void corruptAtOffset(Buffer::Instance& data, size_t index, char new_value) { - if (data.length() == 0) { - return; - } - reinterpret_cast(data.linearize(data.length()))[index % data.length()] = new_value; - } - - void expectDetailsRequest(const absl::string_view details) { - EXPECT_EQ(details, request_encoder_->getStream().responseDetails()); - } - - void expectDetailsResponse(const absl::string_view details) { - EXPECT_EQ(details, response_encoder_->getStream().responseDetails()); - } - - absl::optional client_settings_; - absl::optional server_settings_; - bool allow_metadata_ = false; - bool stream_error_on_invalid_http_messaging_ = false; - Stats::TestUtil::TestStore client_stats_store_; - envoy::config::core::v3::Http2ProtocolOptions client_http2_options_; - NiceMock client_connection_; - MockConnectionCallbacks client_callbacks_; - std::unique_ptr client_; - ConnectionWrapper client_wrapper_; - Stats::TestUtil::TestStore server_stats_store_; - envoy::config::core::v3::Http2ProtocolOptions server_http2_options_; - NiceMock server_connection_; - MockServerConnectionCallbacks server_callbacks_; - std::unique_ptr server_; - ConnectionWrapper server_wrapper_; - MockResponseDecoder response_decoder_; - RequestEncoder* request_encoder_; - MockRequestDecoder request_decoder_; - ResponseEncoder* response_encoder_{}; - MockStreamCallbacks server_stream_callbacks_; - // Corrupt a metadata frame payload. - bool corrupt_metadata_frame_ = false; - - uint32_t max_request_headers_kb_ = Http::DEFAULT_MAX_REQUEST_HEADERS_KB; - uint32_t max_request_headers_count_ = Http::DEFAULT_MAX_HEADERS_COUNT; - uint32_t max_response_headers_count_ = Http::DEFAULT_MAX_HEADERS_COUNT; - uint32_t max_outbound_frames_ = CommonUtility::OptionsLimits::DEFAULT_MAX_OUTBOUND_FRAMES; - uint32_t max_outbound_control_frames_ = - CommonUtility::OptionsLimits::DEFAULT_MAX_OUTBOUND_CONTROL_FRAMES; - uint32_t max_consecutive_inbound_frames_with_empty_payload_ = - CommonUtility::OptionsLimits::DEFAULT_MAX_CONSECUTIVE_INBOUND_FRAMES_WITH_EMPTY_PAYLOAD; - uint32_t max_inbound_priority_frames_per_stream_ = - CommonUtility::OptionsLimits::DEFAULT_MAX_INBOUND_PRIORITY_FRAMES_PER_STREAM; - uint32_t max_inbound_window_update_frames_per_data_frame_sent_ = - CommonUtility::OptionsLimits::DEFAULT_MAX_INBOUND_WINDOW_UPDATE_FRAMES_PER_DATA_FRAME_SENT; - envoy::config::core::v3::HttpProtocolOptions::HeadersWithUnderscoresAction - headers_with_underscores_action_{envoy::config::core::v3::HttpProtocolOptions::ALLOW}; -}; - -class Http2CodecImplTest : public ::testing::TestWithParam, - protected Http2CodecImplTestFixture { -public: - Http2CodecImplTest() - : Http2CodecImplTestFixture(::testing::get<0>(GetParam()), ::testing::get<1>(GetParam())) {} - -protected: - void priorityFlood() { - initialize(); - - TestRequestHeaderMapImpl request_headers; - HttpTestUtility::addDefaultHeaders(request_headers, "POST"); - EXPECT_CALL(request_decoder_, decodeHeaders_(_, false)); - request_encoder_->encodeHeaders(request_headers, false); - - nghttp2_priority_spec spec = {0, 10, 0}; - // HTTP/2 codec adds 1 to the number of active streams when computing PRIORITY frames limit - constexpr uint32_t max_allowed = - 2 * CommonUtility::OptionsLimits::DEFAULT_MAX_INBOUND_PRIORITY_FRAMES_PER_STREAM; - for (uint32_t i = 0; i < max_allowed + 1; ++i) { - EXPECT_EQ(0, nghttp2_submit_priority(client_->session(), NGHTTP2_FLAG_NONE, 1, &spec)); - } - } - - void windowUpdateFlood() { - initialize(); - - TestRequestHeaderMapImpl request_headers; - HttpTestUtility::addDefaultHeaders(request_headers); - EXPECT_CALL(request_decoder_, decodeHeaders_(_, true)); - request_encoder_->encodeHeaders(request_headers, true); - - // Send one DATA frame back - EXPECT_CALL(response_decoder_, decodeHeaders_(_, false)); - EXPECT_CALL(response_decoder_, decodeData(_, false)); - TestResponseHeaderMapImpl response_headers{{":status", "200"}}; - response_encoder_->encodeHeaders(response_headers, false); - Buffer::OwnedImpl data("0"); - EXPECT_NO_THROW(response_encoder_->encodeData(data, false)); - - // See the limit formula in the - // `Envoy::Http::Http2::ServerConnectionImpl::checkInboundFrameLimits()' method. - constexpr uint32_t max_allowed = - 1 + 2 * (CommonUtility::OptionsLimits:: - DEFAULT_MAX_INBOUND_WINDOW_UPDATE_FRAMES_PER_DATA_FRAME_SENT + - 1); - for (uint32_t i = 0; i < max_allowed + 1; ++i) { - EXPECT_EQ(0, nghttp2_submit_window_update(client_->session(), NGHTTP2_FLAG_NONE, 1, 1)); - } - } - - void emptyDataFlood(Buffer::OwnedImpl& data) { - initialize(); - - TestRequestHeaderMapImpl request_headers; - HttpTestUtility::addDefaultHeaders(request_headers, "POST"); - EXPECT_CALL(request_decoder_, decodeHeaders_(_, false)); - request_encoder_->encodeHeaders(request_headers, false); - - // HTTP/2 codec does not send empty DATA frames with no END_STREAM flag. - // To make this work, send raw bytes representing empty DATA frames bypassing client codec. - Http2Frame emptyDataFrame = Http2Frame::makeEmptyDataFrame(0); - constexpr uint32_t max_allowed = - CommonUtility::OptionsLimits::DEFAULT_MAX_CONSECUTIVE_INBOUND_FRAMES_WITH_EMPTY_PAYLOAD; - for (uint32_t i = 0; i < max_allowed + 1; ++i) { - data.add(emptyDataFrame.data(), emptyDataFrame.size()); - } - } -}; - -TEST_P(Http2CodecImplTest, ShutdownNotice) { - initialize(); - EXPECT_EQ(absl::nullopt, request_encoder_->http1StreamEncoderOptions()); - - TestRequestHeaderMapImpl request_headers; - HttpTestUtility::addDefaultHeaders(request_headers); - EXPECT_CALL(request_decoder_, decodeHeaders_(_, true)); - request_encoder_->encodeHeaders(request_headers, true); - - EXPECT_CALL(client_callbacks_, onGoAway(_)); - server_->shutdownNotice(); - server_->goAway(); - - TestResponseHeaderMapImpl response_headers{{":status", "200"}}; - EXPECT_CALL(response_decoder_, decodeHeaders_(_, true)); - response_encoder_->encodeHeaders(response_headers, true); -} - -TEST_P(Http2CodecImplTest, ContinueHeaders) { - initialize(); - - TestRequestHeaderMapImpl request_headers; - HttpTestUtility::addDefaultHeaders(request_headers); - EXPECT_CALL(request_decoder_, decodeHeaders_(_, true)); - request_encoder_->encodeHeaders(request_headers, true); - - TestResponseHeaderMapImpl continue_headers{{":status", "100"}}; - EXPECT_CALL(response_decoder_, decode100ContinueHeaders_(_)); - response_encoder_->encode100ContinueHeaders(continue_headers); - - TestResponseHeaderMapImpl response_headers{{":status", "200"}}; - EXPECT_CALL(response_decoder_, decodeHeaders_(_, true)); - response_encoder_->encodeHeaders(response_headers, true); -}; - -TEST_P(Http2CodecImplTest, InvalidContinueWithFin) { - initialize(); - - TestRequestHeaderMapImpl request_headers; - HttpTestUtility::addDefaultHeaders(request_headers); - EXPECT_CALL(request_decoder_, decodeHeaders_(_, true)); - request_encoder_->encodeHeaders(request_headers, true); - - TestResponseHeaderMapImpl continue_headers{{":status", "100"}}; - EXPECT_THROW(response_encoder_->encodeHeaders(continue_headers, true), ClientCodecError); - EXPECT_EQ(1, client_stats_store_.counter("http2.rx_messaging_error").value()); -} - -TEST_P(Http2CodecImplTest, InvalidContinueWithFinAllowed) { - stream_error_on_invalid_http_messaging_ = true; - initialize(); - - MockStreamCallbacks request_callbacks; - request_encoder_->getStream().addCallbacks(request_callbacks); - - TestRequestHeaderMapImpl request_headers; - HttpTestUtility::addDefaultHeaders(request_headers); - EXPECT_CALL(request_decoder_, decodeHeaders_(_, true)); - request_encoder_->encodeHeaders(request_headers, true); - - // Buffer client data to avoid mock recursion causing lifetime issues. - ON_CALL(server_connection_, write(_, _)) - .WillByDefault( - Invoke([&](Buffer::Instance& data, bool) -> void { client_wrapper_.buffer_.add(data); })); - - TestResponseHeaderMapImpl continue_headers{{":status", "100"}}; - response_encoder_->encodeHeaders(continue_headers, true); - - // Flush pending data. - EXPECT_CALL(request_callbacks, onResetStream(StreamResetReason::LocalReset, _)); - setupDefaultConnectionMocks(); - auto status = client_wrapper_.dispatch(Buffer::OwnedImpl(), *client_); - EXPECT_TRUE(status.ok()); - - EXPECT_EQ(1, client_stats_store_.counter("http2.rx_messaging_error").value()); - expectDetailsRequest("http2.violation.of.messaging.rule"); -} - -TEST_P(Http2CodecImplTest, InvalidRepeatContinue) { - initialize(); - - TestRequestHeaderMapImpl request_headers; - HttpTestUtility::addDefaultHeaders(request_headers); - EXPECT_CALL(request_decoder_, decodeHeaders_(_, true)); - request_encoder_->encodeHeaders(request_headers, true); - - TestResponseHeaderMapImpl continue_headers{{":status", "100"}}; - EXPECT_CALL(response_decoder_, decode100ContinueHeaders_(_)); - response_encoder_->encode100ContinueHeaders(continue_headers); - - EXPECT_THROW(response_encoder_->encodeHeaders(continue_headers, true), ClientCodecError); - EXPECT_EQ(1, client_stats_store_.counter("http2.rx_messaging_error").value()); -}; - -TEST_P(Http2CodecImplTest, InvalidRepeatContinueAllowed) { - stream_error_on_invalid_http_messaging_ = true; - initialize(); - - MockStreamCallbacks request_callbacks; - request_encoder_->getStream().addCallbacks(request_callbacks); - - TestRequestHeaderMapImpl request_headers; - HttpTestUtility::addDefaultHeaders(request_headers); - EXPECT_CALL(request_decoder_, decodeHeaders_(_, true)); - request_encoder_->encodeHeaders(request_headers, true); - - TestResponseHeaderMapImpl continue_headers{{":status", "100"}}; - EXPECT_CALL(response_decoder_, decode100ContinueHeaders_(_)); - response_encoder_->encode100ContinueHeaders(continue_headers); - - // Buffer client data to avoid mock recursion causing lifetime issues. - ON_CALL(server_connection_, write(_, _)) - .WillByDefault( - Invoke([&](Buffer::Instance& data, bool) -> void { client_wrapper_.buffer_.add(data); })); - - response_encoder_->encodeHeaders(continue_headers, true); - - // Flush pending data. - EXPECT_CALL(request_callbacks, onResetStream(StreamResetReason::LocalReset, _)); - setupDefaultConnectionMocks(); - auto status = client_wrapper_.dispatch(Buffer::OwnedImpl(), *client_); - EXPECT_TRUE(status.ok()); - - EXPECT_EQ(1, client_stats_store_.counter("http2.rx_messaging_error").value()); - expectDetailsRequest("http2.violation.of.messaging.rule"); -}; - -TEST_P(Http2CodecImplTest, Invalid103) { - initialize(); - - TestRequestHeaderMapImpl request_headers; - HttpTestUtility::addDefaultHeaders(request_headers); - EXPECT_CALL(request_decoder_, decodeHeaders_(_, true)); - request_encoder_->encodeHeaders(request_headers, true); - - TestResponseHeaderMapImpl continue_headers{{":status", "100"}}; - EXPECT_CALL(response_decoder_, decode100ContinueHeaders_(_)); - response_encoder_->encode100ContinueHeaders(continue_headers); - - TestResponseHeaderMapImpl early_hint_headers{{":status", "103"}}; - EXPECT_CALL(response_decoder_, decodeHeaders_(_, false)); - response_encoder_->encodeHeaders(early_hint_headers, false); - - EXPECT_THROW_WITH_MESSAGE(response_encoder_->encodeHeaders(early_hint_headers, false), - ClientCodecError, "Unexpected 'trailers' with no end stream."); - EXPECT_EQ(1, client_stats_store_.counter("http2.too_many_header_frames").value()); -} - -TEST_P(Http2CodecImplTest, Invalid204WithContentLength) { - initialize(); - - TestRequestHeaderMapImpl request_headers; - HttpTestUtility::addDefaultHeaders(request_headers); - EXPECT_CALL(request_decoder_, decodeHeaders_(_, true)); - request_encoder_->encodeHeaders(request_headers, true); - - TestResponseHeaderMapImpl response_headers{{":status", "204"}, {"content-length", "3"}}; - // What follows is a hack to get headers that should span into continuation frames. The default - // maximum frame size is 16K. We will add 3,000 headers that will take us above this size and - // not easily compress with HPACK. (I confirmed this generates 26,468 bytes of header data - // which should contain a continuation.) - for (unsigned i = 1; i < 3000; i++) { - response_headers.addCopy(std::to_string(i), std::to_string(i)); - } - - EXPECT_LOG_CONTAINS( - "debug", - "Invalid HTTP header field was received: frame type: 1, stream: 1, name: [content-length], " - "value: [3]", - EXPECT_THROW(response_encoder_->encodeHeaders(response_headers, false), ClientCodecError)); - EXPECT_EQ(1, client_stats_store_.counter("http2.rx_messaging_error").value()); -}; - -TEST_P(Http2CodecImplTest, Invalid204WithContentLengthAllowed) { - stream_error_on_invalid_http_messaging_ = true; - initialize(); - - MockStreamCallbacks request_callbacks; - request_encoder_->getStream().addCallbacks(request_callbacks); - - TestRequestHeaderMapImpl request_headers; - HttpTestUtility::addDefaultHeaders(request_headers); - EXPECT_CALL(request_decoder_, decodeHeaders_(_, true)); - request_encoder_->encodeHeaders(request_headers, true); - - // Buffer client data to avoid mock recursion causing lifetime issues. - ON_CALL(server_connection_, write(_, _)) - .WillByDefault( - Invoke([&](Buffer::Instance& data, bool) -> void { client_wrapper_.buffer_.add(data); })); - - TestResponseHeaderMapImpl response_headers{{":status", "204"}, {"content-length", "3"}}; - // What follows is a hack to get headers that should span into continuation frames. The default - // maximum frame size is 16K. We will add 3,000 headers that will take us above this size and - // not easily compress with HPACK. (I confirmed this generates 26,468 bytes of header data - // which should contain a continuation.) - for (int i = 1; i < 3000; i++) { - response_headers.addCopy(std::to_string(i), std::to_string(i)); - } - - response_encoder_->encodeHeaders(response_headers, false); - - // Flush pending data. - EXPECT_CALL(request_callbacks, onResetStream(StreamResetReason::LocalReset, _)); - EXPECT_CALL(server_stream_callbacks_, onResetStream(StreamResetReason::RemoteReset, _)); - setupDefaultConnectionMocks(); - auto status = client_wrapper_.dispatch(Buffer::OwnedImpl(), *client_); - EXPECT_TRUE(status.ok()); - - EXPECT_EQ(1, client_stats_store_.counter("http2.rx_messaging_error").value()); - expectDetailsRequest("http2.invalid.header.field"); -}; - -TEST_P(Http2CodecImplTest, RefusedStreamReset) { - initialize(); - - TestRequestHeaderMapImpl request_headers; - HttpTestUtility::addDefaultHeaders(request_headers); - EXPECT_CALL(request_decoder_, decodeHeaders_(_, false)); - request_encoder_->encodeHeaders(request_headers, false); - - MockStreamCallbacks callbacks; - request_encoder_->getStream().addCallbacks(callbacks); - EXPECT_CALL(server_stream_callbacks_, - onResetStream(StreamResetReason::LocalRefusedStreamReset, _)); - EXPECT_CALL(callbacks, onResetStream(StreamResetReason::RemoteRefusedStreamReset, _)); - response_encoder_->getStream().resetStream(StreamResetReason::LocalRefusedStreamReset); -} - -TEST_P(Http2CodecImplTest, InvalidHeadersFrame) { - initialize(); - - EXPECT_THROW(request_encoder_->encodeHeaders(TestRequestHeaderMapImpl{}, true), ServerCodecError); - EXPECT_EQ(1, server_stats_store_.counter("http2.rx_messaging_error").value()); -} - -TEST_P(Http2CodecImplTest, InvalidHeadersFrameAllowed) { - stream_error_on_invalid_http_messaging_ = true; - initialize(); - - MockStreamCallbacks request_callbacks; - request_encoder_->getStream().addCallbacks(request_callbacks); - - ON_CALL(client_connection_, write(_, _)) - .WillByDefault( - Invoke([&](Buffer::Instance& data, bool) -> void { server_wrapper_.buffer_.add(data); })); - - request_encoder_->encodeHeaders(TestRequestHeaderMapImpl{}, true); - EXPECT_CALL(server_stream_callbacks_, onResetStream(StreamResetReason::LocalReset, _)); - EXPECT_CALL(request_callbacks, onResetStream(StreamResetReason::RemoteReset, _)); - auto status = server_wrapper_.dispatch(Buffer::OwnedImpl(), *server_); - EXPECT_TRUE(status.ok()); - expectDetailsResponse("http2.violation.of.messaging.rule"); -} - -TEST_P(Http2CodecImplTest, TrailingHeaders) { - initialize(); - - TestRequestHeaderMapImpl request_headers; - HttpTestUtility::addDefaultHeaders(request_headers); - EXPECT_CALL(request_decoder_, decodeHeaders_(_, false)); - request_encoder_->encodeHeaders(request_headers, false); - EXPECT_CALL(request_decoder_, decodeData(_, false)); - Buffer::OwnedImpl hello("hello"); - request_encoder_->encodeData(hello, false); - EXPECT_CALL(request_decoder_, decodeTrailers_(_)); - request_encoder_->encodeTrailers(TestRequestTrailerMapImpl{{"trailing", "header"}}); - - TestResponseHeaderMapImpl response_headers{{":status", "200"}}; - EXPECT_CALL(response_decoder_, decodeHeaders_(_, false)); - response_encoder_->encodeHeaders(response_headers, false); - EXPECT_CALL(response_decoder_, decodeData(_, false)); - Buffer::OwnedImpl world("world"); - response_encoder_->encodeData(world, false); - EXPECT_CALL(response_decoder_, decodeTrailers_(_)); - response_encoder_->encodeTrailers(TestResponseTrailerMapImpl{{"trailing", "header"}}); -} - -TEST_P(Http2CodecImplTest, TrailingHeadersLargeClientBody) { - initialize(); - - // Buffer server data so we can make sure we don't get any window updates. - ON_CALL(client_connection_, write(_, _)) - .WillByDefault( - Invoke([&](Buffer::Instance& data, bool) -> void { server_wrapper_.buffer_.add(data); })); - - TestRequestHeaderMapImpl request_headers; - HttpTestUtility::addDefaultHeaders(request_headers); - EXPECT_CALL(request_decoder_, decodeHeaders_(_, false)); - request_encoder_->encodeHeaders(request_headers, false); - EXPECT_CALL(request_decoder_, decodeData(_, false)).Times(AtLeast(1)); - Buffer::OwnedImpl body(std::string(1024 * 1024, 'a')); - request_encoder_->encodeData(body, false); - request_encoder_->encodeTrailers(TestRequestTrailerMapImpl{{"trailing", "header"}}); - - // Flush pending data. - setupDefaultConnectionMocks(); - EXPECT_CALL(request_decoder_, decodeTrailers_(_)); - auto status = server_wrapper_.dispatch(Buffer::OwnedImpl(), *server_); - EXPECT_TRUE(status.ok()); - - TestResponseHeaderMapImpl response_headers{{":status", "200"}}; - EXPECT_CALL(response_decoder_, decodeHeaders_(_, false)); - response_encoder_->encodeHeaders(response_headers, false); - EXPECT_CALL(response_decoder_, decodeData(_, false)); - Buffer::OwnedImpl world("world"); - response_encoder_->encodeData(world, false); - EXPECT_CALL(response_decoder_, decodeTrailers_(_)); - response_encoder_->encodeTrailers(TestResponseTrailerMapImpl{{"trailing", "header"}}); -} - -TEST_P(Http2CodecImplTest, SmallMetadataVecTest) { - allow_metadata_ = true; - initialize(); - - // Generates a valid stream_id by sending a request header. - TestRequestHeaderMapImpl request_headers; - HttpTestUtility::addDefaultHeaders(request_headers); - EXPECT_CALL(request_decoder_, decodeHeaders_(_, true)); - request_encoder_->encodeHeaders(request_headers, true); - - MetadataMapVector metadata_map_vector; - const int size = 10; - for (int i = 0; i < size; i++) { - MetadataMap metadata_map = { - {"header_key1", "header_value1"}, - {"header_key2", "header_value2"}, - {"header_key3", "header_value3"}, - {"header_key4", "header_value4"}, - }; - MetadataMapPtr metadata_map_ptr = std::make_unique(metadata_map); - metadata_map_vector.push_back(std::move(metadata_map_ptr)); - } - - EXPECT_CALL(request_decoder_, decodeMetadata_(_)).Times(size); - request_encoder_->encodeMetadata(metadata_map_vector); - - EXPECT_CALL(response_decoder_, decodeMetadata_(_)).Times(size); - response_encoder_->encodeMetadata(metadata_map_vector); -} - -TEST_P(Http2CodecImplTest, LargeMetadataVecTest) { - allow_metadata_ = true; - initialize(); - - // Generates a valid stream_id by sending a request header. - TestRequestHeaderMapImpl request_headers; - HttpTestUtility::addDefaultHeaders(request_headers); - EXPECT_CALL(request_decoder_, decodeHeaders_(_, true)); - request_encoder_->encodeHeaders(request_headers, true); - - MetadataMapVector metadata_map_vector; - const int size = 10; - for (int i = 0; i < size; i++) { - MetadataMap metadata_map = { - {"header_key1", std::string(50 * 1024, 'a')}, - }; - MetadataMapPtr metadata_map_ptr = std::make_unique(metadata_map); - metadata_map_vector.push_back(std::move(metadata_map_ptr)); - } - - EXPECT_CALL(request_decoder_, decodeMetadata_(_)).Times(size); - request_encoder_->encodeMetadata(metadata_map_vector); - - EXPECT_CALL(response_decoder_, decodeMetadata_(_)).Times(size); - response_encoder_->encodeMetadata(metadata_map_vector); -} - -TEST_P(Http2CodecImplTest, BadMetadataVecReceivedTest) { - allow_metadata_ = true; - initialize(); - - // Generates a valid stream_id by sending a request header. - TestRequestHeaderMapImpl request_headers; - HttpTestUtility::addDefaultHeaders(request_headers); - EXPECT_CALL(request_decoder_, decodeHeaders_(_, true)); - request_encoder_->encodeHeaders(request_headers, true); - - MetadataMap metadata_map = { - {"header_key1", "header_value1"}, - {"header_key2", "header_value2"}, - {"header_key3", "header_value3"}, - {"header_key4", "header_value4"}, - }; - MetadataMapPtr metadata_map_ptr = std::make_unique(metadata_map); - MetadataMapVector metadata_map_vector; - metadata_map_vector.push_back(std::move(metadata_map_ptr)); - - corrupt_metadata_frame_ = true; - EXPECT_THROW_WITH_MESSAGE(request_encoder_->encodeMetadata(metadata_map_vector), ServerCodecError, - "The user callback function failed"); -} - -// Encode response metadata while dispatching request data from the client, so -// that nghttp2 can't fill the metadata frames' payloads until dispatching -// is finished. -TEST_P(Http2CodecImplTest, EncodeMetadataWhileDispatchingTest) { - allow_metadata_ = true; - initialize(); - - MetadataMapVector metadata_map_vector; - const int size = 10; - for (int i = 0; i < size; i++) { - MetadataMap metadata_map = { - {"header_key1", "header_value1"}, - {"header_key2", "header_value2"}, - {"header_key3", "header_value3"}, - {"header_key4", "header_value4"}, - }; - MetadataMapPtr metadata_map_ptr = std::make_unique(metadata_map); - metadata_map_vector.push_back(std::move(metadata_map_ptr)); - } - - TestRequestHeaderMapImpl request_headers; - HttpTestUtility::addDefaultHeaders(request_headers); - EXPECT_CALL(request_decoder_, decodeHeaders_(_, true)).WillOnce(InvokeWithoutArgs([&]() -> void { - response_encoder_->encodeMetadata(metadata_map_vector); - })); - EXPECT_CALL(response_decoder_, decodeMetadata_(_)).Times(size); - request_encoder_->encodeHeaders(request_headers, true); -} -class Http2CodecImplDeferredResetTest : public Http2CodecImplTest {}; - -TEST_P(Http2CodecImplDeferredResetTest, DeferredResetClient) { - initialize(); - - InSequence s; - - MockStreamCallbacks client_stream_callbacks; - request_encoder_->getStream().addCallbacks(client_stream_callbacks); - - // Do a request, but pause server dispatch so we don't send window updates. This will result in a - // deferred reset, followed by a pending frames flush which will cause the stream to actually - // be reset immediately since we are outside of dispatch context. - ON_CALL(client_connection_, write(_, _)) - .WillByDefault( - Invoke([&](Buffer::Instance& data, bool) -> void { server_wrapper_.buffer_.add(data); })); - TestRequestHeaderMapImpl request_headers; - HttpTestUtility::addDefaultHeaders(request_headers); - request_encoder_->encodeHeaders(request_headers, false); - Buffer::OwnedImpl body(std::string(1024 * 1024, 'a')); - EXPECT_CALL(client_stream_callbacks, onAboveWriteBufferHighWatermark()).Times(AnyNumber()); - request_encoder_->encodeData(body, true); - EXPECT_CALL(client_stream_callbacks, onResetStream(StreamResetReason::LocalReset, _)); - request_encoder_->getStream().resetStream(StreamResetReason::LocalReset); - - // Dispatch server. We expect to see some data. - EXPECT_CALL(response_decoder_, decodeHeaders_(_, _)).Times(0); - EXPECT_CALL(request_decoder_, decodeHeaders_(_, false)).WillOnce(InvokeWithoutArgs([&]() -> void { - // Start a response inside the headers callback. This should not result in the client - // seeing any headers as the stream should already be reset on the other side, even though - // we don't know about it yet. - TestResponseHeaderMapImpl response_headers{{":status", "200"}}; - response_encoder_->encodeHeaders(response_headers, false); - })); - EXPECT_CALL(request_decoder_, decodeData(_, false)).Times(AtLeast(1)); - EXPECT_CALL(server_stream_callbacks_, onResetStream(StreamResetReason::RemoteReset, _)); - - setupDefaultConnectionMocks(); - auto status = server_wrapper_.dispatch(Buffer::OwnedImpl(), *server_); - EXPECT_TRUE(status.ok()); -} - -TEST_P(Http2CodecImplDeferredResetTest, DeferredResetServer) { - initialize(); - - InSequence s; - - TestRequestHeaderMapImpl request_headers; - HttpTestUtility::addDefaultHeaders(request_headers); - EXPECT_CALL(request_decoder_, decodeHeaders_(_, false)); - request_encoder_->encodeHeaders(request_headers, false); - - // In this case we do the same thing as DeferredResetClient but on the server side. - ON_CALL(server_connection_, write(_, _)) - .WillByDefault( - Invoke([&](Buffer::Instance& data, bool) -> void { client_wrapper_.buffer_.add(data); })); - TestResponseHeaderMapImpl response_headers{{":status", "200"}}; - response_encoder_->encodeHeaders(response_headers, false); - Buffer::OwnedImpl body(std::string(1024 * 1024, 'a')); - EXPECT_CALL(server_stream_callbacks_, onAboveWriteBufferHighWatermark()).Times(AnyNumber()); - auto flush_timer = new Event::MockTimer(&server_connection_.dispatcher_); - EXPECT_CALL(*flush_timer, enableTimer(std::chrono::milliseconds(30000), _)); - response_encoder_->encodeData(body, true); - EXPECT_CALL(server_stream_callbacks_, onResetStream(StreamResetReason::LocalReset, _)); - EXPECT_CALL(*flush_timer, disableTimer()); - response_encoder_->getStream().resetStream(StreamResetReason::LocalReset); - - MockStreamCallbacks client_stream_callbacks; - request_encoder_->getStream().addCallbacks(client_stream_callbacks); - EXPECT_CALL(response_decoder_, decodeHeaders_(_, false)); - EXPECT_CALL(response_decoder_, decodeData(_, false)).Times(AtLeast(1)); - EXPECT_CALL(client_stream_callbacks, onResetStream(StreamResetReason::RemoteReset, _)); - setupDefaultConnectionMocks(); - auto status = client_wrapper_.dispatch(Buffer::OwnedImpl(), *client_); - EXPECT_TRUE(status.ok()); -} - -class Http2CodecImplFlowControlTest : public Http2CodecImplTest {}; - -// Back up the pending_sent_data_ buffer in the client connection and make sure the watermarks fire -// as expected. -// -// This also tests the readDisable logic in StreamImpl, verifying that h2 bytes are consumed -// when the stream has readDisable(true) called. -TEST_P(Http2CodecImplFlowControlTest, TestFlowControlInPendingSendData) { - initialize(); - MockStreamCallbacks callbacks; - request_encoder_->getStream().addCallbacks(callbacks); - - TestRequestHeaderMapImpl request_headers; - HttpTestUtility::addDefaultHeaders(request_headers); - TestRequestHeaderMapImpl expected_headers; - HttpTestUtility::addDefaultHeaders(expected_headers); - EXPECT_CALL(request_decoder_, decodeHeaders_(HeaderMapEqual(&expected_headers), false)); - request_encoder_->encodeHeaders(request_headers, false); - - // Force the server stream to be read disabled. This will cause it to stop sending window - // updates to the client. - server_->getStream(1)->readDisable(true); - EXPECT_EQ(1, TestUtility::findGauge(client_stats_store_, "http2.streams_active")->value()); - EXPECT_EQ(1, TestUtility::findGauge(server_stats_store_, "http2.streams_active")->value()); - - uint32_t initial_stream_window = - nghttp2_session_get_stream_effective_local_window_size(client_->session(), 1); - // If this limit is changed, this test will fail due to the initial large writes being divided - // into more than 4 frames. Fast fail here with this explanatory comment. - ASSERT_EQ(65535, initial_stream_window); - // Make sure the limits were configured properly in test set up. - EXPECT_EQ(initial_stream_window, server_->getStream(1)->bufferLimit()); - EXPECT_EQ(initial_stream_window, client_->getStream(1)->bufferLimit()); - - // One large write gets broken into smaller frames. - EXPECT_CALL(request_decoder_, decodeData(_, false)).Times(AnyNumber()); - Buffer::OwnedImpl long_data(std::string(initial_stream_window, 'a')); - request_encoder_->encodeData(long_data, false); - - // Verify that the window is full. The client will not send more data to the server for this - // stream. - EXPECT_EQ(0, nghttp2_session_get_stream_local_window_size(server_->session(), 1)); - EXPECT_EQ(0, nghttp2_session_get_stream_remote_window_size(client_->session(), 1)); - EXPECT_EQ(initial_stream_window, server_->getStream(1)->unconsumed_bytes_); - - // Now that the flow control window is full, further data causes the send buffer to back up. - Buffer::OwnedImpl more_long_data(std::string(initial_stream_window, 'a')); - request_encoder_->encodeData(more_long_data, false); - EXPECT_EQ(initial_stream_window, client_->getStream(1)->pending_send_data_.length()); - EXPECT_EQ(initial_stream_window, - TestUtility::findGauge(client_stats_store_, "http2.pending_send_bytes")->value()); - EXPECT_EQ(initial_stream_window, server_->getStream(1)->unconsumed_bytes_); - - // If we go over the limit, the stream callbacks should fire. - EXPECT_CALL(callbacks, onAboveWriteBufferHighWatermark()); - Buffer::OwnedImpl last_byte("!"); - request_encoder_->encodeData(last_byte, false); - EXPECT_EQ(initial_stream_window + 1, client_->getStream(1)->pending_send_data_.length()); - EXPECT_EQ(initial_stream_window + 1, - TestUtility::findGauge(client_stats_store_, "http2.pending_send_bytes")->value()); - - // Now create a second stream on the connection. - MockResponseDecoder response_decoder2; - RequestEncoder* request_encoder2 = &client_->newStream(response_decoder_); - StreamEncoder* response_encoder2; - MockStreamCallbacks server_stream_callbacks2; - MockRequestDecoder request_decoder2; - // When the server stream is created it should check the status of the - // underlying connection. Pretend it is overrun. - EXPECT_CALL(server_connection_, aboveHighWatermark()).WillOnce(Return(true)); - EXPECT_CALL(server_stream_callbacks2, onAboveWriteBufferHighWatermark()); - EXPECT_CALL(server_callbacks_, newStream(_, _)) - .WillOnce(Invoke([&](ResponseEncoder& encoder, bool) -> RequestDecoder& { - response_encoder2 = &encoder; - encoder.getStream().addCallbacks(server_stream_callbacks2); - return request_decoder2; - })); - EXPECT_CALL(request_decoder2, decodeHeaders_(_, false)); - request_encoder2->encodeHeaders(request_headers, false); - - // Add the stream callbacks belatedly. On creation the stream should have - // been noticed that the connection was backed up. Any new subscriber to - // stream callbacks should get a callback when they addCallbacks. - MockStreamCallbacks callbacks2; - EXPECT_CALL(callbacks2, onAboveWriteBufferHighWatermark()); - request_encoder_->getStream().addCallbacks(callbacks2); - - // Add a third callback to make testing removal mid-watermark call below more interesting. - MockStreamCallbacks callbacks3; - EXPECT_CALL(callbacks3, onAboveWriteBufferHighWatermark()); - request_encoder_->getStream().addCallbacks(callbacks3); - - // Now unblock the server's stream. This will cause the bytes to be consumed, flow control - // updates to be sent, and the client to flush all queued data. - // For bonus corner case coverage, remove callback2 in the middle of runLowWatermarkCallbacks() - // and ensure it is not called. - EXPECT_CALL(callbacks, onBelowWriteBufferLowWatermark()).WillOnce(Invoke([&]() -> void { - request_encoder_->getStream().removeCallbacks(callbacks2); - })); - EXPECT_CALL(callbacks2, onBelowWriteBufferLowWatermark()).Times(0); - EXPECT_CALL(callbacks3, onBelowWriteBufferLowWatermark()); - server_->getStream(1)->readDisable(false); - EXPECT_EQ(0, client_->getStream(1)->pending_send_data_.length()); - EXPECT_EQ(0, TestUtility::findGauge(client_stats_store_, "http2.pending_send_bytes")->value()); - // The extra 1 byte sent won't trigger another window update, so the final window should be the - // initial window minus the last 1 byte flush from the client to server. - EXPECT_EQ(initial_stream_window - 1, - nghttp2_session_get_stream_local_window_size(server_->session(), 1)); - EXPECT_EQ(initial_stream_window - 1, - nghttp2_session_get_stream_remote_window_size(client_->session(), 1)); -} - -// Set up the same asTestFlowControlInPendingSendData, but tears the stream down with an early reset -// once the flow control window is full up. -TEST_P(Http2CodecImplFlowControlTest, EarlyResetRestoresWindow) { - initialize(); - MockStreamCallbacks callbacks; - request_encoder_->getStream().addCallbacks(callbacks); - - TestRequestHeaderMapImpl request_headers; - HttpTestUtility::addDefaultHeaders(request_headers); - TestRequestHeaderMapImpl expected_headers; - HttpTestUtility::addDefaultHeaders(expected_headers); - EXPECT_CALL(request_decoder_, decodeHeaders_(HeaderMapEqual(&expected_headers), false)); - request_encoder_->encodeHeaders(request_headers, false); - - // Force the server stream to be read disabled. This will cause it to stop sending window - // updates to the client. - server_->getStream(1)->readDisable(true); - - uint32_t initial_stream_window = - nghttp2_session_get_stream_effective_local_window_size(client_->session(), 1); - uint32_t initial_connection_window = nghttp2_session_get_remote_window_size(client_->session()); - // If this limit is changed, this test will fail due to the initial large writes being divided - // into more than 4 frames. Fast fail here with this explanatory comment. - ASSERT_EQ(65535, initial_stream_window); - // One large write may get broken into smaller frames. - EXPECT_CALL(request_decoder_, decodeData(_, false)).Times(AnyNumber()); - Buffer::OwnedImpl long_data(std::string(initial_stream_window, 'a')); - // The one giant write will cause the buffer to go over the limit, then drain and go back under - // the limit. - request_encoder_->encodeData(long_data, false); - - // Verify that the window is full. The client will not send more data to the server for this - // stream. - EXPECT_EQ(0, nghttp2_session_get_stream_local_window_size(server_->session(), 1)); - EXPECT_EQ(0, nghttp2_session_get_stream_remote_window_size(client_->session(), 1)); - EXPECT_EQ(initial_stream_window, server_->getStream(1)->unconsumed_bytes_); - EXPECT_GT(initial_connection_window, nghttp2_session_get_remote_window_size(client_->session())); - - EXPECT_CALL(server_stream_callbacks_, - onResetStream(StreamResetReason::LocalRefusedStreamReset, _)); - EXPECT_CALL(callbacks, onAboveWriteBufferHighWatermark()).Times(0); - EXPECT_CALL(callbacks, onBelowWriteBufferLowWatermark()).Times(0); - EXPECT_CALL(server_stream_callbacks_, onAboveWriteBufferHighWatermark()).Times(0); - EXPECT_CALL(server_stream_callbacks_, onBelowWriteBufferLowWatermark()).Times(0); - EXPECT_CALL(callbacks, onResetStream(StreamResetReason::RemoteRefusedStreamReset, _)) - .WillOnce(Invoke([&](StreamResetReason, absl::string_view) -> void { - // Test the case where the reset callbacks cause the socket to fill up, - // causing the underlying connection to back up. Given the stream is - // being destroyed the watermark callbacks should not fire (mocks for Times(0) - // above) - client_->onUnderlyingConnectionAboveWriteBufferHighWatermark(); - client_->onUnderlyingConnectionBelowWriteBufferLowWatermark(); - server_->onUnderlyingConnectionAboveWriteBufferHighWatermark(); - server_->onUnderlyingConnectionBelowWriteBufferLowWatermark(); - })); - response_encoder_->getStream().resetStream(StreamResetReason::LocalRefusedStreamReset); - - // Regression test that the window is consumed even if the stream is destroyed early. - EXPECT_EQ(initial_connection_window, nghttp2_session_get_remote_window_size(client_->session())); -} - -// Test the HTTP2 pending_recv_data_ buffer going over and under watermark limits. -TEST_P(Http2CodecImplFlowControlTest, FlowControlPendingRecvData) { - initialize(); - MockStreamCallbacks callbacks; - - TestRequestHeaderMapImpl request_headers; - HttpTestUtility::addDefaultHeaders(request_headers); - TestRequestHeaderMapImpl expected_headers; - HttpTestUtility::addDefaultHeaders(expected_headers); - EXPECT_CALL(request_decoder_, decodeHeaders_(HeaderMapEqual(&expected_headers), false)); - request_encoder_->encodeHeaders(request_headers, false); - - // Set artificially small watermarks to make the recv buffer easy to overrun. In production, - // the recv buffer can be overrun by a client which negotiates a larger - // SETTINGS_MAX_FRAME_SIZE but there's no current easy way to tweak that in - // envoy (without sending raw HTTP/2 frames) so we lower the buffer limit instead. - server_->getStream(1)->setWriteBufferWatermarks(10, 20); - - EXPECT_CALL(request_decoder_, decodeData(_, false)); - Buffer::OwnedImpl data(std::string(40, 'a')); - request_encoder_->encodeData(data, false); -} - -// Verify that we create and disable the stream flush timer when trailers follow a stream that -// does not have enough window. -TEST_P(Http2CodecImplFlowControlTest, TrailingHeadersLargeServerBody) { - initialize(); - - InSequence s; - TestRequestHeaderMapImpl request_headers; - HttpTestUtility::addDefaultHeaders(request_headers); - EXPECT_CALL(request_decoder_, decodeHeaders_(_, true)); - request_encoder_->encodeHeaders(request_headers, true); - - ON_CALL(client_connection_, write(_, _)) - .WillByDefault( - Invoke([&](Buffer::Instance& data, bool) -> void { server_wrapper_.buffer_.add(data); })); - TestResponseHeaderMapImpl response_headers{{":status", "200"}}; - EXPECT_CALL(response_decoder_, decodeHeaders_(_, false)); - response_encoder_->encodeHeaders(response_headers, false); - EXPECT_CALL(server_stream_callbacks_, onAboveWriteBufferHighWatermark()); - EXPECT_CALL(response_decoder_, decodeData(_, false)).Times(AtLeast(1)); - auto flush_timer = new Event::MockTimer(&server_connection_.dispatcher_); - EXPECT_CALL(*flush_timer, enableTimer(std::chrono::milliseconds(30000), _)); - Buffer::OwnedImpl body(std::string(1024 * 1024, 'a')); - response_encoder_->encodeData(body, false); - response_encoder_->encodeTrailers(TestResponseTrailerMapImpl{{"trailing", "header"}}); - - // Send window updates from the client. - setupDefaultConnectionMocks(); - EXPECT_CALL(response_decoder_, decodeData(_, false)).Times(AtLeast(1)); - EXPECT_CALL(response_decoder_, decodeTrailers_(_)); - EXPECT_CALL(*flush_timer, disableTimer()); - auto status = server_wrapper_.dispatch(Buffer::OwnedImpl(), *server_); - EXPECT_TRUE(status.ok()); - EXPECT_EQ(0, server_stats_store_.counter("http2.tx_flush_timeout").value()); -} - -// Verify that we create and handle the stream flush timeout when trailers follow a stream that -// does not have enough window. -TEST_P(Http2CodecImplFlowControlTest, TrailingHeadersLargeServerBodyFlushTimeout) { - initialize(); - - InSequence s; - MockStreamCallbacks client_stream_callbacks; - request_encoder_->getStream().addCallbacks(client_stream_callbacks); - TestRequestHeaderMapImpl request_headers; - HttpTestUtility::addDefaultHeaders(request_headers); - EXPECT_CALL(request_decoder_, decodeHeaders_(_, true)); - request_encoder_->encodeHeaders(request_headers, true); - - ON_CALL(client_connection_, write(_, _)) - .WillByDefault( - Invoke([&](Buffer::Instance& data, bool) -> void { server_wrapper_.buffer_.add(data); })); - TestResponseHeaderMapImpl response_headers{{":status", "200"}}; - EXPECT_CALL(response_decoder_, decodeHeaders_(_, false)); - response_encoder_->encodeHeaders(response_headers, false); - EXPECT_CALL(server_stream_callbacks_, onAboveWriteBufferHighWatermark()); - EXPECT_CALL(response_decoder_, decodeData(_, false)).Times(AtLeast(1)); - auto flush_timer = new Event::MockTimer(&server_connection_.dispatcher_); - EXPECT_CALL(*flush_timer, enableTimer(std::chrono::milliseconds(30000), _)); - Buffer::OwnedImpl body(std::string(1024 * 1024, 'a')); - response_encoder_->encodeData(body, false); - response_encoder_->encodeTrailers(TestResponseTrailerMapImpl{{"trailing", "header"}}); - - // Invoke a stream flush timeout. Make sure we don't get a reset locally for higher layers but - // we do get a reset on the client. - EXPECT_CALL(server_stream_callbacks_, onResetStream(_, _)).Times(0); - EXPECT_CALL(client_stream_callbacks, onResetStream(StreamResetReason::RemoteReset, _)); - flush_timer->invokeCallback(); - EXPECT_EQ(1, server_stats_store_.counter("http2.tx_flush_timeout").value()); -} - -// Verify that we create and handle the stream flush timeout when there is a large body that -// does not have enough window. -TEST_P(Http2CodecImplFlowControlTest, LargeServerBodyFlushTimeout) { - initialize(); - - InSequence s; - MockStreamCallbacks client_stream_callbacks; - request_encoder_->getStream().addCallbacks(client_stream_callbacks); - TestRequestHeaderMapImpl request_headers; - HttpTestUtility::addDefaultHeaders(request_headers); - EXPECT_CALL(request_decoder_, decodeHeaders_(_, true)); - request_encoder_->encodeHeaders(request_headers, true); - - ON_CALL(client_connection_, write(_, _)) - .WillByDefault( - Invoke([&](Buffer::Instance& data, bool) -> void { server_wrapper_.buffer_.add(data); })); - TestResponseHeaderMapImpl response_headers{{":status", "200"}}; - EXPECT_CALL(response_decoder_, decodeHeaders_(_, false)); - response_encoder_->encodeHeaders(response_headers, false); - EXPECT_CALL(response_decoder_, decodeData(_, false)).Times(AtLeast(1)); - auto flush_timer = new Event::MockTimer(&server_connection_.dispatcher_); - EXPECT_CALL(*flush_timer, enableTimer(std::chrono::milliseconds(30000), _)); - Buffer::OwnedImpl body(std::string(1024 * 1024, 'a')); - response_encoder_->encodeData(body, true); - - // Invoke a stream flush timeout. Make sure we don't get a reset locally for higher layers but - // we do get a reset on the client. - EXPECT_CALL(server_stream_callbacks_, onResetStream(_, _)).Times(0); - EXPECT_CALL(client_stream_callbacks, onResetStream(StreamResetReason::RemoteReset, _)); - flush_timer->invokeCallback(); - EXPECT_EQ(1, server_stats_store_.counter("http2.tx_flush_timeout").value()); -} - -// Verify that when an incoming protocol error races with a stream flush timeout we correctly -// disable the flush timeout and do not attempt to reset the stream. -TEST_P(Http2CodecImplFlowControlTest, LargeServerBodyFlushTimeoutAfterGoaway) { - initialize(); - - InSequence s; - MockStreamCallbacks client_stream_callbacks; - request_encoder_->getStream().addCallbacks(client_stream_callbacks); - TestRequestHeaderMapImpl request_headers; - HttpTestUtility::addDefaultHeaders(request_headers); - EXPECT_CALL(request_decoder_, decodeHeaders_(_, true)); - request_encoder_->encodeHeaders(request_headers, true); - - ON_CALL(client_connection_, write(_, _)) - .WillByDefault( - Invoke([&](Buffer::Instance& data, bool) -> void { server_wrapper_.buffer_.add(data); })); - TestResponseHeaderMapImpl response_headers{{":status", "200"}}; - EXPECT_CALL(response_decoder_, decodeHeaders_(_, false)); - response_encoder_->encodeHeaders(response_headers, false); - EXPECT_CALL(response_decoder_, decodeData(_, false)).Times(AtLeast(1)); - auto flush_timer = new Event::MockTimer(&server_connection_.dispatcher_); - EXPECT_CALL(*flush_timer, enableTimer(std::chrono::milliseconds(30000), _)); - Buffer::OwnedImpl body(std::string(1024 * 1024, 'a')); - response_encoder_->encodeData(body, true); - - // Force a protocol error. - Buffer::OwnedImpl garbage_data("this should cause a protocol error"); - EXPECT_CALL(client_callbacks_, onGoAway(_)); - EXPECT_CALL(*flush_timer, disableTimer()); - EXPECT_CALL(server_stream_callbacks_, onResetStream(_, _)).Times(0); - auto status = server_wrapper_.dispatch(garbage_data, *server_); - EXPECT_FALSE(status.ok()); - EXPECT_EQ(0, server_stats_store_.counter("http2.tx_flush_timeout").value()); -} - -TEST_P(Http2CodecImplTest, WatermarkUnderEndStream) { - initialize(); - MockStreamCallbacks callbacks; - request_encoder_->getStream().addCallbacks(callbacks); - - TestRequestHeaderMapImpl request_headers; - HttpTestUtility::addDefaultHeaders(request_headers); - EXPECT_CALL(request_decoder_, decodeHeaders_(_, false)); - request_encoder_->encodeHeaders(request_headers, false); - - // The 'true' on encodeData will set local_end_stream_ on the client but not - // the server. Verify that client watermark callbacks will not be called, but - // server callbacks may be called by simulating connection overflow on both - // ends. - EXPECT_CALL(callbacks, onAboveWriteBufferHighWatermark()).Times(0); - EXPECT_CALL(callbacks, onBelowWriteBufferLowWatermark()).Times(0); - EXPECT_CALL(server_stream_callbacks_, onAboveWriteBufferHighWatermark()); - EXPECT_CALL(server_stream_callbacks_, onBelowWriteBufferLowWatermark()); - EXPECT_CALL(request_decoder_, decodeData(_, true)).WillOnce(InvokeWithoutArgs([&]() -> void { - client_->onUnderlyingConnectionAboveWriteBufferHighWatermark(); - client_->onUnderlyingConnectionBelowWriteBufferLowWatermark(); - server_->onUnderlyingConnectionAboveWriteBufferHighWatermark(); - server_->onUnderlyingConnectionBelowWriteBufferLowWatermark(); - })); - Buffer::OwnedImpl hello("hello"); - request_encoder_->encodeData(hello, true); - - // The 'true' on encodeData will set local_end_stream_ on the server. Verify - // that neither client nor server watermark callbacks will be called again. - EXPECT_CALL(callbacks, onAboveWriteBufferHighWatermark()).Times(0); - EXPECT_CALL(callbacks, onBelowWriteBufferLowWatermark()).Times(0); - EXPECT_CALL(server_stream_callbacks_, onAboveWriteBufferHighWatermark()).Times(0); - EXPECT_CALL(server_stream_callbacks_, onBelowWriteBufferLowWatermark()).Times(0); - TestResponseHeaderMapImpl response_headers{{":status", "200"}}; - EXPECT_CALL(response_decoder_, decodeHeaders_(HeaderMapEqual(&response_headers), true)) - .WillOnce(InvokeWithoutArgs([&]() -> void { - client_->onUnderlyingConnectionAboveWriteBufferHighWatermark(); - client_->onUnderlyingConnectionBelowWriteBufferLowWatermark(); - server_->onUnderlyingConnectionAboveWriteBufferHighWatermark(); - server_->onUnderlyingConnectionBelowWriteBufferLowWatermark(); - })); - response_encoder_->encodeHeaders(response_headers, true); -} - -class Http2CodecImplStreamLimitTest : public Http2CodecImplTest {}; - -// Regression test for issue #3076. -// -// TODO(PiotrSikora): add tests that exercise both scenarios: before and after receiving -// the HTTP/2 SETTINGS frame. -TEST_P(Http2CodecImplStreamLimitTest, MaxClientStreams) { - http2OptionsFromTuple(client_http2_options_, ::testing::get<0>(GetParam())); - http2OptionsFromTuple(server_http2_options_, ::testing::get<1>(GetParam())); - client_ = std::make_unique( - client_connection_, client_callbacks_, client_stats_store_, client_http2_options_, - max_request_headers_kb_, max_response_headers_count_, ProdNghttp2SessionFactory::get()); - server_ = std::make_unique( - server_connection_, server_callbacks_, server_stats_store_, server_http2_options_, - max_request_headers_kb_, max_request_headers_count_, headers_with_underscores_action_); - - for (int i = 0; i < 101; ++i) { - request_encoder_ = &client_->newStream(response_decoder_); - setupDefaultConnectionMocks(); - EXPECT_CALL(server_callbacks_, newStream(_, _)) - .WillOnce(Invoke([&](ResponseEncoder& encoder, bool) -> RequestDecoder& { - response_encoder_ = &encoder; - encoder.getStream().addCallbacks(server_stream_callbacks_); - return request_decoder_; - })); - - TestRequestHeaderMapImpl request_headers; - HttpTestUtility::addDefaultHeaders(request_headers); - EXPECT_CALL(request_decoder_, decodeHeaders_(_, true)); - request_encoder_->encodeHeaders(request_headers, true); - } -} - -#define HTTP2SETTINGS_SMALL_WINDOW_COMBINE \ - ::testing::Combine( \ - ::testing::Values(CommonUtility::OptionsLimits::DEFAULT_HPACK_TABLE_SIZE), \ - ::testing::Values(CommonUtility::OptionsLimits::DEFAULT_MAX_CONCURRENT_STREAMS), \ - ::testing::Values(CommonUtility::OptionsLimits::MIN_INITIAL_STREAM_WINDOW_SIZE), \ - ::testing::Values(CommonUtility::OptionsLimits::MIN_INITIAL_CONNECTION_WINDOW_SIZE)) - -// Deferred reset tests use only small windows so that we can test certain conditions. -INSTANTIATE_TEST_SUITE_P(Http2CodecImplDeferredResetTest, Http2CodecImplDeferredResetTest, - ::testing::Combine(HTTP2SETTINGS_SMALL_WINDOW_COMBINE, - HTTP2SETTINGS_SMALL_WINDOW_COMBINE)); - -// Flow control tests only use only small windows so that we can test certain conditions. -INSTANTIATE_TEST_SUITE_P(Http2CodecImplFlowControlTest, Http2CodecImplFlowControlTest, - ::testing::Combine(HTTP2SETTINGS_SMALL_WINDOW_COMBINE, - HTTP2SETTINGS_SMALL_WINDOW_COMBINE)); - -// we separate default/edge cases here to avoid combinatorial explosion -#define HTTP2SETTINGS_DEFAULT_COMBINE \ - ::testing::Combine( \ - ::testing::Values(CommonUtility::OptionsLimits::DEFAULT_HPACK_TABLE_SIZE), \ - ::testing::Values(CommonUtility::OptionsLimits::DEFAULT_MAX_CONCURRENT_STREAMS), \ - ::testing::Values(CommonUtility::OptionsLimits::DEFAULT_INITIAL_STREAM_WINDOW_SIZE), \ - ::testing::Values(CommonUtility::OptionsLimits::DEFAULT_INITIAL_CONNECTION_WINDOW_SIZE)) - -// Stream limit test only uses the default values because not all combinations of -// edge settings allow for the number of streams needed by the test. -INSTANTIATE_TEST_SUITE_P(Http2CodecImplStreamLimitTest, Http2CodecImplStreamLimitTest, - ::testing::Combine(HTTP2SETTINGS_DEFAULT_COMBINE, - HTTP2SETTINGS_DEFAULT_COMBINE)); - -INSTANTIATE_TEST_SUITE_P(Http2CodecImplTestDefaultSettings, Http2CodecImplTest, - ::testing::Combine(HTTP2SETTINGS_DEFAULT_COMBINE, - HTTP2SETTINGS_DEFAULT_COMBINE)); - -#define HTTP2SETTINGS_EDGE_COMBINE \ - ::testing::Combine( \ - ::testing::Values(CommonUtility::OptionsLimits::MIN_HPACK_TABLE_SIZE, \ - CommonUtility::OptionsLimits::MAX_HPACK_TABLE_SIZE), \ - ::testing::Values(CommonUtility::OptionsLimits::MIN_MAX_CONCURRENT_STREAMS, \ - CommonUtility::OptionsLimits::MAX_MAX_CONCURRENT_STREAMS), \ - ::testing::Values(CommonUtility::OptionsLimits::MIN_INITIAL_STREAM_WINDOW_SIZE, \ - CommonUtility::OptionsLimits::MAX_INITIAL_STREAM_WINDOW_SIZE), \ - ::testing::Values(CommonUtility::OptionsLimits::MIN_INITIAL_CONNECTION_WINDOW_SIZE, \ - CommonUtility::OptionsLimits::MAX_INITIAL_CONNECTION_WINDOW_SIZE)) - -// Make sure we have coverage for high and low values for various combinations and permutations -// of HTTP settings in at least one test fixture. -// Use with caution as any test using this runs 255 times. -using Http2CodecImplTestAll = Http2CodecImplTest; - -INSTANTIATE_TEST_SUITE_P(Http2CodecImplTestDefaultSettings, Http2CodecImplTestAll, - ::testing::Combine(HTTP2SETTINGS_DEFAULT_COMBINE, - HTTP2SETTINGS_DEFAULT_COMBINE)); -INSTANTIATE_TEST_SUITE_P(Http2CodecImplTestEdgeSettings, Http2CodecImplTestAll, - ::testing::Combine(HTTP2SETTINGS_EDGE_COMBINE, - HTTP2SETTINGS_EDGE_COMBINE)); - -TEST(Http2CodecUtility, reconstituteCrumbledCookies) { - { - HeaderString key; - HeaderString value; - HeaderString cookies; - EXPECT_FALSE(Utility::reconstituteCrumbledCookies(key, value, cookies)); - EXPECT_TRUE(cookies.empty()); - } - - { - HeaderString key(Headers::get().ContentLength); - HeaderString value; - value.setInteger(5); - HeaderString cookies; - EXPECT_FALSE(Utility::reconstituteCrumbledCookies(key, value, cookies)); - EXPECT_TRUE(cookies.empty()); - } - - { - HeaderString key(Headers::get().Cookie); - HeaderString value; - value.setCopy("a=b", 3); - HeaderString cookies; - EXPECT_TRUE(Utility::reconstituteCrumbledCookies(key, value, cookies)); - EXPECT_EQ(cookies, "a=b"); - - HeaderString key2(Headers::get().Cookie); - HeaderString value2; - value2.setCopy("c=d", 3); - EXPECT_TRUE(Utility::reconstituteCrumbledCookies(key2, value2, cookies)); - EXPECT_EQ(cookies, "a=b; c=d"); - } -} - -MATCHER_P(HasValue, m, "") { - if (!arg.has_value()) { - *result_listener << "does not contain a value"; - return false; - } - const auto& value = arg.value(); - return ExplainMatchResult(m, value, result_listener); -}; - -class Http2CustomSettingsTestBase : public Http2CodecImplTestFixture { -public: - struct SettingsParameter { - uint16_t identifier; - uint32_t value; - }; - - Http2CustomSettingsTestBase(Http2SettingsTuple client_settings, - Http2SettingsTuple server_settings, bool validate_client) - : Http2CodecImplTestFixture(client_settings, server_settings), - validate_client_(validate_client) {} - - ~Http2CustomSettingsTestBase() override = default; - - // Sets the custom settings parameters specified by |parameters| in the |options| proto. - void setHttp2CustomSettingsParameters(envoy::config::core::v3::Http2ProtocolOptions& options, - std::vector parameters) { - for (const auto& parameter : parameters) { - envoy::config::core::v3::Http2ProtocolOptions::SettingsParameter* custom_param = - options.mutable_custom_settings_parameters()->Add(); - custom_param->mutable_identifier()->set_value(parameter.identifier); - custom_param->mutable_value()->set_value(parameter.value); - } - } - - // Returns the Http2ProtocolOptions proto which specifies the settings parameters to be sent to - // the endpoint being validated. - envoy::config::core::v3::Http2ProtocolOptions& getCustomOptions() { - return validate_client_ ? server_http2_options_ : client_http2_options_; - } - - // Returns the endpoint being validated. - const TestCodecSettingsProvider& getSettingsProvider() { - if (validate_client_) { - return *client_; - } - return *server_; - } - - // Returns the settings tuple which specifies a subset of the settings parameters to be sent to - // the endpoint being validated. - const Http2SettingsTuple& getSettingsTuple() { - ASSERT(client_settings_.has_value() && server_settings_.has_value()); - return validate_client_ ? *server_settings_ : *client_settings_; - } - -protected: - bool validate_client_{false}; -}; - -class Http2CustomSettingsTest - : public Http2CustomSettingsTestBase, - public ::testing::TestWithParam< - ::testing::tuple> { -public: - Http2CustomSettingsTest() - : Http2CustomSettingsTestBase(::testing::get<0>(GetParam()), ::testing::get<1>(GetParam()), - ::testing::get<2>(GetParam())) {} -}; -INSTANTIATE_TEST_SUITE_P(Http2CodecImplTestEdgeSettings, Http2CustomSettingsTest, - ::testing::Combine(HTTP2SETTINGS_DEFAULT_COMBINE, - HTTP2SETTINGS_DEFAULT_COMBINE, ::testing::Bool())); - -// Validates that custom parameters (those which are not explicitly named in the -// envoy::config::core::v3::Http2ProtocolOptions proto) are properly sent and processed by -// client and server connections. -TEST_P(Http2CustomSettingsTest, UserDefinedSettings) { - std::vector custom_parameters{{0x10, 10}, {0x11, 20}}; - setHttp2CustomSettingsParameters(getCustomOptions(), custom_parameters); - initialize(); - TestRequestHeaderMapImpl request_headers; - HttpTestUtility::addDefaultHeaders(request_headers); - EXPECT_CALL(request_decoder_, decodeHeaders_(_, _)); - request_encoder_->encodeHeaders(request_headers, false); - uint32_t hpack_table_size = - ::testing::get(getSettingsTuple()); - if (hpack_table_size != NGHTTP2_DEFAULT_HEADER_TABLE_SIZE) { - EXPECT_THAT( - getSettingsProvider().getRemoteSettingsParameterValue(NGHTTP2_SETTINGS_HEADER_TABLE_SIZE), - HasValue(hpack_table_size)); - } - uint32_t max_concurrent_streams = - ::testing::get(getSettingsTuple()); - if (max_concurrent_streams != NGHTTP2_INITIAL_MAX_CONCURRENT_STREAMS) { - EXPECT_THAT(getSettingsProvider().getRemoteSettingsParameterValue( - NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS), - HasValue(max_concurrent_streams)); - } - uint32_t initial_stream_window_size = - ::testing::get(getSettingsTuple()); - if (max_concurrent_streams != NGHTTP2_INITIAL_WINDOW_SIZE) { - EXPECT_THAT( - getSettingsProvider().getRemoteSettingsParameterValue(NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE), - HasValue(initial_stream_window_size)); - } - // Validate that custom parameters are received by the endpoint (client or server) under - // test. - for (const auto& parameter : custom_parameters) { - EXPECT_THAT(getSettingsProvider().getRemoteSettingsParameterValue(parameter.identifier), - HasValue(parameter.value)); - } -} - -// Tests request headers whose size is larger than the default limit of 60K. -TEST_P(Http2CodecImplTest, LargeRequestHeadersInvokeResetStream) { - initialize(); - - TestRequestHeaderMapImpl request_headers; - HttpTestUtility::addDefaultHeaders(request_headers); - std::string long_string = std::string(63 * 1024, 'q'); - request_headers.addCopy("big", long_string); - EXPECT_CALL(server_stream_callbacks_, onResetStream(_, _)).Times(1); - request_encoder_->encodeHeaders(request_headers, false); -} - -// Large request headers are accepted when max limit configured. -TEST_P(Http2CodecImplTest, LargeRequestHeadersAccepted) { - max_request_headers_kb_ = 64; - initialize(); - - TestRequestHeaderMapImpl request_headers; - HttpTestUtility::addDefaultHeaders(request_headers); - std::string long_string = std::string(63 * 1024, 'q'); - request_headers.addCopy("big", long_string); - - EXPECT_CALL(request_decoder_, decodeHeaders_(_, _)); - EXPECT_CALL(server_stream_callbacks_, onResetStream(_, _)).Times(0); - request_encoder_->encodeHeaders(request_headers, false); -} - -// Tests request headers with name containing underscore are dropped when the option is set to drop -// header. -TEST_P(Http2CodecImplTest, HeaderNameWithUnderscoreAreDropped) { - headers_with_underscores_action_ = envoy::config::core::v3::HttpProtocolOptions::DROP_HEADER; - initialize(); - - TestRequestHeaderMapImpl request_headers; - HttpTestUtility::addDefaultHeaders(request_headers); - TestRequestHeaderMapImpl expected_headers(request_headers); - request_headers.addCopy("bad_header", "something"); - EXPECT_CALL(request_decoder_, decodeHeaders_(HeaderMapEqual(&expected_headers), _)); - request_encoder_->encodeHeaders(request_headers, false); - EXPECT_EQ(1, server_stats_store_.counter("http2.dropped_headers_with_underscores").value()); -} - -// Tests that request with header names containing underscore are rejected when the option is set to -// reject request. -TEST_P(Http2CodecImplTest, HeaderNameWithUnderscoreAreRejectedByDefault) { - headers_with_underscores_action_ = envoy::config::core::v3::HttpProtocolOptions::REJECT_REQUEST; - initialize(); - - TestRequestHeaderMapImpl request_headers; - HttpTestUtility::addDefaultHeaders(request_headers); - request_headers.addCopy("bad_header", "something"); - EXPECT_CALL(server_stream_callbacks_, onResetStream(_, _)).Times(1); - request_encoder_->encodeHeaders(request_headers, false); - EXPECT_EQ( - 1, - server_stats_store_.counter("http2.requests_rejected_with_underscores_in_headers").value()); -} - -// Tests request headers with name containing underscore are allowed when the option is set to -// allow. -TEST_P(Http2CodecImplTest, HeaderNameWithUnderscoreAllowed) { - headers_with_underscores_action_ = envoy::config::core::v3::HttpProtocolOptions::ALLOW; - initialize(); - - TestRequestHeaderMapImpl request_headers; - HttpTestUtility::addDefaultHeaders(request_headers); - request_headers.addCopy("bad_header", "something"); - TestRequestHeaderMapImpl expected_headers(request_headers); - EXPECT_CALL(request_decoder_, decodeHeaders_(HeaderMapEqual(&expected_headers), _)); - EXPECT_CALL(server_stream_callbacks_, onResetStream(_, _)).Times(0); - request_encoder_->encodeHeaders(request_headers, false); - EXPECT_EQ(0, server_stats_store_.counter("http2.dropped_headers_with_underscores").value()); -} - -// This is the HTTP/2 variant of the HTTP/1 regression test for CVE-2019-18801. -// Large method headers should not trigger ASSERTs or ASAN. The underlying issue -// in CVE-2019-18801 only affected the HTTP/1 encoder, but we include a test -// here for belt-and-braces. This also demonstrates that the HTTP/2 codec will -// accept arbitrary :method headers, unlike the HTTP/1 codec (see -// Http1ServerConnectionImplTest.RejectInvalidMethod for comparison). -TEST_P(Http2CodecImplTest, LargeMethodRequestEncode) { - max_request_headers_kb_ = 80; - initialize(); - - const std::string long_method = std::string(79 * 1024, 'a'); - TestRequestHeaderMapImpl request_headers; - HttpTestUtility::addDefaultHeaders(request_headers); - request_headers.setReferenceKey(Headers::get().Method, long_method); - EXPECT_CALL(request_decoder_, decodeHeaders_(HeaderMapEqual(&request_headers), false)); - EXPECT_CALL(server_stream_callbacks_, onResetStream(_, _)).Times(0); - request_encoder_->encodeHeaders(request_headers, false); -} - -// Tests stream reset when the number of request headers exceeds the default maximum of 100. -TEST_P(Http2CodecImplTest, ManyRequestHeadersInvokeResetStream) { - initialize(); - - TestRequestHeaderMapImpl request_headers; - HttpTestUtility::addDefaultHeaders(request_headers); - for (int i = 0; i < 100; i++) { - request_headers.addCopy(std::to_string(i), ""); - } - EXPECT_CALL(server_stream_callbacks_, onResetStream(_, _)).Times(1); - request_encoder_->encodeHeaders(request_headers, false); -} - -// Tests that max number of request headers is configurable. -TEST_P(Http2CodecImplTest, ManyRequestHeadersAccepted) { - max_request_headers_count_ = 150; - initialize(); - - TestRequestHeaderMapImpl request_headers; - HttpTestUtility::addDefaultHeaders(request_headers); - for (int i = 0; i < 145; i++) { - request_headers.addCopy(std::to_string(i), ""); - } - EXPECT_CALL(request_decoder_, decodeHeaders_(_, _)); - EXPECT_CALL(server_stream_callbacks_, onResetStream(_, _)).Times(0); - request_encoder_->encodeHeaders(request_headers, false); -} - -// Tests that max number of response headers is configurable. -TEST_P(Http2CodecImplTest, ManyResponseHeadersAccepted) { - max_response_headers_count_ = 110; - initialize(); - - TestRequestHeaderMapImpl request_headers; - HttpTestUtility::addDefaultHeaders(request_headers); - EXPECT_CALL(request_decoder_, decodeHeaders_(_, false)); - request_encoder_->encodeHeaders(request_headers, false); - - TestResponseHeaderMapImpl response_headers{{":status", "200"}, {"compression", "test"}}; - for (int i = 0; i < 105; i++) { - response_headers.addCopy(std::to_string(i), ""); - } - EXPECT_CALL(response_decoder_, decodeHeaders_(_, true)); - response_encoder_->encodeHeaders(response_headers, true); -} - -TEST_P(Http2CodecImplTest, LargeRequestHeadersAtLimitAccepted) { - uint32_t codec_limit_kb = 64; - max_request_headers_kb_ = codec_limit_kb; - initialize(); - - TestRequestHeaderMapImpl request_headers; - HttpTestUtility::addDefaultHeaders(request_headers); - std::string key = "big"; - uint32_t head_room = 77; - uint32_t long_string_length = - codec_limit_kb * 1024 - request_headers.byteSize() - key.length() - head_room; - std::string long_string = std::string(long_string_length, 'q'); - request_headers.addCopy(key, long_string); - - // The amount of data sent to the codec is not equivalent to the size of the - // request headers that Envoy computes, as the codec limits based on the - // entire http2 frame. The exact head room needed (76) was found through iteration. - ASSERT_EQ(request_headers.byteSize() + head_room, codec_limit_kb * 1024); - - EXPECT_CALL(request_decoder_, decodeHeaders_(_, _)); - request_encoder_->encodeHeaders(request_headers, true); -} - -TEST_P(Http2CodecImplTest, LargeRequestHeadersOverDefaultCodecLibraryLimit) { - max_request_headers_kb_ = 66; - initialize(); - - TestRequestHeaderMapImpl 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, LargeRequestHeadersExceedPerHeaderLimit) { - // 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(); - - TestRequestHeaderMapImpl 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, ManyLargeRequestHeadersUnderPerHeaderLimit) { - max_request_headers_kb_ = 81; - initialize(); - - TestRequestHeaderMapImpl request_headers; - HttpTestUtility::addDefaultHeaders(request_headers); - std::string long_string = std::string(1024, 'q'); - for (int i = 0; i < 80; i++) { - request_headers.addCopy(std::to_string(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, LargeRequestHeadersAtMaxConfigurable) { - // Raising the limit past this triggers some unexpected nghttp2 error. - // Further debugging required to increase past ~96 KiB. - max_request_headers_kb_ = 96; - initialize(); - - TestRequestHeaderMapImpl request_headers; - HttpTestUtility::addDefaultHeaders(request_headers); - std::string long_string = std::string(1024, 'q'); - for (int i = 0; i < 95; i++) { - request_headers.addCopy(std::to_string(i), long_string); - } - - EXPECT_CALL(request_decoder_, decodeHeaders_(_, _)).Times(1); - EXPECT_CALL(server_stream_callbacks_, onResetStream(_, _)).Times(0); - request_encoder_->encodeHeaders(request_headers, true); -} - -// Note this is Http2CodecImplTestAll not Http2CodecImplTest, to test -// compression with min and max HPACK table size. -TEST_P(Http2CodecImplTestAll, TestCodecHeaderCompression) { - initialize(); - - TestRequestHeaderMapImpl request_headers; - HttpTestUtility::addDefaultHeaders(request_headers); - EXPECT_CALL(request_decoder_, decodeHeaders_(_, true)); - request_encoder_->encodeHeaders(request_headers, true); - - TestResponseHeaderMapImpl response_headers{{":status", "200"}, {"compression", "test"}}; - EXPECT_CALL(response_decoder_, decodeHeaders_(_, true)); - response_encoder_->encodeHeaders(response_headers, true); - - // Sanity check to verify that state of encoders and decoders matches. - EXPECT_EQ(nghttp2_session_get_hd_deflate_dynamic_table_size(server_->session()), - nghttp2_session_get_hd_inflate_dynamic_table_size(client_->session())); - EXPECT_EQ(nghttp2_session_get_hd_deflate_dynamic_table_size(client_->session()), - nghttp2_session_get_hd_inflate_dynamic_table_size(server_->session())); - - // Verify that headers are compressed only when both client and server advertise table size - // > 0: - if (client_http2_options_.hpack_table_size().value() && - server_http2_options_.hpack_table_size().value()) { - EXPECT_NE(0, nghttp2_session_get_hd_deflate_dynamic_table_size(client_->session())); - EXPECT_NE(0, nghttp2_session_get_hd_deflate_dynamic_table_size(server_->session())); - } else { - EXPECT_EQ(0, nghttp2_session_get_hd_deflate_dynamic_table_size(client_->session())); - EXPECT_EQ(0, nghttp2_session_get_hd_deflate_dynamic_table_size(server_->session())); - } -} - -// Verify that codec detects PING flood -TEST_P(Http2CodecImplTest, PingFlood) { - initialize(); - - TestRequestHeaderMapImpl request_headers; - HttpTestUtility::addDefaultHeaders(request_headers); - EXPECT_CALL(request_decoder_, decodeHeaders_(_, false)); - request_encoder_->encodeHeaders(request_headers, false); - - // Send one frame above the outbound control queue size limit - for (uint32_t i = 0; i < CommonUtility::OptionsLimits::DEFAULT_MAX_OUTBOUND_CONTROL_FRAMES + 1; - ++i) { - EXPECT_EQ(0, nghttp2_submit_ping(client_->session(), NGHTTP2_FLAG_NONE, nullptr)); - } - - int ack_count = 0; - Buffer::OwnedImpl buffer; - ON_CALL(server_connection_, write(_, _)) - .WillByDefault(Invoke([&buffer, &ack_count](Buffer::Instance& frame, bool) { - ++ack_count; - buffer.move(frame); - })); - - EXPECT_THROW(client_->sendPendingFrames(), ServerCodecError); - EXPECT_EQ(ack_count, CommonUtility::OptionsLimits::DEFAULT_MAX_OUTBOUND_CONTROL_FRAMES); - EXPECT_EQ(1, server_stats_store_.counter("http2.outbound_control_flood").value()); -} - -// Verify that codec allows PING flood when mitigation is disabled -TEST_P(Http2CodecImplTest, PingFloodMitigationDisabled) { - max_outbound_control_frames_ = 2147483647; - initialize(); - - TestRequestHeaderMapImpl request_headers; - HttpTestUtility::addDefaultHeaders(request_headers); - EXPECT_CALL(request_decoder_, decodeHeaders_(_, false)); - request_encoder_->encodeHeaders(request_headers, false); - - // Send one frame above the outbound control queue size limit - for (uint32_t i = 0; i < CommonUtility::OptionsLimits::DEFAULT_MAX_OUTBOUND_CONTROL_FRAMES + 1; - ++i) { - EXPECT_EQ(0, nghttp2_submit_ping(client_->session(), NGHTTP2_FLAG_NONE, nullptr)); - } - - EXPECT_CALL(server_connection_, write(_, _)) - .Times(CommonUtility::OptionsLimits::DEFAULT_MAX_OUTBOUND_CONTROL_FRAMES + 1); - EXPECT_NO_THROW(client_->sendPendingFrames()); -} - -// Verify that outbound control frame counter decreases when send buffer is drained -TEST_P(Http2CodecImplTest, PingFloodCounterReset) { - // Ping frames are 17 bytes each so 237 full frames and a partial frame fit in the current min - // size for buffer slices. Setting the limit to 2x+1 the number that fits in a single slice allows - // the logic below that verifies drain and overflow thresholds. - static const int kMaxOutboundControlFrames = 475; - max_outbound_control_frames_ = kMaxOutboundControlFrames; - initialize(); - - TestRequestHeaderMapImpl request_headers; - HttpTestUtility::addDefaultHeaders(request_headers); - EXPECT_CALL(request_decoder_, decodeHeaders_(_, false)); - request_encoder_->encodeHeaders(request_headers, false); - - for (int i = 0; i < kMaxOutboundControlFrames; ++i) { - EXPECT_EQ(0, nghttp2_submit_ping(client_->session(), NGHTTP2_FLAG_NONE, nullptr)); - } - - int ack_count = 0; - Buffer::OwnedImpl buffer; - ON_CALL(server_connection_, write(_, _)) - .WillByDefault(Invoke([&buffer, &ack_count](Buffer::Instance& frame, bool) { - ++ack_count; - buffer.move(frame); - })); - - // We should be 1 frame under the control frame flood mitigation threshold. - EXPECT_NO_THROW(client_->sendPendingFrames()); - EXPECT_EQ(ack_count, kMaxOutboundControlFrames); - - // Drain floor(kMaxOutboundFrames / 2) slices from the send buffer - buffer.drain(buffer.length() / 2); - - // Send floor(kMaxOutboundFrames / 2) more pings. - for (int i = 0; i < kMaxOutboundControlFrames / 2; ++i) { - EXPECT_EQ(0, nghttp2_submit_ping(client_->session(), NGHTTP2_FLAG_NONE, nullptr)); - } - // The number of outbound frames should be half of max so the connection should not be - // terminated. - EXPECT_NO_THROW(client_->sendPendingFrames()); - EXPECT_EQ(ack_count, kMaxOutboundControlFrames + kMaxOutboundControlFrames / 2); - - // 1 more ping frame should overflow the outbound frame limit. - EXPECT_EQ(0, nghttp2_submit_ping(client_->session(), NGHTTP2_FLAG_NONE, nullptr)); - EXPECT_THROW(client_->sendPendingFrames(), ServerCodecError); -} - -// Verify that codec detects flood of outbound HEADER frames -TEST_P(Http2CodecImplTest, ResponseHeadersFlood) { - initialize(); - - TestRequestHeaderMapImpl request_headers; - HttpTestUtility::addDefaultHeaders(request_headers); - EXPECT_CALL(request_decoder_, decodeHeaders_(_, false)); - request_encoder_->encodeHeaders(request_headers, false); - - int frame_count = 0; - Buffer::OwnedImpl buffer; - ON_CALL(server_connection_, write(_, _)) - .WillByDefault(Invoke([&buffer, &frame_count](Buffer::Instance& frame, bool) { - ++frame_count; - buffer.move(frame); - })); - - TestResponseHeaderMapImpl response_headers{{":status", "200"}}; - for (uint32_t i = 0; i < CommonUtility::OptionsLimits::DEFAULT_MAX_OUTBOUND_FRAMES + 1; ++i) { - EXPECT_NO_THROW(response_encoder_->encodeHeaders(response_headers, false)); - } - // Presently flood mitigation is done only when processing downstream data - // So we need to send stream from downstream client to trigger mitigation - EXPECT_EQ(0, nghttp2_submit_ping(client_->session(), NGHTTP2_FLAG_NONE, nullptr)); - EXPECT_THROW(client_->sendPendingFrames(), ServerCodecError); - - EXPECT_EQ(frame_count, CommonUtility::OptionsLimits::DEFAULT_MAX_OUTBOUND_FRAMES + 1); - EXPECT_EQ(1, server_stats_store_.counter("http2.outbound_flood").value()); -} - -// Verify that codec detects flood of outbound DATA frames -TEST_P(Http2CodecImplTest, ResponseDataFlood) { - initialize(); - - TestRequestHeaderMapImpl request_headers; - HttpTestUtility::addDefaultHeaders(request_headers); - EXPECT_CALL(request_decoder_, decodeHeaders_(_, false)); - request_encoder_->encodeHeaders(request_headers, false); - - int frame_count = 0; - Buffer::OwnedImpl buffer; - ON_CALL(server_connection_, write(_, _)) - .WillByDefault(Invoke([&buffer, &frame_count](Buffer::Instance& frame, bool) { - ++frame_count; - buffer.move(frame); - })); - - TestResponseHeaderMapImpl response_headers{{":status", "200"}}; - response_encoder_->encodeHeaders(response_headers, false); - // Account for the single HEADERS frame above - for (uint32_t i = 0; i < CommonUtility::OptionsLimits::DEFAULT_MAX_OUTBOUND_FRAMES; ++i) { - Buffer::OwnedImpl data("0"); - EXPECT_NO_THROW(response_encoder_->encodeData(data, false)); - } - // Presently flood mitigation is done only when processing downstream data - // So we need to send stream from downstream client to trigger mitigation - EXPECT_EQ(0, nghttp2_submit_ping(client_->session(), NGHTTP2_FLAG_NONE, nullptr)); - EXPECT_THROW(client_->sendPendingFrames(), ServerCodecError); - - EXPECT_EQ(frame_count, CommonUtility::OptionsLimits::DEFAULT_MAX_OUTBOUND_FRAMES + 1); - EXPECT_EQ(1, server_stats_store_.counter("http2.outbound_flood").value()); -} - -// Verify that codec allows outbound DATA flood when mitigation is disabled -TEST_P(Http2CodecImplTest, ResponseDataFloodMitigationDisabled) { - max_outbound_control_frames_ = 2147483647; - initialize(); - - TestRequestHeaderMapImpl request_headers; - HttpTestUtility::addDefaultHeaders(request_headers); - EXPECT_CALL(request_decoder_, decodeHeaders_(_, false)); - request_encoder_->encodeHeaders(request_headers, false); - - // +2 is to account for HEADERS and PING ACK, that is used to trigger mitigation - EXPECT_CALL(server_connection_, write(_, _)) - .Times(CommonUtility::OptionsLimits::DEFAULT_MAX_OUTBOUND_FRAMES + 2); - EXPECT_CALL(response_decoder_, decodeHeaders_(_, false)).Times(1); - EXPECT_CALL(response_decoder_, decodeData(_, false)) - .Times(CommonUtility::OptionsLimits::DEFAULT_MAX_OUTBOUND_FRAMES); - TestResponseHeaderMapImpl response_headers{{":status", "200"}}; - response_encoder_->encodeHeaders(response_headers, false); - // Account for the single HEADERS frame above - for (uint32_t i = 0; i < CommonUtility::OptionsLimits::DEFAULT_MAX_OUTBOUND_FRAMES; ++i) { - Buffer::OwnedImpl data("0"); - EXPECT_NO_THROW(response_encoder_->encodeData(data, false)); - } - // Presently flood mitigation is done only when processing downstream data - // So we need to send stream from downstream client to trigger mitigation - EXPECT_EQ(0, nghttp2_submit_ping(client_->session(), NGHTTP2_FLAG_NONE, nullptr)); - EXPECT_NO_THROW(client_->sendPendingFrames()); -} - -// Verify that outbound frame counter decreases when send buffer is drained -TEST_P(Http2CodecImplTest, ResponseDataFloodCounterReset) { - static const int kMaxOutboundFrames = 100; - max_outbound_frames_ = kMaxOutboundFrames; - initialize(); - - TestRequestHeaderMapImpl request_headers; - HttpTestUtility::addDefaultHeaders(request_headers); - EXPECT_CALL(request_decoder_, decodeHeaders_(_, false)); - request_encoder_->encodeHeaders(request_headers, false); - - int frame_count = 0; - Buffer::OwnedImpl buffer; - ON_CALL(server_connection_, write(_, _)) - .WillByDefault(Invoke([&buffer, &frame_count](Buffer::Instance& frame, bool) { - ++frame_count; - buffer.move(frame); - })); - - TestResponseHeaderMapImpl response_headers{{":status", "200"}}; - response_encoder_->encodeHeaders(response_headers, false); - // Account for the single HEADERS frame above - for (uint32_t i = 0; i < kMaxOutboundFrames - 1; ++i) { - Buffer::OwnedImpl data("0"); - EXPECT_NO_THROW(response_encoder_->encodeData(data, false)); - } - - EXPECT_EQ(frame_count, kMaxOutboundFrames); - // Drain kMaxOutboundFrames / 2 slices from the send buffer - buffer.drain(buffer.length() / 2); - - for (uint32_t i = 0; i < kMaxOutboundFrames / 2 + 1; ++i) { - Buffer::OwnedImpl data("0"); - EXPECT_NO_THROW(response_encoder_->encodeData(data, false)); - } - - // Presently flood mitigation is done only when processing downstream data - // So we need to send a frame from downstream client to trigger mitigation - EXPECT_EQ(0, nghttp2_submit_ping(client_->session(), NGHTTP2_FLAG_NONE, nullptr)); - EXPECT_THROW(client_->sendPendingFrames(), ServerCodecError); -} - -// Verify that control frames are added to the counter of outbound frames of all types. -TEST_P(Http2CodecImplTest, PingStacksWithDataFlood) { - initialize(); - - TestRequestHeaderMapImpl request_headers; - HttpTestUtility::addDefaultHeaders(request_headers); - EXPECT_CALL(request_decoder_, decodeHeaders_(_, false)); - request_encoder_->encodeHeaders(request_headers, false); - - int frame_count = 0; - Buffer::OwnedImpl buffer; - ON_CALL(server_connection_, write(_, _)) - .WillByDefault(Invoke([&buffer, &frame_count](Buffer::Instance& frame, bool) { - ++frame_count; - buffer.move(frame); - })); - - TestResponseHeaderMapImpl response_headers{{":status", "200"}}; - response_encoder_->encodeHeaders(response_headers, false); - // Account for the single HEADERS frame above - for (uint32_t i = 0; i < CommonUtility::OptionsLimits::DEFAULT_MAX_OUTBOUND_FRAMES - 1; ++i) { - Buffer::OwnedImpl data("0"); - EXPECT_NO_THROW(response_encoder_->encodeData(data, false)); - } - // Send one PING frame above the outbound queue size limit - EXPECT_EQ(0, nghttp2_submit_ping(client_->session(), NGHTTP2_FLAG_NONE, nullptr)); - EXPECT_THROW(client_->sendPendingFrames(), ServerCodecError); - - EXPECT_EQ(frame_count, CommonUtility::OptionsLimits::DEFAULT_MAX_OUTBOUND_FRAMES); - EXPECT_EQ(1, server_stats_store_.counter("http2.outbound_flood").value()); -} - -TEST_P(Http2CodecImplTest, PriorityFlood) { - priorityFlood(); - EXPECT_THROW(client_->sendPendingFrames(), ServerCodecError); -} - -TEST_P(Http2CodecImplTest, PriorityFloodOverride) { - max_inbound_priority_frames_per_stream_ = 2147483647; - - priorityFlood(); - EXPECT_NO_THROW(client_->sendPendingFrames()); -} - -TEST_P(Http2CodecImplTest, WindowUpdateFlood) { - windowUpdateFlood(); - EXPECT_THROW(client_->sendPendingFrames(), ServerCodecError); -} - -TEST_P(Http2CodecImplTest, WindowUpdateFloodOverride) { - max_inbound_window_update_frames_per_data_frame_sent_ = 2147483647; - windowUpdateFlood(); - EXPECT_NO_THROW(client_->sendPendingFrames()); -} - -TEST_P(Http2CodecImplTest, EmptyDataFlood) { - Buffer::OwnedImpl data; - emptyDataFlood(data); - EXPECT_CALL(request_decoder_, decodeData(_, false)); - auto status = server_wrapper_.dispatch(data, *server_); - EXPECT_FALSE(status.ok()); - EXPECT_TRUE(isBufferFloodError(status)); -} - -TEST_P(Http2CodecImplTest, EmptyDataFloodOverride) { - max_consecutive_inbound_frames_with_empty_payload_ = 2147483647; - Buffer::OwnedImpl data; - emptyDataFlood(data); - EXPECT_CALL(request_decoder_, decodeData(_, false)) - .Times( - CommonUtility::OptionsLimits::DEFAULT_MAX_CONSECUTIVE_INBOUND_FRAMES_WITH_EMPTY_PAYLOAD + - 1); - auto status = server_wrapper_.dispatch(data, *server_); - EXPECT_TRUE(status.ok()); -} - -// CONNECT without upgrade type gets tagged with "bytestream" -TEST_P(Http2CodecImplTest, ConnectTest) { - client_http2_options_.set_allow_connect(true); - server_http2_options_.set_allow_connect(true); - initialize(); - MockStreamCallbacks callbacks; - request_encoder_->getStream().addCallbacks(callbacks); - - TestRequestHeaderMapImpl request_headers; - HttpTestUtility::addDefaultHeaders(request_headers); - request_headers.setReferenceKey(Headers::get().Method, Http::Headers::get().MethodValues.Connect); - TestRequestHeaderMapImpl expected_headers; - HttpTestUtility::addDefaultHeaders(expected_headers); - expected_headers.setReferenceKey(Headers::get().Method, - Http::Headers::get().MethodValues.Connect); - expected_headers.setReferenceKey(Headers::get().Protocol, "bytestream"); - EXPECT_CALL(request_decoder_, decodeHeaders_(HeaderMapEqual(&expected_headers), false)); - request_encoder_->encodeHeaders(request_headers, false); -} - -class TestNghttp2SessionFactory; - -// Test client for H/2 METADATA frame edge cases. -class MetadataTestClientConnectionImpl : public TestClientConnectionImpl { -public: - MetadataTestClientConnectionImpl( - Network::Connection& connection, Http::ConnectionCallbacks& callbacks, Stats::Scope& scope, - const envoy::config::core::v3::Http2ProtocolOptions& http2_options, - uint32_t max_request_headers_kb, uint32_t max_request_headers_count, - Nghttp2SessionFactory& http2_session_factory) - : TestClientConnectionImpl(connection, callbacks, scope, http2_options, - max_request_headers_kb, max_request_headers_count, - http2_session_factory) {} - - // Overrides TestClientConnectionImpl::submitMetadata(). - bool submitMetadata(const MetadataMapVector& metadata_map_vector, int32_t stream_id) override { - // Creates metadata payload. - encoder_.createPayload(metadata_map_vector); - for (uint8_t flags : encoder_.payloadFrameFlagBytes()) { - int result = nghttp2_submit_extension(session(), ::Envoy::Http::METADATA_FRAME_TYPE, flags, - stream_id, nullptr); - if (result != 0) { - return false; - } - } - // Triggers nghttp2 to populate the payloads of the METADATA frames. - int result = nghttp2_session_send(session()); - return result == 0; - } - -protected: - friend class TestNghttp2SessionFactory; - - Http::Http2::MetadataEncoder encoder_; -}; - -class TestNghttp2SessionFactory : public Nghttp2SessionFactory { -public: - ~TestNghttp2SessionFactory() override { - nghttp2_session_callbacks_del(callbacks_); - nghttp2_option_del(options_); - } - - nghttp2_session* create(const nghttp2_session_callbacks*, ConnectionImpl* connection, - const nghttp2_option*) override { - // Only need to provide callbacks required to send METADATA frames. - nghttp2_session_callbacks_new(&callbacks_); - nghttp2_session_callbacks_set_pack_extension_callback( - callbacks_, - [](nghttp2_session*, uint8_t* data, size_t length, const nghttp2_frame*, - void* user_data) -> ssize_t { - // Double cast required due to multiple inheritance. - return static_cast( - static_cast(user_data)) - ->encoder_.packNextFramePayload(data, length); - }); - nghttp2_session_callbacks_set_send_callback( - callbacks_, - [](nghttp2_session*, const uint8_t* data, size_t length, int, void* user_data) -> ssize_t { - // Cast down to MetadataTestClientConnectionImpl to leverage friendship. - return static_cast( - static_cast(user_data)) - ->onSend(data, length); - }); - nghttp2_option_new(&options_); - nghttp2_option_set_user_recv_extension_type(options_, METADATA_FRAME_TYPE); - nghttp2_session* session; - nghttp2_session_client_new2(&session, callbacks_, connection, options_); - return session; - } - - void init(nghttp2_session*, ConnectionImpl*, - const envoy::config::core::v3::Http2ProtocolOptions&) override {} - -private: - nghttp2_session_callbacks* callbacks_; - nghttp2_option* options_; -}; - -class Http2CodecMetadataTest : public Http2CodecImplTestFixture, public ::testing::Test { -public: - Http2CodecMetadataTest() = default; - -protected: - void initialize() override { - allow_metadata_ = true; - http2OptionsFromTuple(client_http2_options_, client_settings_); - http2OptionsFromTuple(server_http2_options_, server_settings_); - client_ = std::make_unique( - client_connection_, client_callbacks_, client_stats_store_, client_http2_options_, - max_request_headers_kb_, max_response_headers_count_, http2_session_factory_); - server_ = std::make_unique( - server_connection_, server_callbacks_, server_stats_store_, server_http2_options_, - max_request_headers_kb_, max_request_headers_count_, headers_with_underscores_action_); - ON_CALL(client_connection_, write(_, _)) - .WillByDefault(Invoke([&](Buffer::Instance& data, bool) -> void { - ASSERT_TRUE(server_wrapper_.dispatch(data, *server_).ok()); - })); - ON_CALL(server_connection_, write(_, _)) - .WillByDefault(Invoke([&](Buffer::Instance& data, bool) -> void { - ASSERT_TRUE(client_wrapper_.dispatch(data, *client_).ok()); - })); - } - -private: - TestNghttp2SessionFactory http2_session_factory_; -}; - -// Validates noop handling of METADATA frames without a known stream ID. -// This is required per RFC 7540, section 5.1.1, which states that stream ID = 0 can be used for -// "connection control" messages, and per the H2 METADATA spec (source/docs/h2_metadata.md), which -// states that these frames can be received prior to the headers. -TEST_F(Http2CodecMetadataTest, UnknownStreamId) { - initialize(); - MetadataMap metadata_map = {{"key", "value"}}; - MetadataMapVector metadata_vector; - metadata_vector.emplace_back(std::make_unique(metadata_map)); - // SETTINGS are required as part of the preface. - ASSERT_EQ(nghttp2_submit_settings(client_->session(), NGHTTP2_FLAG_NONE, nullptr, 0), 0); - // Validate both the ID = 0 special case and a non-zero ID not already bound to a stream (any ID > - // 0 for this test). - EXPECT_TRUE(client_->submitMetadata(metadata_vector, 0)); - EXPECT_TRUE(client_->submitMetadata(metadata_vector, 1000)); -} - -} // namespace Http2 -} // namespace Legacy -} // namespace Http -} // namespace Envoy diff --git a/test/common/http/http2/codec_impl_test.cc b/test/common/http/http2/codec_impl_test.cc index fa1c92346e73..1deb3c412284 100644 --- a/test/common/http/http2/codec_impl_test.cc +++ b/test/common/http/http2/codec_impl_test.cc @@ -7,7 +7,6 @@ #include "common/http/exception.h" #include "common/http/header_map_impl.h" #include "common/http/http2/codec_impl.h" -#include "common/runtime/runtime_features.h" #include "test/common/http/common.h" #include "test/common/http/http2/http2_frame.h" @@ -74,7 +73,7 @@ class Http2CodecImplTestFixture { }; struct ConnectionWrapper { - Http::Status dispatch(const Buffer::Instance& data, Connection& connection) { + Http::Status dispatch(const Buffer::Instance& data, ConnectionImpl& connection) { Http::Status status = Http::okStatus(); buffer_.add(data); if (!dispatching_) { @@ -129,23 +128,13 @@ class Http2CodecImplTestFixture { virtual void initialize() { http2OptionsFromTuple(client_http2_options_, client_settings_); http2OptionsFromTuple(server_http2_options_, server_settings_); - if (Runtime::runtimeFeatureEnabled("envoy.reloadable_features.new_codec_behavior")) { - client_ = std::make_unique( - client_connection_, client_callbacks_, client_stats_store_, client_http2_options_, - max_request_headers_kb_, max_response_headers_count_, - ProdNghttp2SessionFactoryNew::get()); - server_ = std::make_unique( - server_connection_, server_callbacks_, server_stats_store_, server_http2_options_, - max_request_headers_kb_, max_request_headers_count_, headers_with_underscores_action_); - } else { - client_ = std::make_unique( - client_connection_, client_callbacks_, client_stats_store_, client_http2_options_, - max_request_headers_kb_, max_response_headers_count_, - ProdNghttp2SessionFactoryLegacy::get()); - server_ = std::make_unique( - server_connection_, server_callbacks_, server_stats_store_, server_http2_options_, - max_request_headers_kb_, max_request_headers_count_, headers_with_underscores_action_); - } + client_ = std::make_unique( + client_connection_, client_callbacks_, client_stats_store_, client_http2_options_, + max_request_headers_kb_, max_response_headers_count_, ProdNghttp2SessionFactory::get()); + server_ = std::make_unique( + server_connection_, server_callbacks_, server_stats_store_, server_http2_options_, + max_request_headers_kb_, max_request_headers_count_, headers_with_underscores_action_); + request_encoder_ = &client_->newStream(response_decoder_); setupDefaultConnectionMocks(); @@ -240,13 +229,13 @@ class Http2CodecImplTestFixture { envoy::config::core::v3::Http2ProtocolOptions client_http2_options_; NiceMock client_connection_; MockConnectionCallbacks client_callbacks_; - std::unique_ptr client_; + std::unique_ptr client_; ConnectionWrapper client_wrapper_; Stats::TestUtil::TestStore server_stats_store_; envoy::config::core::v3::Http2ProtocolOptions server_http2_options_; NiceMock server_connection_; MockServerConnectionCallbacks server_callbacks_; - std::unique_ptr server_; + std::unique_ptr server_; ConnectionWrapper server_wrapper_; MockResponseDecoder response_decoder_; RequestEncoder* request_encoder_; @@ -882,21 +871,21 @@ TEST_P(Http2CodecImplFlowControlTest, TestFlowControlInPendingSendData) { // stream. EXPECT_EQ(0, nghttp2_session_get_stream_local_window_size(server_->session(), 1)); EXPECT_EQ(0, nghttp2_session_get_stream_remote_window_size(client_->session(), 1)); - EXPECT_EQ(initial_stream_window, server_->getStreamUnconsumedBytes(1)); + EXPECT_EQ(initial_stream_window, server_->getStream(1)->unconsumed_bytes_); // Now that the flow control window is full, further data causes the send buffer to back up. Buffer::OwnedImpl more_long_data(std::string(initial_stream_window, 'a')); request_encoder_->encodeData(more_long_data, false); - EXPECT_EQ(initial_stream_window, client_->getStreamPendingSendDataLength(1)); + EXPECT_EQ(initial_stream_window, client_->getStream(1)->pending_send_data_.length()); EXPECT_EQ(initial_stream_window, TestUtility::findGauge(client_stats_store_, "http2.pending_send_bytes")->value()); - EXPECT_EQ(initial_stream_window, server_->getStreamUnconsumedBytes(1)); + EXPECT_EQ(initial_stream_window, server_->getStream(1)->unconsumed_bytes_); // If we go over the limit, the stream callbacks should fire. EXPECT_CALL(callbacks, onAboveWriteBufferHighWatermark()); Buffer::OwnedImpl last_byte("!"); request_encoder_->encodeData(last_byte, false); - EXPECT_EQ(initial_stream_window + 1, client_->getStreamPendingSendDataLength(1)); + EXPECT_EQ(initial_stream_window + 1, client_->getStream(1)->pending_send_data_.length()); EXPECT_EQ(initial_stream_window + 1, TestUtility::findGauge(client_stats_store_, "http2.pending_send_bytes")->value()); @@ -941,7 +930,7 @@ TEST_P(Http2CodecImplFlowControlTest, TestFlowControlInPendingSendData) { EXPECT_CALL(callbacks2, onBelowWriteBufferLowWatermark()).Times(0); EXPECT_CALL(callbacks3, onBelowWriteBufferLowWatermark()); server_->getStream(1)->readDisable(false); - EXPECT_EQ(0, client_->getStreamPendingSendDataLength(1)); + EXPECT_EQ(0, client_->getStream(1)->pending_send_data_.length()); EXPECT_EQ(0, TestUtility::findGauge(client_stats_store_, "http2.pending_send_bytes")->value()); // The extra 1 byte sent won't trigger another window update, so the final window should be the // initial window minus the last 1 byte flush from the client to server. @@ -986,7 +975,7 @@ TEST_P(Http2CodecImplFlowControlTest, EarlyResetRestoresWindow) { // stream. EXPECT_EQ(0, nghttp2_session_get_stream_local_window_size(server_->session(), 1)); EXPECT_EQ(0, nghttp2_session_get_stream_remote_window_size(client_->session(), 1)); - EXPECT_EQ(initial_stream_window, server_->getStreamUnconsumedBytes(1)); + EXPECT_EQ(initial_stream_window, server_->getStream(1)->unconsumed_bytes_); EXPECT_GT(initial_connection_window, nghttp2_session_get_remote_window_size(client_->session())); EXPECT_CALL(server_stream_callbacks_, @@ -1028,7 +1017,7 @@ TEST_P(Http2CodecImplFlowControlTest, FlowControlPendingRecvData) { // the recv buffer can be overrun by a client which negotiates a larger // SETTINGS_MAX_FRAME_SIZE but there's no current easy way to tweak that in // envoy (without sending raw HTTP/2 frames) so we lower the buffer limit instead. - server_->setStreamWriteBufferWatermarks(1, 10, 20); + server_->getStream(1)->setWriteBufferWatermarks(10, 20); EXPECT_CALL(request_decoder_, decodeData(_, false)); Buffer::OwnedImpl data(std::string(40, 'a')); @@ -1226,23 +1215,13 @@ class Http2CodecImplStreamLimitTest : public Http2CodecImplTest {}; TEST_P(Http2CodecImplStreamLimitTest, MaxClientStreams) { http2OptionsFromTuple(client_http2_options_, ::testing::get<0>(GetParam())); http2OptionsFromTuple(server_http2_options_, ::testing::get<1>(GetParam())); - if (Runtime::runtimeFeatureEnabled("envoy.reloadable_features.new_codec_behavior")) { - client_ = std::make_unique( - client_connection_, client_callbacks_, client_stats_store_, client_http2_options_, - max_request_headers_kb_, max_response_headers_count_, ProdNghttp2SessionFactoryNew::get()); - server_ = std::make_unique( - server_connection_, server_callbacks_, server_stats_store_, server_http2_options_, - max_request_headers_kb_, max_request_headers_count_, headers_with_underscores_action_); + client_ = std::make_unique( + client_connection_, client_callbacks_, client_stats_store_, client_http2_options_, + max_request_headers_kb_, max_response_headers_count_, ProdNghttp2SessionFactory::get()); + server_ = std::make_unique( + server_connection_, server_callbacks_, server_stats_store_, server_http2_options_, + max_request_headers_kb_, max_request_headers_count_, headers_with_underscores_action_); - } else { - client_ = std::make_unique( - client_connection_, client_callbacks_, client_stats_store_, client_http2_options_, - max_request_headers_kb_, max_response_headers_count_, - ProdNghttp2SessionFactoryLegacy::get()); - server_ = std::make_unique( - server_connection_, server_callbacks_, server_stats_store_, server_http2_options_, - max_request_headers_kb_, max_request_headers_count_, headers_with_underscores_action_); - } for (int i = 0; i < 101; ++i) { request_encoder_ = &client_->newStream(response_decoder_); setupDefaultConnectionMocks(); @@ -2052,64 +2031,50 @@ TEST_P(Http2CodecImplTest, ConnectTest) { request_encoder_->encodeHeaders(request_headers, false); } -template class TestNghttp2SessionFactory; +class TestNghttp2SessionFactory; // Test client for H/2 METADATA frame edge cases. -template -class MetadataTestClientConnectionImpl : public TestClientConnectionImplType { +class MetadataTestClientConnectionImpl : public TestClientConnectionImpl { public: MetadataTestClientConnectionImpl( Network::Connection& connection, Http::ConnectionCallbacks& callbacks, Stats::Scope& scope, const envoy::config::core::v3::Http2ProtocolOptions& http2_options, uint32_t max_request_headers_kb, uint32_t max_request_headers_count, - typename TestClientConnectionImplType::SessionFactory& http2_session_factory) - : TestClientConnectionImplType(connection, callbacks, scope, http2_options, - max_request_headers_kb, max_request_headers_count, - http2_session_factory) {} + Nghttp2SessionFactory& http2_session_factory) + : TestClientConnectionImpl(connection, callbacks, scope, http2_options, + max_request_headers_kb, max_request_headers_count, + http2_session_factory) {} // Overrides TestClientConnectionImpl::submitMetadata(). bool submitMetadata(const MetadataMapVector& metadata_map_vector, int32_t stream_id) override { // Creates metadata payload. encoder_.createPayload(metadata_map_vector); for (uint8_t flags : encoder_.payloadFrameFlagBytes()) { - int result = - nghttp2_submit_extension(TestClientConnectionImplType::session(), - ::Envoy::Http::METADATA_FRAME_TYPE, flags, stream_id, nullptr); + int result = nghttp2_submit_extension(session(), ::Envoy::Http::METADATA_FRAME_TYPE, flags, + stream_id, nullptr); if (result != 0) { return false; } } // Triggers nghttp2 to populate the payloads of the METADATA frames. - int result = nghttp2_session_send(TestClientConnectionImplType::session()); + int result = nghttp2_session_send(session()); return result == 0; } protected: - template friend class TestNghttp2SessionFactory; + friend class TestNghttp2SessionFactory; MetadataEncoder encoder_; }; -using MetadataTestClientConnectionImplNew = - MetadataTestClientConnectionImpl; -using MetadataTestClientConnectionImplLegacy = - MetadataTestClientConnectionImpl; - -struct Nghttp2SessionFactoryDeleter { - virtual ~Nghttp2SessionFactoryDeleter() = default; -}; - -template -class TestNghttp2SessionFactory : public Nghttp2SessionFactoryType, - public Nghttp2SessionFactoryDeleter { +class TestNghttp2SessionFactory : public Nghttp2SessionFactory { public: ~TestNghttp2SessionFactory() override { nghttp2_session_callbacks_del(callbacks_); nghttp2_option_del(options_); } - nghttp2_session* create(const nghttp2_session_callbacks*, - typename Nghttp2SessionFactoryType::ConnectionImplType* connection, + nghttp2_session* create(const nghttp2_session_callbacks*, ConnectionImpl* connection, const nghttp2_option*) override { // Only need to provide callbacks required to send METADATA frames. nghttp2_session_callbacks_new(&callbacks_); @@ -2118,18 +2083,16 @@ class TestNghttp2SessionFactory : public Nghttp2SessionFactoryType, [](nghttp2_session*, uint8_t* data, size_t length, const nghttp2_frame*, void* user_data) -> ssize_t { // Double cast required due to multiple inheritance. - return static_cast*>( - static_cast( - user_data)) + return static_cast( + static_cast(user_data)) ->encoder_.packNextFramePayload(data, length); }); nghttp2_session_callbacks_set_send_callback( callbacks_, [](nghttp2_session*, const uint8_t* data, size_t length, int, void* user_data) -> ssize_t { // Cast down to MetadataTestClientConnectionImpl to leverage friendship. - return static_cast*>( - static_cast( - user_data)) + return static_cast( + static_cast(user_data)) ->onSend(data, length); }); nghttp2_option_new(&options_); @@ -2139,7 +2102,7 @@ class TestNghttp2SessionFactory : public Nghttp2SessionFactoryType, return session; } - void init(nghttp2_session*, typename Nghttp2SessionFactoryType::ConnectionImplType*, + void init(nghttp2_session*, ConnectionImpl*, const envoy::config::core::v3::Http2ProtocolOptions&) override {} private: @@ -2147,12 +2110,6 @@ class TestNghttp2SessionFactory : public Nghttp2SessionFactoryType, nghttp2_option* options_; }; -using TestNghttp2SessionFactoryNew = - TestNghttp2SessionFactory; -using TestNghttp2SessionFactoryLegacy = - TestNghttp2SessionFactory; - class Http2CodecMetadataTest : public Http2CodecImplTestFixture, public ::testing::Test { public: Http2CodecMetadataTest() = default; @@ -2162,27 +2119,12 @@ class Http2CodecMetadataTest : public Http2CodecImplTestFixture, public ::testin allow_metadata_ = true; http2OptionsFromTuple(client_http2_options_, client_settings_); http2OptionsFromTuple(server_http2_options_, server_settings_); - if (Runtime::runtimeFeatureEnabled("envoy.reloadable_features.new_codec_behavior")) { - std::unique_ptr session_factory = - std::make_unique(); - client_ = std::make_unique( - client_connection_, client_callbacks_, client_stats_store_, client_http2_options_, - max_request_headers_kb_, max_response_headers_count_, *session_factory); - server_ = std::make_unique( - server_connection_, server_callbacks_, server_stats_store_, server_http2_options_, - max_request_headers_kb_, max_request_headers_count_, headers_with_underscores_action_); - http2_session_factory_ = std::move(session_factory); - } else { - std::unique_ptr session_factory = - std::make_unique(); - client_ = std::make_unique( - client_connection_, client_callbacks_, client_stats_store_, client_http2_options_, - max_request_headers_kb_, max_response_headers_count_, *session_factory); - server_ = std::make_unique( - server_connection_, server_callbacks_, server_stats_store_, server_http2_options_, - max_request_headers_kb_, max_request_headers_count_, headers_with_underscores_action_); - http2_session_factory_ = std::move(session_factory); - } + client_ = std::make_unique( + client_connection_, client_callbacks_, client_stats_store_, client_http2_options_, + max_request_headers_kb_, max_response_headers_count_, http2_session_factory_); + server_ = std::make_unique( + server_connection_, server_callbacks_, server_stats_store_, server_http2_options_, + max_request_headers_kb_, max_request_headers_count_, headers_with_underscores_action_); ON_CALL(client_connection_, write(_, _)) .WillByDefault(Invoke([&](Buffer::Instance& data, bool) -> void { ASSERT_TRUE(server_wrapper_.dispatch(data, *server_).ok()); @@ -2194,7 +2136,7 @@ class Http2CodecMetadataTest : public Http2CodecImplTestFixture, public ::testin } private: - std::unique_ptr http2_session_factory_; + TestNghttp2SessionFactory http2_session_factory_; }; // Validates noop handling of METADATA frames without a known stream ID. diff --git a/test/common/http/http2/codec_impl_test_util.h b/test/common/http/http2/codec_impl_test_util.h index 2ba9f545a20c..1eb8bd581a9e 100644 --- a/test/common/http/http2/codec_impl_test_util.h +++ b/test/common/http/http2/codec_impl_test_util.h @@ -3,7 +3,6 @@ #include "envoy/http/codec.h" #include "common/http/http2/codec_impl.h" -#include "common/http/http2/codec_impl_legacy.h" #include "common/http/utility.h" namespace Envoy { @@ -33,7 +32,7 @@ class TestCodecSettingsProvider { return it->second; } - // protected: +protected: // Stores SETTINGS parameters contained in |settings_frame| to make them available via // getRemoteSettingsParameterValue(). void onSettingsFrame(const nghttp2_settings& settings_frame) { @@ -58,23 +57,9 @@ class TestCodecSettingsProvider { std::unordered_map settings_; }; -struct ServerCodecFacade : public virtual Connection { - virtual nghttp2_session* session() PURE; - virtual Http::Stream* getStream(int32_t stream_id) PURE; - virtual uint32_t getStreamUnconsumedBytes(int32_t stream_id) PURE; - virtual void setStreamWriteBufferWatermarks(int32_t stream_id, uint32_t low_watermark, - uint32_t high_watermark) PURE; -}; - -class TestServerConnection : public TestCodecStatsProvider, - public TestCodecSettingsProvider, - public ServerCodecFacade { -public: - TestServerConnection(Stats::Scope& scope) : TestCodecStatsProvider(scope) {} -}; - -template -class TestServerConnectionImpl : public TestServerConnection, public CodecImplType { +class TestServerConnectionImpl : public TestCodecStatsProvider, + public ServerConnectionImpl, + public TestCodecSettingsProvider { public: TestServerConnectionImpl( Network::Connection& connection, ServerConnectionCallbacks& callbacks, Stats::Scope& scope, @@ -82,94 +67,50 @@ class TestServerConnectionImpl : public TestServerConnection, public CodecImplTy uint32_t max_request_headers_kb, uint32_t max_request_headers_count, envoy::config::core::v3::HttpProtocolOptions::HeadersWithUnderscoresAction headers_with_underscores_action) - : TestServerConnection(scope), - CodecImplType(connection, callbacks, http2CodecStats(), http2_options, - max_request_headers_kb, max_request_headers_count, - headers_with_underscores_action) {} - - // ServerCodecFacade - nghttp2_session* session() override { return CodecImplType::session_; } - Http::Stream* getStream(int32_t stream_id) override { - return CodecImplType::getStream(stream_id); - } - uint32_t getStreamUnconsumedBytes(int32_t stream_id) override { - return CodecImplType::getStream(stream_id)->unconsumed_bytes_; - } - void setStreamWriteBufferWatermarks(int32_t stream_id, uint32_t low_watermark, - uint32_t high_watermark) override { - CodecImplType::getStream(stream_id)->setWriteBufferWatermarks(low_watermark, high_watermark); - } + : TestCodecStatsProvider(scope), + ServerConnectionImpl(connection, callbacks, http2CodecStats(), http2_options, + max_request_headers_kb, max_request_headers_count, + headers_with_underscores_action) {} + nghttp2_session* session() { return session_; } + using ServerConnectionImpl::getStream; protected: // Overrides ServerConnectionImpl::onSettingsForTest(). void onSettingsForTest(const nghttp2_settings& settings) override { onSettingsFrame(settings); } }; -using TestServerConnectionImplLegacy = - TestServerConnectionImpl; -using TestServerConnectionImplNew = - TestServerConnectionImpl; - -struct ClientCodecFacade : public ClientConnection { - virtual nghttp2_session* session() PURE; - virtual Http::Stream* getStream(int32_t stream_id) PURE; - virtual uint64_t getStreamPendingSendDataLength(int32_t stream_id) PURE; - virtual void sendPendingFrames() PURE; - virtual bool submitMetadata(const MetadataMapVector& mm_vector, int32_t stream_id) PURE; -}; - -class TestClientConnection : public TestCodecStatsProvider, - public TestCodecSettingsProvider, - public ClientCodecFacade { -public: - TestClientConnection(Stats::Scope& scope) : TestCodecStatsProvider(scope) {} -}; - -template -class TestClientConnectionImpl : public TestClientConnection, public CodecImplType { +class TestClientConnectionImpl : public TestCodecStatsProvider, + public ClientConnectionImpl, + public TestCodecSettingsProvider { public: TestClientConnectionImpl(Network::Connection& connection, Http::ConnectionCallbacks& callbacks, Stats::Scope& scope, const envoy::config::core::v3::Http2ProtocolOptions& http2_options, uint32_t max_request_headers_kb, uint32_t max_request_headers_count, - typename CodecImplType::SessionFactory& http2_session_factory) - : TestClientConnection(scope), - CodecImplType(connection, callbacks, http2CodecStats(), http2_options, - max_request_headers_kb, max_request_headers_count, http2_session_factory) {} - - // ClientCodecFacade - RequestEncoder& newStream(ResponseDecoder& response_decoder) override { - return CodecImplType::newStream(response_decoder); - } - nghttp2_session* session() override { return CodecImplType::session_; } - Http::Stream* getStream(int32_t stream_id) override { - return CodecImplType::getStream(stream_id); - } - uint64_t getStreamPendingSendDataLength(int32_t stream_id) override { - return CodecImplType::getStream(stream_id)->pending_send_data_.length(); - } - void sendPendingFrames() override { CodecImplType::sendPendingFrames(); } + Nghttp2SessionFactory& http2_session_factory) + : TestCodecStatsProvider(scope), + ClientConnectionImpl(connection, callbacks, http2CodecStats(), http2_options, + max_request_headers_kb, max_request_headers_count, + http2_session_factory) {} + + nghttp2_session* session() { return session_; } + // Submits an H/2 METADATA frame to the peer. // Returns true on success, false otherwise. - bool submitMetadata(const MetadataMapVector& mm_vector, int32_t stream_id) override { + virtual bool submitMetadata(const MetadataMapVector& mm_vector, int32_t stream_id) { UNREFERENCED_PARAMETER(mm_vector); UNREFERENCED_PARAMETER(stream_id); return false; } + using ClientConnectionImpl::getStream; + using ConnectionImpl::sendPendingFrames; + protected: // Overrides ClientConnectionImpl::onSettingsForTest(). void onSettingsForTest(const nghttp2_settings& settings) override { onSettingsFrame(settings); } }; -using TestClientConnectionImplLegacy = - TestClientConnectionImpl; -using TestClientConnectionImplNew = - TestClientConnectionImpl; - -using ProdNghttp2SessionFactoryLegacy = Envoy::Http::Legacy::Http2::ProdNghttp2SessionFactory; -using ProdNghttp2SessionFactoryNew = Envoy::Http::Http2::ProdNghttp2SessionFactory; - } // namespace Http2 } // namespace Http } // namespace Envoy diff --git a/test/common/http/http2/frame_replay_test.cc b/test/common/http/http2/frame_replay_test.cc index aadda98c8b3d..c88458e10c7e 100644 --- a/test/common/http/http2/frame_replay_test.cc +++ b/test/common/http/http2/frame_replay_test.cc @@ -26,7 +26,7 @@ class RequestFrameCommentTest : public ::testing::Test {}; class ResponseFrameCommentTest : public ::testing::Test {}; // Creates and sets up a stream to reply to. -void setupStream(ClientCodecFrameInjector& codec, TestClientConnectionImplNew& connection) { +void setupStream(ClientCodecFrameInjector& codec, TestClientConnectionImpl& connection) { codec.request_encoder_ = &connection.newStream(codec.response_decoder_); codec.request_encoder_->getStream().addCallbacks(codec.client_stream_callbacks_); // Setup a single stream to inject frames as a reply to. @@ -56,7 +56,7 @@ TEST_F(RequestFrameCommentTest, SimpleExampleHuffman) { // Validate HEADERS decode. ServerCodecFrameInjector codec; - TestServerConnectionImplNew connection( + TestServerConnectionImpl connection( codec.server_connection_, codec.server_callbacks_, codec.stats_store_, codec.options_, Http::DEFAULT_MAX_REQUEST_HEADERS_KB, Http::DEFAULT_MAX_HEADERS_COUNT, envoy::config::core::v3::HttpProtocolOptions::ALLOW); @@ -89,7 +89,7 @@ TEST_F(ResponseFrameCommentTest, SimpleExampleHuffman) { // Validate HEADERS decode. ClientCodecFrameInjector codec; - TestClientConnectionImplNew connection( + TestClientConnectionImpl connection( codec.client_connection_, codec.client_callbacks_, codec.stats_store_, codec.options_, Http::DEFAULT_MAX_REQUEST_HEADERS_KB, Http::DEFAULT_MAX_HEADERS_COUNT, ProdNghttp2SessionFactory::get()); @@ -134,7 +134,7 @@ TEST_F(RequestFrameCommentTest, SimpleExamplePlain) { // Validate HEADERS decode. ServerCodecFrameInjector codec; - TestServerConnectionImplNew connection( + TestServerConnectionImpl connection( codec.server_connection_, codec.server_callbacks_, codec.stats_store_, codec.options_, Http::DEFAULT_MAX_REQUEST_HEADERS_KB, Http::DEFAULT_MAX_HEADERS_COUNT, envoy::config::core::v3::HttpProtocolOptions::ALLOW); @@ -169,7 +169,7 @@ TEST_F(ResponseFrameCommentTest, SimpleExamplePlain) { // Validate HEADERS decode. ClientCodecFrameInjector codec; - TestClientConnectionImplNew connection( + TestClientConnectionImpl connection( codec.client_connection_, codec.client_callbacks_, codec.stats_store_, codec.options_, Http::DEFAULT_MAX_REQUEST_HEADERS_KB, Http::DEFAULT_MAX_HEADERS_COUNT, ProdNghttp2SessionFactory::get()); @@ -199,7 +199,7 @@ TEST_F(RequestFrameCommentTest, SingleByteNulCrLfInHeaderFrame) { header.frame()[offset] = c; // Play the frames back. ServerCodecFrameInjector codec; - TestServerConnectionImplNew connection( + TestServerConnectionImpl connection( codec.server_connection_, codec.server_callbacks_, codec.stats_store_, codec.options_, Http::DEFAULT_MAX_REQUEST_HEADERS_KB, Http::DEFAULT_MAX_HEADERS_COUNT, envoy::config::core::v3::HttpProtocolOptions::ALLOW); @@ -232,7 +232,7 @@ TEST_F(ResponseFrameCommentTest, SingleByteNulCrLfInHeaderFrame) { header.frame()[offset] = c; // Play the frames back. ClientCodecFrameInjector codec; - TestClientConnectionImplNew connection( + TestClientConnectionImpl connection( codec.client_connection_, codec.client_callbacks_, codec.stats_store_, codec.options_, Http::DEFAULT_MAX_REQUEST_HEADERS_KB, Http::DEFAULT_MAX_HEADERS_COUNT, ProdNghttp2SessionFactory::get()); @@ -267,7 +267,7 @@ TEST_F(RequestFrameCommentTest, SingleByteNulCrLfInHeaderField) { header.frame()[offset] = c; // Play the frames back. ServerCodecFrameInjector codec; - TestServerConnectionImplNew connection( + TestServerConnectionImpl connection( codec.server_connection_, codec.server_callbacks_, codec.stats_store_, codec.options_, Http::DEFAULT_MAX_REQUEST_HEADERS_KB, Http::DEFAULT_MAX_HEADERS_COUNT, envoy::config::core::v3::HttpProtocolOptions::ALLOW); @@ -305,7 +305,7 @@ TEST_F(ResponseFrameCommentTest, SingleByteNulCrLfInHeaderField) { header.frame()[offset] = c; // Play the frames back. ClientCodecFrameInjector codec; - TestClientConnectionImplNew connection( + TestClientConnectionImpl connection( codec.client_connection_, codec.client_callbacks_, codec.stats_store_, codec.options_, Http::DEFAULT_MAX_REQUEST_HEADERS_KB, Http::DEFAULT_MAX_HEADERS_COUNT, ProdNghttp2SessionFactory::get()); diff --git a/test/common/http/http2/request_header_fuzz_test.cc b/test/common/http/http2/request_header_fuzz_test.cc index d925ed1bb002..5dc75d58ebbb 100644 --- a/test/common/http/http2/request_header_fuzz_test.cc +++ b/test/common/http/http2/request_header_fuzz_test.cc @@ -14,7 +14,7 @@ namespace { void Replay(const Frame& frame, ServerCodecFrameInjector& codec) { // Create the server connection containing the nghttp2 session. - TestServerConnectionImplNew connection( + TestServerConnectionImpl connection( codec.server_connection_, codec.server_callbacks_, codec.stats_store_, codec.options_, Http::DEFAULT_MAX_REQUEST_HEADERS_KB, Http::DEFAULT_MAX_HEADERS_COUNT, envoy::config::core::v3::HttpProtocolOptions::ALLOW); diff --git a/test/common/http/http2/response_header_fuzz_test.cc b/test/common/http/http2/response_header_fuzz_test.cc index e73b88ab954d..8b1a5d3d0797 100644 --- a/test/common/http/http2/response_header_fuzz_test.cc +++ b/test/common/http/http2/response_header_fuzz_test.cc @@ -15,7 +15,7 @@ namespace { void Replay(const Frame& frame, ClientCodecFrameInjector& codec) { // Create the client connection containing the nghttp2 session. - TestClientConnectionImplNew connection( + TestClientConnectionImpl connection( codec.client_connection_, codec.client_callbacks_, codec.stats_store_, codec.options_, Http::DEFAULT_MAX_REQUEST_HEADERS_KB, Http::DEFAULT_MAX_HEADERS_COUNT, ProdNghttp2SessionFactory::get()); diff --git a/test/common/stats/stat_test_utility.h b/test/common/stats/stat_test_utility.h index b1df35b6d189..6b46a0f05aea 100644 --- a/test/common/stats/stat_test_utility.h +++ b/test/common/stats/stat_test_utility.h @@ -91,11 +91,6 @@ class MemoryTest { class TestStore : public IsolatedStoreImpl { public: TestStore() = default; - ~TestStore() { - counter_map_.clear(); - gauge_map_.clear(); - histogram_map_.clear(); - } // Constructs a store using a symbol table, allowing for explicit sharing. explicit TestStore(SymbolTable& symbol_table) : IsolatedStoreImpl(symbol_table) {} diff --git a/test/config/utility.cc b/test/config/utility.cc index f57e7af4286c..ea96c7ddb142 100644 --- a/test/config/utility.cc +++ b/test/config/utility.cc @@ -586,10 +586,6 @@ void ConfigHelper::addRuntimeOverride(const std::string& key, const std::string& (*static_layer->mutable_fields())[std::string(key)] = ValueUtil::stringValue(std::string(value)); } -void ConfigHelper::setLegacyCodecs() { - addRuntimeOverride("envoy.reloadable_features.new_codec_behavior", "false"); -} - void ConfigHelper::finalize(const std::vector& ports) { RELEASE_ASSERT(!finalized_, ""); diff --git a/test/config/utility.h b/test/config/utility.h index b4217d4f31f5..39bcb00a4454 100644 --- a/test/config/utility.h +++ b/test/config/utility.h @@ -229,9 +229,6 @@ class ConfigHelper { const envoy::extensions::filters::network::http_connection_manager::v3::LocalReplyConfig& config); - // Set legacy codecs to use for upstream and downstream codecs. - void setLegacyCodecs(); - private: static bool shouldBoost(envoy::config::core::v3::ApiVersion api_version) { return api_version == envoy::config::core::v3::ApiVersion::V2; diff --git a/test/extensions/filters/network/http_connection_manager/config_test.cc b/test/extensions/filters/network/http_connection_manager/config_test.cc index 5e7648ba1ce4..d1d32fdd634e 100644 --- a/test/extensions/filters/network/http_connection_manager/config_test.cc +++ b/test/extensions/filters/network/http_connection_manager/config_test.cc @@ -1619,66 +1619,6 @@ TEST_F(HttpConnectionManagerConfigTest, DefaultRequestIDExtension) { ASSERT_NE(nullptr, request_id_extension); } -TEST_F(HttpConnectionManagerConfigTest, LegacyH1Codecs) { - const std::string yaml_string = R"EOF( -codec_type: http1 -server_name: foo -stat_prefix: router -route_config: - virtual_hosts: - - name: service - domains: - - "*" - routes: - - match: - prefix: "/" - route: - cluster: cluster -http_filters: -- name: envoy.filters.http.router - )EOF"; - - envoy::extensions::filters::network::http_connection_manager::v3::HttpConnectionManager - proto_config; - TestUtility::loadFromYaml(yaml_string, proto_config); - NiceMock filter_callbacks; - EXPECT_CALL(context_.runtime_loader_.snapshot_, runtimeFeatureEnabled(_)).WillOnce(Return(false)); - auto http_connection_manager_factory = - HttpConnectionManagerFactory::createHttpConnectionManagerFactoryFromProto( - proto_config, context_, filter_callbacks); - http_connection_manager_factory(); -} - -TEST_F(HttpConnectionManagerConfigTest, LegacyH2Codecs) { - const std::string yaml_string = R"EOF( -codec_type: http2 -server_name: foo -stat_prefix: router -route_config: - virtual_hosts: - - name: service - domains: - - "*" - routes: - - match: - prefix: "/" - route: - cluster: cluster -http_filters: -- name: envoy.filters.http.router - )EOF"; - - envoy::extensions::filters::network::http_connection_manager::v3::HttpConnectionManager - proto_config; - TestUtility::loadFromYaml(yaml_string, proto_config); - NiceMock filter_callbacks; - EXPECT_CALL(context_.runtime_loader_.snapshot_, runtimeFeatureEnabled(_)).WillOnce(Return(false)); - auto http_connection_manager_factory = - HttpConnectionManagerFactory::createHttpConnectionManagerFactoryFromProto( - proto_config, context_, filter_callbacks); - http_connection_manager_factory(); -} - class FilterChainTest : public HttpConnectionManagerConfigTest { public: const std::string basic_config_ = R"EOF( diff --git a/test/integration/BUILD b/test/integration/BUILD index d16bfcbe114b..2efec82ce37b 100644 --- a/test/integration/BUILD +++ b/test/integration/BUILD @@ -8,7 +8,6 @@ load( "envoy_package", "envoy_proto_library", "envoy_select_hot_restart", - "envoy_select_legacy_codecs_in_integration_tests", "envoy_sh_test", ) @@ -577,10 +576,6 @@ envoy_cc_test_library( "ssl_utility.h", "utility.h", ], - copts = envoy_select_legacy_codecs_in_integration_tests( - ["-DENVOY_USE_LEGACY_CODECS_IN_INTEGRATION_TESTS"], - "@envoy", - ), data = ["//test/common/runtime:filesystem_test_data"], deps = [ ":server_stats_interface", @@ -612,9 +607,7 @@ envoy_cc_test_library( "//source/common/http:codec_client_lib", "//source/common/http:header_map_lib", "//source/common/http:headers_lib", - "//source/common/http/http1:codec_legacy_lib", "//source/common/http/http1:codec_lib", - "//source/common/http/http2:codec_legacy_lib", "//source/common/http/http2:codec_lib", "//source/common/local_info:local_info_lib", "//source/common/network:filter_lib", diff --git a/test/integration/api_version_integration_test.cc b/test/integration/api_version_integration_test.cc index 952c095a820e..c8bf5164b028 100644 --- a/test/integration/api_version_integration_test.cc +++ b/test/integration/api_version_integration_test.cc @@ -316,11 +316,9 @@ TEST_P(ApiVersionIntegrationTest, Eds) { TEST_P(ApiVersionIntegrationTest, Rtds) { config_helper_.addConfigModifier([this](envoy::config::bootstrap::v3::Bootstrap& bootstrap) { - if (bootstrap.mutable_layered_runtime()->layers_size() == 0) { - auto* admin_layer = bootstrap.mutable_layered_runtime()->add_layers(); - admin_layer->set_name("admin layer"); - admin_layer->mutable_admin_layer(); - } + auto* admin_layer = bootstrap.mutable_layered_runtime()->add_layers(); + admin_layer->set_name("admin layer"); + admin_layer->mutable_admin_layer(); auto* rtds_layer = bootstrap.mutable_layered_runtime()->add_layers(); rtds_layer->set_name("rtds_layer"); setupConfigSource(*rtds_layer->mutable_rtds_layer()->mutable_rtds_config()); diff --git a/test/integration/fake_upstream.cc b/test/integration/fake_upstream.cc index a6c94d91a0c4..4763c9bbfe05 100644 --- a/test/integration/fake_upstream.cc +++ b/test/integration/fake_upstream.cc @@ -12,9 +12,7 @@ #include "common/common/fmt.h" #include "common/http/header_map_impl.h" #include "common/http/http1/codec_impl.h" -#include "common/http/http1/codec_impl_legacy.h" #include "common/http/http2/codec_impl.h" -#include "common/http/http2/codec_impl_legacy.h" #include "common/network/address_impl.h" #include "common/network/listen_socket_impl.h" #include "common/network/raw_buffer_socket.h" @@ -251,29 +249,6 @@ class TestHttp1ServerConnectionImpl : public Http::Http1::ServerConnectionImpl { } }; -namespace Legacy { -class TestHttp1ServerConnectionImpl : public Http::Legacy::Http1::ServerConnectionImpl { -public: - using Http::Legacy::Http1::ServerConnectionImpl::ServerConnectionImpl; - - void onMessageComplete() override { - ServerConnectionImpl::onMessageComplete(); - - if (activeRequest().has_value() && activeRequest().value().request_decoder_) { - // Undo the read disable from the base class - we have many tests which - // waitForDisconnect after a full request has been read which will not - // receive the disconnect if reading is disabled. - activeRequest().value().response_encoder_.readDisable(false); - } - } - ~TestHttp1ServerConnectionImpl() override { - if (activeRequest().has_value()) { - activeRequest().value().response_encoder_.clearReadDisableCallsForTests(); - } - } -}; -} // namespace Legacy - FakeHttpConnection::FakeHttpConnection( FakeUpstream& fake_upstream, SharedConnectionWrapper& shared_connection, Type type, Event::TestTimeSystem& time_system, uint32_t max_request_headers_kb, @@ -286,15 +261,9 @@ FakeHttpConnection::FakeHttpConnection( // For the purpose of testing, we always have the upstream encode the trailers if any http1_settings.enable_trailers_ = true; Http::Http1::CodecStats& stats = fake_upstream.http1CodecStats(); -#ifdef ENVOY_USE_LEGACY_CODECS_IN_INTEGRATION_TESTS codec_ = std::make_unique( shared_connection_.connection(), stats, *this, http1_settings, max_request_headers_kb, max_request_headers_count, headers_with_underscores_action); -#else - codec_ = std::make_unique( - shared_connection_.connection(), stats, *this, http1_settings, max_request_headers_kb, - max_request_headers_count, headers_with_underscores_action); -#endif } else { envoy::config::core::v3::Http2ProtocolOptions http2_options = ::Envoy::Http2::Utility::initializeAndValidateOptions( @@ -302,17 +271,12 @@ FakeHttpConnection::FakeHttpConnection( http2_options.set_allow_connect(true); http2_options.set_allow_metadata(true); Http::Http2::CodecStats& stats = fake_upstream.http2CodecStats(); -#ifdef ENVOY_USE_LEGACY_CODECS_IN_INTEGRATION_TESTS codec_ = std::make_unique( shared_connection_.connection(), *this, stats, http2_options, max_request_headers_kb, max_request_headers_count, headers_with_underscores_action); -#else - codec_ = std::make_unique( - shared_connection_.connection(), *this, stats, http2_options, max_request_headers_kb, - max_request_headers_count, headers_with_underscores_action); -#endif ASSERT(type == Type::HTTP2); } + shared_connection_.connection().addReadFilter( Network::ReadFilterSharedPtr{new ReadFilter(*this)}); } diff --git a/test/integration/integration.cc b/test/integration/integration.cc index 2e4b846870e4..309548595313 100644 --- a/test/integration/integration.cc +++ b/test/integration/integration.cc @@ -286,11 +286,6 @@ BaseIntegrationTest::BaseIntegrationTest(const InstanceConstSharedPtrFn& upstrea return new Buffer::WatermarkBuffer(below_low, above_high, above_overflow); })); ON_CALL(factory_context_, api()).WillByDefault(ReturnRef(*api_)); - // In ENVOY_USE_LEGACY_CODECS_IN_INTEGRATION_TESTS mode, set runtime config to use legacy codecs. -#ifdef ENVOY_USE_LEGACY_CODECS_IN__INTEGRATION_TESTS - ENVOY_LOG_MISC(debug, "Using legacy codecs"); - setLegacyCodecs(); -#endif } BaseIntegrationTest::BaseIntegrationTest(Network::Address::IpVersion version, diff --git a/test/integration/integration.h b/test/integration/integration.h index d345b5051095..0ec9133736cb 100644 --- a/test/integration/integration.h +++ b/test/integration/integration.h @@ -191,7 +191,6 @@ class BaseIntegrationTest : protected Logger::Loggable { void skipPortUsageValidation() { config_helper_.skipPortUsageValidation(); } // Make test more deterministic by using a fixed RNG value. void setDeterministic() { deterministic_ = true; } - void setLegacyCodecs() { config_helper_.setLegacyCodecs(); } FakeHttpConnection::Type upstreamProtocol() const { return upstream_protocol_; } diff --git a/tools/code_format/check_format.py b/tools/code_format/check_format.py index ea5214dda67f..351414da436b 100755 --- a/tools/code_format/check_format.py +++ b/tools/code_format/check_format.py @@ -2,7 +2,6 @@ import argparse import common -import difflib import functools import multiprocessing import os @@ -85,21 +84,6 @@ "./source/server/admin/prometheus_stats.cc", "./tools/clang_tools/api_booster/main.cc", "./tools/clang_tools/api_booster/proto_cxx_utils.cc", "./source/common/common/version.cc") -# These triples (file1, file2, diff) represent two files, file1 and file2 that should maintain -# the diff diff. This is meant to keep these two files in sync. -CODEC_DIFFS = (("./source/common/http/http1/codec_impl.h", - "./source/common/http/http1/codec_impl_legacy.h", - "./tools/code_format/codec_diffs/http1_codec_impl_h"), - ("./source/common/http/http1/codec_impl.cc", - "./source/common/http/http1/codec_impl_legacy.cc", - "./tools/code_format/codec_diffs/http1_codec_impl_cc"), - ("./source/common/http/http2/codec_impl.h", - "./source/common/http/http2/codec_impl_legacy.h", - "./tools/code_format/codec_diffs/http2_codec_impl_h"), - ("./source/common/http/http2/codec_impl.cc", - "./source/common/http/http2/codec_impl_legacy.cc", - "./tools/code_format/codec_diffs/http2_codec_impl_cc")) - # Only one C++ file should instantiate grpc_init GRPC_INIT_ALLOWLIST = ("./source/common/grpc/google_grpc_context.cc") @@ -548,38 +532,6 @@ def fixSourceLine(line, line_number): return line -def codecDiffHelper(file1, file2, diff): - f1 = readLines(file1) - f2 = readLines(file2) - - # Create diff between two files - code_diff = list(difflib.unified_diff(f1, f2, lineterm="")) - # Compare with golden diff. - golden_diff = readLines(diff) - # It is fairly ugly to diff a diff, so return a warning to sync codec changes - # and/or update golden_diff. - if code_diff != golden_diff: - error_message = "Codecs are not synced: %s does not match %s. Update codec implementations to sync and/or update the diff manually to:\n%s" % ( - file1, file2, '\n'.join(code_diff)) - # The following line will write the diff to the file diff if it does not match. - # Do not uncomment unless you know the change is safe! - # new_diff = pathlib.Path(diff) - #new_diff.open('w') - # new_diff.write_text('\n'.join(code_diff), encoding='utf-8') - return error_message - - -def checkCodecDiffs(error_messages): - try: - for triple in CODEC_DIFFS: - codec_diff = codecDiffHelper(*triple) - if codec_diff != None: - error_messages.append(codecDiffHelper(*triple)) - return error_messages - except IOError: # for check format tests - return error_messages - - # We want to look for a call to condvar.waitFor, but there's no strong pattern # to the variable name of the condvar. If we just look for ".waitFor" we'll also # pick up time_system_.waitFor(...), and we don't want to return true for that @@ -1096,9 +1048,6 @@ def ownedDirectories(error_messages): error_messages = [] owned_directories = ownedDirectories(error_messages) - # Check codec synchronization once per run. - checkCodecDiffs(error_messages) - if os.path.isfile(target_path): error_messages += checkFormat("./" + target_path) else: diff --git a/tools/code_format/codec_diffs/http1_codec_impl_cc b/tools/code_format/codec_diffs/http1_codec_impl_cc deleted file mode 100644 index b9ea3f3ae002..000000000000 --- a/tools/code_format/codec_diffs/http1_codec_impl_cc +++ /dev/null @@ -1,35 +0,0 @@ ---- -+++ -@@ -1,4 +1,4 @@ --#include "common/http/http1/codec_impl.h" -+#include "common/http/http1/codec_impl_legacy.h" - - #include - #include -@@ -25,6 +25,7 @@ - - namespace Envoy { - namespace Http { -+namespace Legacy { - namespace Http1 { - namespace { - -@@ -48,6 +49,10 @@ - - using Http1ResponseCodeDetails = ConstSingleton; - using Http1HeaderTypes = ConstSingleton; -+using Http::Http1::CodecStats; -+using Http::Http1::HeaderKeyFormatter; -+using Http::Http1::HeaderKeyFormatterPtr; -+using Http::Http1::ProperCaseHeaderKeyFormatter; - - const StringUtil::CaseUnorderedSet& caseUnorderdSetContainingUpgradeAndHttp2Settings() { - CONSTRUCT_ON_FIRST_USE(StringUtil::CaseUnorderedSet, -@@ -1236,6 +1241,7 @@ - } - - } // namespace Http1 -+} // namespace Legacy - } // namespace Http - } // namespace Envoy - \ No newline at end of file diff --git a/tools/code_format/codec_diffs/http1_codec_impl_h b/tools/code_format/codec_diffs/http1_codec_impl_h deleted file mode 100644 index e81d2c838d5e..000000000000 --- a/tools/code_format/codec_diffs/http1_codec_impl_h +++ /dev/null @@ -1,130 +0,0 @@ ---- -+++ -@@ -24,6 +24,7 @@ - - namespace Envoy { - namespace Http { -+namespace Legacy { - namespace Http1 { - - class ConnectionImpl; -@@ -75,7 +76,8 @@ - void clearReadDisableCallsForTests() { read_disable_calls_ = 0; } - - protected: -- StreamEncoderImpl(ConnectionImpl& connection, HeaderKeyFormatter* header_key_formatter); -+ StreamEncoderImpl(ConnectionImpl& connection, -+ Http::Http1::HeaderKeyFormatter* header_key_formatter); - void encodeHeadersBase(const RequestOrResponseHeaderMap& headers, absl::optional status, - bool end_stream); - void encodeTrailersBase(const HeaderMap& headers); -@@ -114,7 +116,7 @@ - - void encodeFormattedHeader(absl::string_view key, absl::string_view value); - -- const HeaderKeyFormatter* const header_key_formatter_; -+ const Http::Http1::HeaderKeyFormatter* const header_key_formatter_; - absl::string_view details_; - }; - -@@ -123,7 +125,8 @@ - */ - class ResponseEncoderImpl : public StreamEncoderImpl, public ResponseEncoder { - public: -- ResponseEncoderImpl(ConnectionImpl& connection, HeaderKeyFormatter* header_key_formatter) -+ ResponseEncoderImpl(ConnectionImpl& connection, -+ Http::Http1::HeaderKeyFormatter* header_key_formatter) - : StreamEncoderImpl(connection, header_key_formatter) {} - - bool startedResponse() { return started_response_; } -@@ -142,7 +145,8 @@ - */ - class RequestEncoderImpl : public StreamEncoderImpl, public RequestEncoder { - public: -- RequestEncoderImpl(ConnectionImpl& connection, HeaderKeyFormatter* header_key_formatter) -+ RequestEncoderImpl(ConnectionImpl& connection, -+ Http::Http1::HeaderKeyFormatter* header_key_formatter) - : StreamEncoderImpl(connection, header_key_formatter) {} - bool upgradeRequest() const { return upgrade_request_; } - bool headRequest() const { return head_request_; } -@@ -203,7 +207,7 @@ - virtual bool supportsHttp10() { return false; } - bool maybeDirectDispatch(Buffer::Instance& data); - virtual void maybeAddSentinelBufferFragment(Buffer::WatermarkBuffer&) {} -- CodecStats& stats() { return stats_; } -+ Http::Http1::CodecStats& stats() { return stats_; } - bool enableTrailers() const { return enable_trailers_; } - - // Http::Connection -@@ -218,9 +222,9 @@ - bool strict1xxAnd204Headers() { return strict_1xx_and_204_headers_; } - - protected: -- ConnectionImpl(Network::Connection& connection, CodecStats& stats, http_parser_type type, -- uint32_t max_headers_kb, const uint32_t max_headers_count, -- HeaderKeyFormatterPtr&& header_key_formatter, bool enable_trailers); -+ ConnectionImpl(Network::Connection& connection, Http::Http1::CodecStats& stats, -+ http_parser_type type, uint32_t max_headers_kb, const uint32_t max_headers_count, -+ Http::Http1::HeaderKeyFormatterPtr&& header_key_formatter, bool enable_trailers); - - bool resetStreamCalled() { return reset_stream_called_; } - void onMessageBeginBase(); -@@ -240,10 +244,10 @@ - void checkMaxHeadersSize(); - - Network::Connection& connection_; -- CodecStats& stats_; -+ Http::Http1::CodecStats& stats_; - http_parser parser_; - Http::Code error_code_{Http::Code::BadRequest}; -- const HeaderKeyFormatterPtr header_key_formatter_; -+ const Http::Http1::HeaderKeyFormatterPtr header_key_formatter_; - HeaderString current_header_field_; - HeaderString current_header_value_; - bool processing_trailers_ : 1; -@@ -420,7 +424,7 @@ - */ - class ServerConnectionImpl : public ServerConnection, public ConnectionImpl { - public: -- ServerConnectionImpl(Network::Connection& connection, CodecStats& stats, -+ ServerConnectionImpl(Network::Connection& connection, Http::Http1::CodecStats& stats, - ServerConnectionCallbacks& callbacks, const Http1Settings& settings, - uint32_t max_request_headers_kb, const uint32_t max_request_headers_count, - envoy::config::core::v3::HttpProtocolOptions::HeadersWithUnderscoresAction -@@ -432,7 +436,7 @@ - * An active HTTP/1.1 request. - */ - struct ActiveRequest { -- ActiveRequest(ConnectionImpl& connection, HeaderKeyFormatter* header_key_formatter) -+ ActiveRequest(ConnectionImpl& connection, Http::Http1::HeaderKeyFormatter* header_key_formatter) - : response_encoder_(connection, header_key_formatter) {} - - HeaderString request_url_; -@@ -524,7 +528,7 @@ - */ - class ClientConnectionImpl : public ClientConnection, public ConnectionImpl { - public: -- ClientConnectionImpl(Network::Connection& connection, CodecStats& stats, -+ ClientConnectionImpl(Network::Connection& connection, Http::Http1::CodecStats& stats, - ConnectionCallbacks& callbacks, const Http1Settings& settings, - const uint32_t max_response_headers_count); - -@@ -533,8 +537,8 @@ - - private: - struct PendingResponse { -- PendingResponse(ConnectionImpl& connection, HeaderKeyFormatter* header_key_formatter, -- ResponseDecoder* decoder) -+ PendingResponse(ConnectionImpl& connection, -+ Http::Http1::HeaderKeyFormatter* header_key_formatter, ResponseDecoder* decoder) - : encoder_(connection, header_key_formatter), decoder_(decoder) {} - - RequestEncoderImpl encoder_; -@@ -598,6 +602,7 @@ - }; - - } // namespace Http1 -+} // namespace Legacy - } // namespace Http - } // namespace Envoy - \ No newline at end of file diff --git a/tools/code_format/codec_diffs/http2_codec_impl_cc b/tools/code_format/codec_diffs/http2_codec_impl_cc deleted file mode 100644 index 46123d9ef031..000000000000 --- a/tools/code_format/codec_diffs/http2_codec_impl_cc +++ /dev/null @@ -1,34 +0,0 @@ ---- -+++ -@@ -1,4 +1,4 @@ --#include "common/http/http2/codec_impl.h" -+#include "common/http/http2/codec_impl_legacy.h" - - #include - #include -@@ -25,6 +25,7 @@ - - namespace Envoy { - namespace Http { -+namespace Legacy { - namespace Http2 { - - class Http2ResponseCodeDetailValues { -@@ -52,6 +53,9 @@ - }; - - using Http2ResponseCodeDetails = ConstSingleton; -+using Http::Http2::CodecStats; -+using Http::Http2::MetadataDecoder; -+using Http::Http2::MetadataEncoder; - - bool Utility::reconstituteCrumbledCookies(const HeaderString& key, const HeaderString& value, - HeaderString& cookies) { -@@ -1464,6 +1468,7 @@ - } - - } // namespace Http2 -+} // namespace Legacy - } // namespace Http - } // namespace Envoy - \ No newline at end of file diff --git a/tools/code_format/codec_diffs/http2_codec_impl_h b/tools/code_format/codec_diffs/http2_codec_impl_h deleted file mode 100644 index 70c306568061..000000000000 --- a/tools/code_format/codec_diffs/http2_codec_impl_h +++ /dev/null @@ -1,77 +0,0 @@ ---- -+++ -@@ -30,6 +30,7 @@ - - namespace Envoy { - namespace Http { -+namespace Legacy { - namespace Http2 { - - // This is not the full client magic, but it's the smallest size that should be able to -@@ -89,7 +90,7 @@ - */ - class ConnectionImpl : public virtual Connection, protected Logger::Loggable { - public: -- ConnectionImpl(Network::Connection& connection, CodecStats& stats, -+ ConnectionImpl(Network::Connection& connection, Http::Http2::CodecStats& stats, - const envoy::config::core::v3::Http2ProtocolOptions& http2_options, - const uint32_t max_headers_kb, const uint32_t max_headers_count); - -@@ -252,9 +253,9 @@ - virtual void decodeTrailers() PURE; - - // Get MetadataEncoder for this stream. -- MetadataEncoder& getMetadataEncoder(); -+ Http::Http2::MetadataEncoder& getMetadataEncoder(); - // Get MetadataDecoder for this stream. -- MetadataDecoder& getMetadataDecoder(); -+ Http::Http2::MetadataDecoder& getMetadataDecoder(); - // Callback function for MetadataDecoder. - void onMetadataDecoded(MetadataMapPtr&& metadata_map_ptr); - -@@ -273,8 +274,8 @@ - [this]() -> void { this->pendingSendBufferHighWatermark(); }, - []() -> void { /* TODO(adisuissa): Handle overflow watermark */ }}; - HeaderMapPtr pending_trailers_to_encode_; -- std::unique_ptr metadata_decoder_; -- std::unique_ptr metadata_encoder_; -+ std::unique_ptr metadata_decoder_; -+ std::unique_ptr metadata_encoder_; - absl::optional deferred_reset_; - HeaderString cookies_; - bool local_end_stream_sent_ : 1; -@@ -414,7 +415,7 @@ - - std::list active_streams_; - nghttp2_session* session_{}; -- CodecStats& stats_; -+ Http::Http2::CodecStats& stats_; - Network::Connection& connection_; - const uint32_t max_headers_kb_; - const uint32_t max_headers_count_; -@@ -522,7 +523,7 @@ - public: - using SessionFactory = Nghttp2SessionFactory; - ClientConnectionImpl(Network::Connection& connection, ConnectionCallbacks& callbacks, -- CodecStats& stats, -+ Http::Http2::CodecStats& stats, - const envoy::config::core::v3::Http2ProtocolOptions& http2_options, - const uint32_t max_response_headers_kb, - const uint32_t max_response_headers_count, -@@ -557,7 +558,7 @@ - class ServerConnectionImpl : public ServerConnection, public ConnectionImpl { - public: - ServerConnectionImpl(Network::Connection& connection, ServerConnectionCallbacks& callbacks, -- CodecStats& stats, -+ Http::Http2::CodecStats& stats, - const envoy::config::core::v3::Http2ProtocolOptions& http2_options, - const uint32_t max_request_headers_kb, - const uint32_t max_request_headers_count, -@@ -596,6 +597,7 @@ - }; - - } // namespace Http2 -+} // namespace Legacy - } // namespace Http - } // namespace Envoy - \ No newline at end of file