diff --git a/bazel/BUILD b/bazel/BUILD index 55dca812b2ed..fef695905916 100644 --- a/bazel/BUILD +++ b/bazel/BUILD @@ -284,11 +284,6 @@ config_setting( values = {"define": "path_normalization_by_default=true"}, ) -config_setting( - name = "enable_legacy_codecs_in_integration_tests", - values = {"define": "use_new_codecs_in_integration_tests=false"}, -) - 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 e8dd5dd0bc25..e33386ed5b98 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_new_codecs_in_integration_tests = "envoy_select_new_codecs_in_integration_tests", _envoy_select_wasm = "envoy_select_wasm", _envoy_select_wasm_v8 = "envoy_select_wasm_v8", _envoy_select_wasm_wasmtime = "envoy_select_wasm_wasmtime", @@ -195,7 +194,6 @@ envoy_select_wasm = _envoy_select_wasm envoy_select_wasm_wavm = _envoy_select_wasm_wavm envoy_select_wasm_wasmtime = _envoy_select_wasm_wasmtime envoy_select_wasm_v8 = _envoy_select_wasm_v8 -envoy_select_new_codecs_in_integration_tests = _envoy_select_new_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 b0789ae51291..a0f2097ad6cc 100644 --- a/bazel/envoy_select.bzl +++ b/bazel/envoy_select.bzl @@ -58,10 +58,3 @@ def envoy_select_wasm_wasmtime(xs): "@envoy//bazel:wasm_wasmtime": xs, "//conditions:default": [], }) - -# Select the given values by default and remove if use new codecs are disabled for current build. -def envoy_select_new_codecs_in_integration_tests(xs, repository = ""): - return select({ - repository + "//bazel:enable_legacy_codecs_in_integration_tests": [], - "//conditions:default": xs, - }) diff --git a/ci/do_ci.sh b/ci/do_ci.sh index f50a538c1958..b1bf68933334 100755 --- a/ci/do_ci.sh +++ b/ci/do_ci.sh @@ -282,7 +282,6 @@ elif [[ "$CI_TARGET" == "bazel.compile_time_options" ]]; then "--define" "quiche=enabled" "--define" "path_normalization_by_default=true" "--define" "deprecated_features=disabled" - "--define" "use_new_codecs_in_integration_tests=false" "--define" "tcmalloc=gperftools" "--define" "zlib=ng" "--@envoy//source/extensions/filters/http/kill_request:enabled" diff --git a/docs/root/version_history/current.rst b/docs/root/version_history/current.rst index 95034b7b1c13..bff52edb3330 100644 --- a/docs/root/version_history/current.rst +++ b/docs/root/version_history/current.rst @@ -56,6 +56,7 @@ Removed Config or Runtime * ext_authz: removed auto ignore case in HTTP-based `ext_authz` header matching and the runtime guard `envoy.reloadable_features.ext_authz_http_service_enable_case_sensitive_string_matcher`. To ignore case, set the :ref:`ignore_case ` field to true. * http: flip default HTTP/1 and HTTP/2 server codec implementations to new codecs that remove the use of exceptions for control flow. To revert to old codec behavior, set the runtime feature `envoy.reloadable_features.new_codec_behavior` to false. * http: removed `envoy.reloadable_features.http1_flood_protection` and legacy code path for turning flood protection off. +* http: removed `envoy.reloadable_features.new_codec_behavior` and legacy codecs. New Features ------------ diff --git a/source/common/http/BUILD b/source/common/http/BUILD index 97963015c4cf..e19d26c058f4 100644 --- a/source/common/http/BUILD +++ b/source/common/http/BUILD @@ -58,21 +58,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", ], ) @@ -248,9 +243,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 761cf879a5f0..47a7fd33f8f1 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 { @@ -162,15 +158,9 @@ 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: { diff --git a/source/common/http/conn_manager_utility.cc b/source/common/http/conn_manager_utility.cc index 86c7861bb420..6042af59adcc 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, random, http2_options, max_request_headers_kb, - max_request_headers_count, headers_with_underscores_action); - } else { - return std::make_unique( - connection, callbacks, stats, random, http2_options, max_request_headers_kb, - max_request_headers_count, headers_with_underscores_action); - } + return std::make_unique( + connection, callbacks, stats, random, 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 325fa94883a8..8c864eaca715 100644 --- a/source/common/http/http1/BUILD +++ b/source/common/http/http1/BUILD @@ -24,45 +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: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 + ["//source/common/common:cleanup_lib"], -) - -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:cleanup_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: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 3616cd7ed14b..000000000000 --- a/source/common/http/http1/codec_impl_legacy.cc +++ /dev/null @@ -1,1292 +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/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"; - const absl::string_view InvalidUnderscore = "http1.unexpected_underscore"; - const absl::string_view ChunkedContentLength = "http1.content_length_and_chunked_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), - connect_request_(false), 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([this](const HeaderEntry& header) -> 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; - } - - encodeFormattedHeader(key_to_use, header.value().getStringView()); - - return HeaderMap::Iterate::Continue; - }); - - 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([this](const HeaderEntry& header) -> HeaderMap::Iterate { - encodeFormattedHeader(header.key().getStringView(), header.value().getStringView()); - return HeaderMap::Iterate::Continue; - }); - - 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(); - // With CONNECT half-closing the connection is used to signal end stream. - if (connect_request_) { - connection_.connection().close(Network::ConnectionCloseType::FlushWriteAndDelay); - } -} - -void ServerConnectionImpl::maybeAddSentinelBufferFragment(Buffer::Instance& output_buffer) { - // 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 { - // 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"; - -Status 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(); - connection_.connection().enableHalfClose(true); - 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); - return okStatus(); -} - -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, - const Http1Settings& settings, http_parser_type type, - uint32_t max_headers_kb, const uint32_t max_headers_count, - HeaderKeyFormatterPtr&& header_key_formatter) - : connection_(connection), stats_(stats), codec_settings_(settings), - header_key_formatter_(std::move(header_key_formatter)), processing_trailers_(false), - handling_upgrade_(false), reset_stream_called_(false), deferred_end_stream_headers_(false), - strict_1xx_and_204_headers_(Runtime::runtimeFeatureEnabled( - "envoy.reloadable_features.strict_1xx_and_204_response_headers")), - output_buffer_(connection.dispatcher().getWatermarkFactory().create( - [&]() -> 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_.allow_chunked_length = 1; - 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 ClientConnectionImpl::dispatch(Buffer::Instance& data) { - Http::Status status = ConnectionImpl::dispatch(data); - if (status.ok() && data.length() > 0) { - // The HTTP/1.1 codec pauses dispatch after a single response is complete. Extraneous data - // after a response is complete indicates an error. - return codecProtocolError("http/1.1 protocol error: extraneous data after response complete"); - } - return status; -} - -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) { - current_dispatching_buffer_ = &data; - while (data.length() > 0) { - auto slice = data.frontSlice(); - dispatching_slice_already_drained_ = false; - const size_t parsed = dispatchSlice(static_cast(slice.mem_), slice.len_); - if (!dispatching_slice_already_drained_) { - ASSERT(parsed <= slice.len_); - data.drain(parsed); - } - - total_parsed += parsed; - 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; - } - } - current_dispatching_buffer_ = nullptr; - dispatchBufferedBody(); - } else { - dispatchSlice(nullptr, 0); - } - ASSERT(buffered_body_.length() == 0); - - ENVOY_CONN_LOG(trace, "parsed {} bytes", connection_, 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 (!enableTrailers()) { - // 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 && !enableTrailers()) { - // 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; - } - - // https://tools.ietf.org/html/rfc7230#section-3.3.3 - // If a message is received with both a Transfer-Encoding and a - // Content-Length header field, the Transfer-Encoding overrides the - // Content-Length. Such a message might indicate an attempt to - // perform request smuggling (Section 9.5) or response splitting - // (Section 9.4) and ought to be handled as an error. A sender MUST - // remove the received Content-Length field prior to forwarding such - // a message. - - // Reject message with Http::Code::BadRequest if both Transfer-Encoding and Content-Length - // headers are present or if allowed by http1 codec settings and 'Transfer-Encoding' - // is chunked - remove Content-Length and serve request. - if (parser_.uses_transfer_encoding != 0 && request_or_response_headers.ContentLength()) { - if ((parser_.flags & F_CHUNKED) && codec_settings_.allow_chunked_length_) { - request_or_response_headers.removeContentLength(); - } else { - error_code_ = Http::Code::BadRequest; - sendProtocolError(Http1ResponseCodeDetails::get().ChunkedContentLength); - throw CodecProtocolException( - "http/1.1 protocol error: both 'Content-Length' and 'Transfer-Encoding' are set."); - } - } - - // 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) { - auto slice = current_dispatching_buffer_->frontSlice(); - if (data == slice.mem_ && length == slice.len_) { - buffered_body_.move(*current_dispatching_buffer_, length); - dispatching_slice_already_drained_ = true; - } else { - 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, settings, HTTP_REQUEST, max_request_headers_kb, - max_request_headers_count, formatter(settings)), - callbacks_(callbacks), - 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)), - headers_with_underscores_action_(headers_with_underscores_action) {} - -uint32_t ServerConnectionImpl::getHeadersSize() { - // Add in 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_ && 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_)); - } - active_request_->request_decoder_->sendLocalReply(is_grpc_request, error_code_, - CodeUtility::toString(error_code_), nullptr, - 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().InvalidUnderscore); - 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, settings, HTTP_RESPONSE, MAX_RESPONSE_HEADERS_KB, - max_response_headers_count, formatter(settings)) {} - -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 && - !(parser_.flags & F_CHUNKED))) { - 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() { - ENVOY_CONN_LOG(trace, "status_code {}", connection_, parser_.status_code); - - // 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 == enumToInt(Http::Code::Continue)) { - pending_response_.value().decoder_->decode100ContinueHeaders(std::move(headers)); - } else if (cannotHaveBody() && !handling_upgrade_) { - deferred_end_stream_headers_ = true; - } else { - pending_response_.value().decoder_->decodeHeaders(std::move(headers), false); - } - - // http-parser treats 1xx headers as their own complete response. Swallow the spurious - // onMessageComplete and continue processing for purely informational headers. - // 101-SwitchingProtocols is exempt as all data after the header is proxied through after - // upgrading. - if (CodeUtility::is1xx(parser_.status_code) && - parser_.status_code != enumToInt(Http::Code::SwitchingProtocols)) { - ignore_message_complete_for_1xx_ = true; - // Reset to ensure no information from the 1xx headers is used for the response headers. - headers_or_trailers_.emplace(nullptr); - } - } - - // 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_1xx_) { - ignore_message_complete_for_1xx_ = 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 703de320637e..000000000000 --- a/source/common/http/http1/codec_impl_legacy.h +++ /dev/null @@ -1,617 +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 connect_request_ : 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, - bool stream_error_on_invalid_http_message) - : StreamEncoderImpl(connection, header_key_formatter), - stream_error_on_invalid_http_message_(stream_error_on_invalid_http_message) {} - - 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); } - - bool streamErrorOnInvalidHttpMessage() const override { - return stream_error_on_invalid_http_message_; - } - -private: - bool started_response_{}; - const bool stream_error_on_invalid_http_message_; -}; - -/** - * 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 - Status encodeHeaders(const RequestHeaderMap& headers, bool end_stream) override; - void encodeTrailers(const RequestTrailerMap& trailers) override { encodeTrailersBase(trailers); } - -private: - bool upgrade_request_{}; - bool head_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::Instance& 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::Instance&) {} - Http::Http1::CodecStats& stats() { return stats_; } - bool enableTrailers() const { return codec_settings_.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, - const Http1Settings& settings, http_parser_type type, uint32_t max_headers_kb, - const uint32_t max_headers_count, - Http::Http1::HeaderKeyFormatterPtr&& header_key_formatter); - - 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_; - const Http1Settings codec_settings_; - http_parser parser_; - Buffer::Instance* current_dispatching_buffer_{}; - 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 strict_1xx_and_204_headers_ : 1; - bool dispatching_slice_already_drained_ : 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::InstancePtr 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(ServerConnectionImpl& connection, - Http::Http1::HeaderKeyFormatter* header_key_formatter) - : response_encoder_(connection, header_key_formatter, - connection.codec_settings_.stream_error_on_invalid_http_message_) {} - - 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::Instance& output_buffer) override; - void doFloodProtectionChecks() const; - void checkHeaderNameForUnderscores() override; - - ServerConnectionCallbacks& callbacks_; - absl::optional active_request_; - 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_{}; - // 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 - Http::Status dispatch(Buffer::Instance& data) override; - 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 non-101 1xx headers and receiving the spurious onMessageComplete. - bool ignore_message_complete_for_1xx_{}; - // 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 832decc73f68..826e3f2beeef 100644 --- a/source/common/http/http2/BUILD +++ b/source/common/http/http2/BUILD @@ -18,38 +18,6 @@ envoy_cc_library( ], ) -CODEC_LIB_DEPS = [ - ":codec_stats_lib", - ":metadata_decoder_lib", - ":metadata_encoder_lib", - ":protocol_constraints_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:statusor_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"], @@ -60,23 +28,37 @@ 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", + ":protocol_constraints_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:statusor_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_legacy.cc b/source/common/http/http2/codec_impl_legacy.cc deleted file mode 100644 index 794885e47526..000000000000 --- a/source/common/http/http2/codec_impl_legacy.cc +++ /dev/null @@ -1,1534 +0,0 @@ -#include "common/http/http2/codec_impl_legacy.h" - -#include -#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 "common/runtime/runtime_features.h" - -#include "absl/container/fixed_array.h" - -namespace Envoy { -namespace Http { -namespace Legacy { -namespace Http2 { - -class Http2ResponseCodeDetailValues { -public: - // 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"; - // The number of headers (or trailers) exceeded the configured limits - const absl::string_view too_many_headers = "http2.too_many_headers"; - // Envoy detected an HTTP/2 frame flood from the server. - const absl::string_view outbound_frame_flood = "http2.outbound_frames_flood"; - // Envoy detected an inbound HTTP/2 frame flood. - const absl::string_view inbound_empty_frame_flood = "http2.inbound_empty_frames_flood"; - // Envoy was configured to drop requests with header keys beginning with underscores. - const absl::string_view invalid_underscore = "http2.unexpected_underscore"; - // The upstream refused the stream. - const absl::string_view remote_refused = "http2.remote_refuse"; - // The upstream reset the stream. - const absl::string_view remote_reset = "http2.remote_reset"; - - 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_; - } - } -}; - -int reasonToReset(StreamResetReason reason) { - switch (reason) { - case StreamResetReason::LocalRefusedStreamReset: - return NGHTTP2_REFUSED_STREAM; - case StreamResetReason::ConnectError: - return NGHTTP2_CONNECT_ERROR; - default: - return NGHTTP2_NO_ERROR; - } -} - -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), received_noninformational_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([&final_headers](const HeaderEntry& header) -> HeaderMap::Iterate { - insertHeader(final_headers, header); - return HeaderMap::Iterate::Continue; - }); -} - -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(); - parent_.checkProtocolConstraintViolation(); -} - -Status 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); - return okStatus(); -} - -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. - // However, for empty trailers, we don't need to to save the trailers. - ASSERT(!pending_trailers_to_encode_); - const bool skip_encoding_empty_trailers = - trailers.empty() && parent_.skip_encoding_empty_trailers_; - if (!skip_encoding_empty_trailers) { - pending_trailers_to_encode_ = cloneTrailers(trailers); - createPendingFlushTimer(); - } - } else { - submitTrailers(trailers); - parent_.sendPendingFrames(); - parent_.checkProtocolConstraintViolation(); - } -} - -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(); - parent_.checkProtocolConstraintViolation(); -} - -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(); - parent_.checkProtocolConstraintViolation(); - } - } -} - -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() { - auto& headers = absl::get(headers_or_trailers_); - const uint64_t status = Http::Utility::getResponseStatus(*headers); - - if (!upgrade_type_.empty() && headers->Status()) { - Http::Utility::transformUpgradeResponseFromH2toH1(*headers, upgrade_type_); - } - - // Non-informational headers are non-1xx OR 101-SwitchingProtocols, since 101 implies that further - // proxying is on an upgrade path. - received_noninformational_headers_ = - !CodeUtility::is1xx(status) || status == enumToInt(Http::Code::SwitchingProtocols); - - if (status == enumToInt(Http::Code::Continue)) { - 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() { - 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) { - ASSERT(local_end_stream_); - const bool skip_encoding_empty_trailers = - trailers.empty() && parent_.skip_encoding_empty_trailers_; - if (skip_encoding_empty_trailers) { - ENVOY_CONN_LOG(debug, "skipping submitting trailers", parent_.connection_); - - // Instead of submitting empty trailers, we send empty data instead. - Buffer::OwnedImpl empty_buffer; - encodeDataHelper(empty_buffer, /*end_stream=*/true, skip_encoding_empty_trailers); - return; - } - - 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_.protocol_constraints_.incrementOutboundDataFrameCount(); - - 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_); - setDetails(Http2ResponseCodeDetails::get().outbound_frame_flood); - 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(); - parent_.checkProtocolConstraintViolation(); -} - -void ConnectionImpl::StreamImpl::encodeData(Buffer::Instance& data, bool end_stream) { - ASSERT(!local_end_stream_); - encodeDataHelper(data, end_stream, /*skip_encoding_empty_trailers=*/false); -} - -void ConnectionImpl::StreamImpl::encodeDataHelper(Buffer::Instance& data, bool end_stream, - bool skip_encoding_empty_trailers) { - if (skip_encoding_empty_trailers) { - ASSERT(data.length() == 0 && 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(); - parent_.checkProtocolConstraintViolation(); - - 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(); - parent_.checkProtocolConstraintViolation(); -} - -void ConnectionImpl::StreamImpl::resetStreamWorker(StreamResetReason reason) { - int rc = nghttp2_submit_rst_stream(parent_.session_, NGHTTP2_FLAG_NONE, stream_id_, - reasonToReset(reason)); - 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, - Random::RandomGenerator& random, - 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.override_stream_error_on_invalid_http_message().value()), - flood_detected_(false), protocol_constraints_(stats, http2_options), - skip_encoding_empty_trailers_(Runtime::runtimeFeatureEnabled( - "envoy.reloadable_features.http2_skip_encoding_empty_trailers")), - dispatching_(false), raised_goaway_(false), pending_deferred_reset_(false), random_(random) { - if (http2_options.has_connection_keepalive()) { - keepalive_interval_ = std::chrono::milliseconds( - PROTOBUF_GET_MS_REQUIRED(http2_options.connection_keepalive(), interval)); - keepalive_timeout_ = std::chrono::milliseconds( - PROTOBUF_GET_MS_REQUIRED(http2_options.connection_keepalive(), timeout)); - keepalive_interval_jitter_percent_ = PROTOBUF_GET_WRAPPED_OR_DEFAULT( - http2_options.connection_keepalive(), interval_jitter, 15.0); - - keepalive_send_timer_ = connection.dispatcher().createTimer([this]() { sendKeepalive(); }); - keepalive_timeout_timer_ = - connection.dispatcher().createTimer([this]() { onKeepaliveResponseTimeout(); }); - - // This call schedules the initial interval, with jitter. - onKeepaliveResponse(); - } -} - -ConnectionImpl::~ConnectionImpl() { - for (const auto& stream : active_streams_) { - stream->destroy(); - } - nghttp2_session_del(session_); -} - -void ConnectionImpl::sendKeepalive() { - // Include the current time as the payload to help with debugging. - SystemTime now = connection_.dispatcher().timeSource().systemTime(); - uint64_t ms_since_epoch = - std::chrono::duration_cast(now.time_since_epoch()).count(); - ENVOY_CONN_LOG(trace, "Sending keepalive PING {}", connection_, ms_since_epoch); - - // The last parameter is an opaque 8-byte buffer, so this cast is safe. - int rc = nghttp2_submit_ping(session_, 0 /*flags*/, reinterpret_cast(&ms_since_epoch)); - ASSERT(rc == 0); - sendPendingFrames(); - checkProtocolConstraintViolation(); - - keepalive_timeout_timer_->enableTimer(keepalive_timeout_); -} - -void ConnectionImpl::onKeepaliveResponse() { - // Check the timers for nullptr in case the peer sent an unsolicited PING ACK. - if (keepalive_timeout_timer_ != nullptr) { - keepalive_timeout_timer_->disableTimer(); - } - if (keepalive_send_timer_ != nullptr) { - uint64_t interval_ms = keepalive_interval_.count(); - const uint64_t jitter_percent_mod = keepalive_interval_jitter_percent_ * interval_ms / 100; - if (jitter_percent_mod > 0) { - interval_ms += random_.random() % jitter_percent_mod; - } - keepalive_send_timer_->enableTimer(std::chrono::milliseconds(interval_ms)); - } -} - -void ConnectionImpl::onKeepaliveResponseTimeout() { - ENVOY_CONN_LOG(debug, "Closing connection due to keepalive timeout", connection_); - stats_.keepalive_timeout_.inc(); - connection_.close(Network::ConnectionCloseType::NoFlush); -} - -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(); - checkProtocolConstraintViolation(); -} - -void ConnectionImpl::shutdownNotice() { - int rc = nghttp2_submit_shutdown_notice(session_); - ASSERT(rc == 0); - - sendPendingFrames(); - checkProtocolConstraintViolation(); -} - -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_PING) && (frame->ping.hd.flags & NGHTTP2_FLAG_ACK)) { - // The ``opaque_data`` should be exactly what was sent in the ping, which is - // was the current time when the ping was sent. This can be useful while debugging - // to match the ping and ack. - uint64_t data; - static_assert(sizeof(data) == sizeof(frame->ping.opaque_data), "Sizes are equal"); - memcpy(&data, frame->ping.opaque_data, sizeof(data)); - ENVOY_CONN_LOG(trace, "recv PING ACK {}", connection_, data); - - onKeepaliveResponse(); - return 0; - } - - 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(); - 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 (nghttp2_session_check_server_session(session_) || - stream->received_noninformational_headers_) { - ASSERT(stream->remote_end_stream_); - stream->decodeTrailers(); - } else { - // We're a client session and still waiting for non-informational headers. - stream->decodeHeaders(); - } - } - 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; -} - -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 { - auto releasor = trackOutboundFrames(is_outbound_flood_monitored_control_frame); - output.add(data, length); - output.addDrainTracker(releasor); - } catch (const FrameFloodException&) { - return false; - } - return true; -} - -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 { - if (error_code == NGHTTP2_REFUSED_STREAM) { - reason = StreamResetReason::RemoteRefusedStreamReset; - stream->setDetails(Http2ResponseCodeDetails::get().remote_refused); - } else if (error_code == NGHTTP2_CONNECT_ERROR) { - reason = StreamResetReason::ConnectError; - stream->setDetails(Http2ResponseCodeDetails::get().remote_reset); - } else { - reason = StreamResetReason::RemoteReset; - stream->setDetails(Http2ResponseCodeDetails::get().remote_reset); - } - } - - 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 || stream->remote_end_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 || stream->remote_end_stream_) { - 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) { - stream->setDetails(Http2ResponseCodeDetails::get().invalid_underscore); - 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_) { - stream->setDetails(Http2ResponseCodeDetails::get().too_many_headers); - stats_.header_overflow_.inc(); - // This will cause the library to reset/close the stream. - 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 (!protocol_constraints_.status().ok()) { - 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); - } -} - -void ConnectionImpl::scheduleProtocolConstraintViolationCallback() { - if (!protocol_constraint_violation_callback_) { - protocol_constraint_violation_callback_ = connection_.dispatcher().createSchedulableCallback( - [this]() { onProtocolConstraintViolation(); }); - protocol_constraint_violation_callback_->scheduleCallbackCurrentIteration(); - } -} - -void ConnectionImpl::onProtocolConstraintViolation() { - // Flooded outbound queue implies that peer is not reading and it does not - // make sense to try to flush pending bytes. - connection_.close(Envoy::Network::ConnectionCloseType::NoFlush); -} - -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); - } else { - ENVOY_LOG(trace, "Codec does not have Metadata frame support."); - } - - // 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, - Random::RandomGenerator& random, - 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, random, 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; - LinkedList::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, - Random::RandomGenerator& random, - 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, random, 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; - LinkedList::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); - auto result = protocol_constraints_.trackInboundFrames(hd, padding_length); - if (!result.ok()) { - ENVOY_CONN_LOG(trace, "error reading frame: {} received in this HTTP/2 session.", connection_, - result.message()); - if (isInboundFramesWithEmptyPayloadError(result)) { - ConnectionImpl::StreamImpl* stream = getStream(hd->stream_id); - if (stream) { - stream->setDetails(Http2ResponseCodeDetails::get().inbound_empty_frame_flood); - } - } - // 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; -} - -Envoy::Http::Http2::ProtocolConstraints::ReleasorProc -ServerConnectionImpl::trackOutboundFrames(bool is_outbound_flood_monitored_control_frame) { - auto releasor = - protocol_constraints_.incrementOutboundFrameCount(is_outbound_flood_monitored_control_frame); - if (dispatching_downstream_data_ && !protocol_constraints_.checkOutboundFrameLimits().ok()) { - throw FrameFloodException(std::string(protocol_constraints_.status().message())); - } - return releasor; -} - -void ServerConnectionImpl::checkProtocolConstraintViolation() { - if (!protocol_constraints_.checkOutboundFrameLimits().ok()) { - scheduleProtocolConstraintViolationCallback(); - } -} - -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. - if (!protocol_constraints_.checkOutboundFrameLimits().ok()) { - throw FrameFloodException(std::string(protocol_constraints_.status().message())); - } - - 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 766724a36d53..000000000000 --- a/source/common/http/http2/codec_impl_legacy.h +++ /dev/null @@ -1,623 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include -#include - -#include "envoy/common/random_generator.h" -#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/http2/protocol_constraints.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, - Random::RandomGenerator& random, - 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) { - // TODO(asraa): In some cases nghttp2's error handling may cause processing of multiple - // invalid frames for a single stream. If a temporal stream error is returned from a callback, - // remaining frames in the buffer will still be partially processed. For example, remaining - // frames will still parse through nghttp2's push promise error handling and in - // onBeforeFrame(Send/Received) callbacks, which may return invalid frame errors and attempt - // to set details again. In these cases, we simply do not overwrite details. When internal - // error latching is implemented in the codec for exception removal, we should prevent calling - // setDetails in an error state. - if (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() 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; } - - void encodeDataHelper(Buffer::Instance& data, bool end_stream, - bool skip_encoding_empty_trailers); - - ConnectionImpl& parent_; - int32_t stream_id_{-1}; - uint32_t unconsumed_bytes_{0}; - uint32_t read_disable_count_{0}; - - // Note that in current implementation the watermark callbacks of the pending_recv_data_ are - // never called. The watermark value is set to the size of the stream window. As a result this - // watermark can never overflow because the peer can never send more bytes than the stream - // window without triggering protocol error and this buffer is drained after each DATA frame was - // dispatched through the filter chain. See source/docs/flow_control.md for more information. - 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 received_noninformational_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() 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 (received_noninformational_headers_) { - headers_or_trailers_.emplace(ResponseTrailerMapImpl::create()); - } else { - headers_or_trailers_.emplace(ResponseHeaderMapImpl::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 - Status 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()) { - stream_error_on_invalid_http_message_ = parent.stream_error_on_invalid_http_messaging_; - } - - // StreamImpl - void submitHeaders(const std::vector& final_headers, - nghttp2_data_provider* provider) override; - StreamDecoder& decoder() override { return *request_decoder_; } - void decodeHeaders() 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_; - bool stream_error_on_invalid_http_message_; - - bool streamErrorOnInvalidHttpMessage() const override { - return stream_error_on_invalid_http_message_; - } - }; - - 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; - } - - /** - * This method checks if a protocol constraint had been violated in the sendPendingFrames() call - * outside of the dispatch context. - * This method is a stop-gap solution for implementing checking of protocol constraint violations - * outside of the dispatch context (where at this point the sendPendingFrames() method always - * returns success). It allows each case where sendPendingFrames() is called outside of the - * dispatch context to be fixed in its own PR so it is easier to review and reason about. Once all - * error handling is implemented this method will be removed and the `sendPendingFrames()` will be - * changed to return error in both dispatching and non-dispatching contexts. At the same time the - * RELEASE_ASSERTs will be removed as well. - * The implementation in the ClientConnectionImpl is a no-op as client connections to not check - * protocol constraints. - * The implementation in the ServerConnectionImpl schedules callback to terminate connection if - * the protocol constraint was violated. - */ - virtual void checkProtocolConstraintViolation() PURE; - - /** - * Callback for terminating connection when protocol constrain has been violated - * outside of the dispatch context. - */ - void scheduleProtocolConstraintViolationCallback(); - void onProtocolConstraintViolation(); - - 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; - ::Envoy::Http::Http2::ProtocolConstraints protocol_constraints_; - - // 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); - - // Some browsers (e.g. WebKit-based browsers: https://bugs.webkit.org/show_bug.cgi?id=210108) have - // a problem with processing empty trailers (END_STREAM | END_HEADERS with zero length HEADERS) of - // an HTTP/2 response as reported here: https://github.com/envoyproxy/envoy/issues/10514. This is - // controlled by "envoy.reloadable_features.http2_skip_encoding_empty_trailers" runtime feature - // flag. - const bool skip_encoding_empty_trailers_; - -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 Envoy::Http::Http2::ProtocolConstraints::ReleasorProc - trackOutboundFrames(bool is_outbound_flood_monitored_control_frame) PURE; - virtual bool trackInboundFrames(const nghttp2_frame_hd* hd, uint32_t padding_length) PURE; - void sendKeepalive(); - void onKeepaliveResponse(); - void onKeepaliveResponseTimeout(); - - bool dispatching_ : 1; - bool raised_goaway_ : 1; - bool pending_deferred_reset_ : 1; - Event::SchedulableCallbackPtr protocol_constraint_violation_callback_; - Random::RandomGenerator& random_; - Event::TimerPtr keepalive_send_timer_; - Event::TimerPtr keepalive_timeout_timer_; - std::chrono::milliseconds keepalive_interval_; - std::chrono::milliseconds keepalive_timeout_; - uint32_t keepalive_interval_jitter_percent_; -}; - -/** - * 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, Random::RandomGenerator& random, - 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 do not track or check queue limits for 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. - Envoy::Http::Http2::ProtocolConstraints::ReleasorProc trackOutboundFrames(bool) override { - return Envoy::Http::Http2::ProtocolConstraints::ReleasorProc([]() {}); - } - bool trackInboundFrames(const nghttp2_frame_hd*, uint32_t) override { return true; } - void checkProtocolConstraintViolation() override {} - - 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, Random::RandomGenerator& random, - 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; - Envoy::Http::Http2::ProtocolConstraints::ReleasorProc - trackOutboundFrames(bool is_outbound_flood_monitored_control_frame) override; - bool trackInboundFrames(const nghttp2_frame_hd* hd, uint32_t padding_length) override; - absl::optional checkHeaderNameForUnderscores(absl::string_view header_name) override; - - /** - * Check protocol constraint violations outside of the dispatching context. - * This method ASSERTs if it is called in the dispatching context. - */ - void checkProtocolConstraintViolation() 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::checkProtocolConstraintsStatus 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 1ac9652de1b4..06288bbe4ba1 100644 --- a/source/common/runtime/runtime_features.cc +++ b/source/common/runtime/runtime_features.cc @@ -77,7 +77,6 @@ constexpr const char* runtime_features[] = { "envoy.reloadable_features.http_transport_failure_reason_in_body", "envoy.reloadable_features.http2_skip_encoding_empty_trailers", "envoy.reloadable_features.listener_in_place_filterchain_update", - "envoy.reloadable_features.new_codec_behavior", "envoy.reloadable_features.overload_manager_disable_keepalive_drain_http2", "envoy.reloadable_features.prefer_quic_kernel_bpf_packet_routing", "envoy.reloadable_features.preserve_query_string_in_path_redirects", diff --git a/source/extensions/filters/network/http_connection_manager/BUILD b/source/extensions/filters/network/http_connection_manager/BUILD index 012cd2b00cce..db02c5750db8 100644 --- a/source/extensions/filters/network/http_connection_manager/BUILD +++ b/source/extensions/filters/network/http_connection_manager/BUILD @@ -38,9 +38,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 2222f052e590..01a88e0b9ab6 100644 --- a/source/extensions/filters/network/http_connection_manager/config.cc +++ b/source/extensions/filters/network/http_connection_manager/config.cc @@ -22,9 +22,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" @@ -566,34 +564,17 @@ HttpConnectionManagerConfig::createCodec(Network::Connection& connection, 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()); - } + 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()), - context_.api().randomGenerator(), http2_options_, maxRequestHeadersKb(), - maxRequestHeadersCount(), headersWithUnderscoresAction()); - } else { - return std::make_unique( - connection, callbacks, - Http::Http2::CodecStats::atomicGet(http2_codec_stats_, context_.scope()), - context_.api().randomGenerator(), http2_options_, maxRequestHeadersKb(), - maxRequestHeadersCount(), headersWithUnderscoresAction()); - } + return std::make_unique( + connection, callbacks, + Http::Http2::CodecStats::atomicGet(http2_codec_stats_, context_.scope()), + context_.api().randomGenerator(), 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/http1/BUILD b/test/common/http/http1/BUILD index b63855d4d0f4..bb66b74b8ffd 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 b292dd1aa644..c2eb068cf3f5 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" @@ -55,7 +54,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,21 +64,12 @@ class Http1CodecTestBase { Http::Http1::CodecStats::AtomicPtr http1_codec_stats_; }; -class Http1ServerConnectionImplTest : public Http1CodecTestBase, - public testing::TestWithParam { +class Http1ServerConnectionImplTest : public Http1CodecTestBase { public: - bool testingNewCodec() { return GetParam(); } - void initialize() { - if (testingNewCodec()) { - 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_; @@ -139,15 +129,9 @@ void Http1ServerConnectionImplTest::expect400(Protocol p, bool allow_absolute_ur if (allow_absolute_url) { codec_settings_.allow_absolute_url_ = allow_absolute_url; - if (testingNewCodec()) { - 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; @@ -175,15 +159,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 (testingNewCodec()) { - 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; @@ -202,15 +180,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 (testingNewCodec()) { - 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; @@ -244,15 +216,9 @@ void Http1ServerConnectionImplTest::testTrailersExceedLimit(std::string trailer_ initialize(); // Make a new 'codec' with the right settings codec_settings_.enable_trailers_ = enable_trailers; - if (testingNewCodec()) { - 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(_, _)) @@ -333,15 +299,9 @@ void Http1ServerConnectionImplTest::testRequestHeadersAccepted(std::string heade void Http1ServerConnectionImplTest::testServerAllowChunkedContentLength(uint32_t content_length, bool allow_chunked_length) { codec_settings_.allow_chunked_length_ = allow_chunked_length; - if (testingNewCodec()) { - 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; Http::ResponseEncoder* response_encoder = nullptr; @@ -389,12 +349,7 @@ void Http1ServerConnectionImplTest::testServerAllowChunkedContentLength(uint32_t } } -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; @@ -418,7 +373,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; @@ -433,7 +388,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; @@ -452,7 +407,7 @@ TEST_P(Http1ServerConnectionImplTest, UnsupportedEncoding) { // Note that this test is validating a performance optimization, not a functional behavior // requirement. If future changes to the codec make this test not pass, but do not regress // performance of large HTTP body handling, this test can be changed or removed. -TEST_P(Http1ServerConnectionImplTest, LargeBodyOptimization) { +TEST_F(Http1ServerConnectionImplTest, LargeBodyOptimization) { initialize(); InSequence sequence; @@ -482,7 +437,7 @@ TEST_P(Http1ServerConnectionImplTest, LargeBodyOptimization) { } // 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; @@ -513,7 +468,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; @@ -551,7 +506,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; @@ -580,7 +535,7 @@ TEST_P(Http1ServerConnectionImplTest, ChunkedBodyFragmentedBuffer) { EXPECT_EQ(0U, buffer.length()); } -TEST_P(Http1ServerConnectionImplTest, ChunkedBodyCase) { +TEST_F(Http1ServerConnectionImplTest, ChunkedBodyCase) { initialize(); InSequence sequence; @@ -607,7 +562,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; @@ -633,7 +588,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; @@ -650,7 +605,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{ @@ -671,7 +626,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 @@ -704,17 +659,11 @@ TEST_P(Http1ServerConnectionImplTest, InnerLWSIsPreserved) { } } -TEST_P(Http1ServerConnectionImplTest, CodecHasCorrectStreamErrorIfTrue) { +TEST_F(Http1ServerConnectionImplTest, CodecHasCorrectStreamErrorIfTrue) { codec_settings_.stream_error_on_invalid_http_message_ = true; - 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); Buffer::OwnedImpl buffer("GET / HTTP/1.1\r\n"); NiceMock decoder; @@ -729,17 +678,11 @@ TEST_P(Http1ServerConnectionImplTest, CodecHasCorrectStreamErrorIfTrue) { EXPECT_TRUE(response_encoder->streamErrorOnInvalidHttpMessage()); } -TEST_P(Http1ServerConnectionImplTest, CodecHasCorrectStreamErrorIfFalse) { +TEST_F(Http1ServerConnectionImplTest, CodecHasCorrectStreamErrorIfFalse) { codec_settings_.stream_error_on_invalid_http_message_ = false; - 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); Buffer::OwnedImpl buffer("GET / HTTP/1.1\r\n"); NiceMock decoder; @@ -754,7 +697,7 @@ TEST_P(Http1ServerConnectionImplTest, CodecHasCorrectStreamErrorIfFalse) { EXPECT_FALSE(response_encoder->streamErrorOnInvalidHttpMessage()); } -TEST_P(Http1ServerConnectionImplTest, CodecHasDefaultStreamErrorIfNotSet) { +TEST_F(Http1ServerConnectionImplTest, CodecHasDefaultStreamErrorIfNotSet) { initialize(); Buffer::OwnedImpl buffer("GET / HTTP/1.1\r\n"); @@ -770,7 +713,7 @@ TEST_P(Http1ServerConnectionImplTest, CodecHasDefaultStreamErrorIfNotSet) { EXPECT_FALSE(response_encoder->streamErrorOnInvalidHttpMessage()); } -TEST_P(Http1ServerConnectionImplTest, Http10) { +TEST_F(Http1ServerConnectionImplTest, Http10) { initialize(); InSequence sequence; @@ -788,7 +731,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"}}; @@ -796,7 +739,7 @@ TEST_P(Http1ServerConnectionImplTest, Http10AbsoluteNoOp) { expectHeadersTest(Protocol::Http10, true, buffer, expected_headers); } -TEST_P(Http1ServerConnectionImplTest, Http10Absolute) { +TEST_F(Http1ServerConnectionImplTest, Http10Absolute) { initialize(); TestRequestHeaderMapImpl expected_headers{ @@ -805,7 +748,7 @@ TEST_P(Http1ServerConnectionImplTest, Http10Absolute) { expectHeadersTest(Protocol::Http10, true, buffer, expected_headers); } -TEST_P(Http1ServerConnectionImplTest, Http10MultipleResponses) { +TEST_F(Http1ServerConnectionImplTest, Http10MultipleResponses) { initialize(); MockRequestDecoder decoder; @@ -851,7 +794,7 @@ TEST_P(Http1ServerConnectionImplTest, Http10MultipleResponses) { } } -TEST_P(Http1ServerConnectionImplTest, Http11AbsolutePath1) { +TEST_F(Http1ServerConnectionImplTest, Http11AbsolutePath1) { initialize(); TestRequestHeaderMapImpl expected_headers{ @@ -860,7 +803,7 @@ TEST_P(Http1ServerConnectionImplTest, Http11AbsolutePath1) { expectHeadersTest(Protocol::Http11, true, buffer, expected_headers); } -TEST_P(Http1ServerConnectionImplTest, Http11AbsolutePath2) { +TEST_F(Http1ServerConnectionImplTest, Http11AbsolutePath2) { initialize(); TestRequestHeaderMapImpl expected_headers{ @@ -869,7 +812,7 @@ TEST_P(Http1ServerConnectionImplTest, Http11AbsolutePath2) { expectHeadersTest(Protocol::Http11, true, buffer, expected_headers); } -TEST_P(Http1ServerConnectionImplTest, Http11AbsolutePathWithPort) { +TEST_F(Http1ServerConnectionImplTest, Http11AbsolutePathWithPort) { initialize(); TestRequestHeaderMapImpl expected_headers{ @@ -879,7 +822,7 @@ TEST_P(Http1ServerConnectionImplTest, Http11AbsolutePathWithPort) { expectHeadersTest(Protocol::Http11, true, buffer, expected_headers); } -TEST_P(Http1ServerConnectionImplTest, Http11AbsoluteEnabledNoOp) { +TEST_F(Http1ServerConnectionImplTest, Http11AbsoluteEnabledNoOp) { initialize(); TestRequestHeaderMapImpl expected_headers{ @@ -888,7 +831,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 @@ -896,7 +839,7 @@ TEST_P(Http1ServerConnectionImplTest, Http11InvalidRequest) { expect400(Protocol::Http11, true, buffer, "http1.codec_error"); } -TEST_P(Http1ServerConnectionImplTest, Http11InvalidTrailerPost) { +TEST_F(Http1ServerConnectionImplTest, Http11InvalidTrailerPost) { initialize(); MockRequestDecoder decoder; @@ -921,7 +864,7 @@ TEST_P(Http1ServerConnectionImplTest, Http11InvalidTrailerPost) { EXPECT_TRUE(isCodecProtocolError(status)); } -TEST_P(Http1ServerConnectionImplTest, Http11AbsolutePathNoSlash) { +TEST_F(Http1ServerConnectionImplTest, Http11AbsolutePathNoSlash) { initialize(); TestRequestHeaderMapImpl expected_headers{ @@ -930,21 +873,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( @@ -952,7 +895,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{ @@ -961,7 +904,7 @@ TEST_P(Http1ServerConnectionImplTest, Http11RelativeOnly) { expectHeadersTest(Protocol::Http11, false, buffer, expected_headers); } -TEST_P(Http1ServerConnectionImplTest, Http11Options) { +TEST_F(Http1ServerConnectionImplTest, Http11Options) { initialize(); TestRequestHeaderMapImpl expected_headers{ @@ -970,7 +913,7 @@ TEST_P(Http1ServerConnectionImplTest, Http11Options) { expectHeadersTest(Protocol::Http11, true, buffer, expected_headers); } -TEST_P(Http1ServerConnectionImplTest, SimpleGet) { +TEST_F(Http1ServerConnectionImplTest, SimpleGet) { initialize(); InSequence sequence; @@ -987,7 +930,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"}}); @@ -1007,7 +950,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; @@ -1027,7 +970,7 @@ TEST_P(Http1ServerConnectionImplTest, BadRequestNoStream) { // 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; @@ -1039,7 +982,7 @@ TEST_P(Http1ServerConnectionImplTest, RejectInvalidMethod) { EXPECT_TRUE(isCodecProtocolError(status)); } -TEST_P(Http1ServerConnectionImplTest, BadRequestStartedStream) { +TEST_F(Http1ServerConnectionImplTest, BadRequestStartedStream) { initialize(); MockRequestDecoder decoder; @@ -1055,7 +998,7 @@ TEST_P(Http1ServerConnectionImplTest, BadRequestStartedStream) { EXPECT_TRUE(isCodecProtocolError(status)); } -TEST_P(Http1ServerConnectionImplTest, FloodProtection) { +TEST_F(Http1ServerConnectionImplTest, FloodProtection) { initialize(); NiceMock decoder; @@ -1106,7 +1049,7 @@ TEST_P(Http1ServerConnectionImplTest, FloodProtection) { } } -TEST_P(Http1ServerConnectionImplTest, HostHeaderTranslation) { +TEST_F(Http1ServerConnectionImplTest, HostHeaderTranslation) { initialize(); InSequence sequence; @@ -1126,7 +1069,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. @@ -1151,7 +1094,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(); @@ -1175,7 +1118,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(); @@ -1198,7 +1141,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(); @@ -1219,7 +1162,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(); @@ -1242,7 +1185,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) { @@ -1266,7 +1209,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'}) { @@ -1286,7 +1229,7 @@ TEST_P(Http1ServerConnectionImplTest, HeaderMutateEmbeddedCRLF) { } } -TEST_P(Http1ServerConnectionImplTest, CloseDuringHeadersComplete) { +TEST_F(Http1ServerConnectionImplTest, CloseDuringHeadersComplete) { initialize(); InSequence sequence; @@ -1308,7 +1251,7 @@ TEST_P(Http1ServerConnectionImplTest, CloseDuringHeadersComplete) { EXPECT_NE(0U, buffer.length()); } -TEST_P(Http1ServerConnectionImplTest, PostWithContentLength) { +TEST_F(Http1ServerConnectionImplTest, PostWithContentLength) { initialize(); InSequence sequence; @@ -1334,7 +1277,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; @@ -1359,7 +1302,7 @@ TEST_P(Http1ServerConnectionImplTest, PostWithContentLengthFragmentedBuffer) { EXPECT_EQ(0U, buffer.length()); } -TEST_P(Http1ServerConnectionImplTest, HeaderOnlyResponse) { +TEST_F(Http1ServerConnectionImplTest, HeaderOnlyResponse) { initialize(); NiceMock decoder; @@ -1385,7 +1328,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; @@ -1411,7 +1354,7 @@ TEST_P(Http1ServerConnectionImplTest, LargeHeaderResponseEncode) { output); } -TEST_P(Http1ServerConnectionImplTest, HeaderOnlyResponseTrainProperHeaders) { +TEST_F(Http1ServerConnectionImplTest, HeaderOnlyResponseTrainProperHeaders) { codec_settings_.header_key_format_ = Http1Settings::HeaderKeyFormat::ProperCase; initialize(); @@ -1438,7 +1381,7 @@ TEST_P(Http1ServerConnectionImplTest, HeaderOnlyResponseTrainProperHeaders) { output); } -TEST_P(Http1ServerConnectionImplTest, HeaderOnlyResponseWith204) { +TEST_F(Http1ServerConnectionImplTest, HeaderOnlyResponseWith204) { initialize(); NiceMock decoder; @@ -1462,7 +1405,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; @@ -1493,7 +1436,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; @@ -1516,7 +1459,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; @@ -1552,7 +1495,7 @@ TEST_P(Http1ServerConnectionImplTest, ChunkedResponse) { output); } -TEST_P(Http1ServerConnectionImplTest, ChunkedResponseWithTrailers) { +TEST_F(Http1ServerConnectionImplTest, ChunkedResponseWithTrailers) { codec_settings_.enable_trailers_ = true; initialize(); NiceMock decoder; @@ -1585,7 +1528,7 @@ TEST_P(Http1ServerConnectionImplTest, ChunkedResponseWithTrailers) { output); } -TEST_P(Http1ServerConnectionImplTest, ContentLengthResponse) { +TEST_F(Http1ServerConnectionImplTest, ContentLengthResponse) { initialize(); NiceMock decoder; @@ -1612,7 +1555,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; @@ -1636,7 +1579,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; @@ -1660,7 +1603,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; @@ -1686,11 +1629,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{ @@ -1701,7 +1644,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"}, @@ -1714,7 +1657,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"}, @@ -1727,7 +1670,7 @@ TEST_P(Http1ServerConnectionImplTest, IgnoreUpgradeH2cCloseEtc) { expectHeadersTest(Protocol::Http11, true, buffer, expected_headers); } -TEST_P(Http1ServerConnectionImplTest, UpgradeRequest) { +TEST_F(Http1ServerConnectionImplTest, UpgradeRequest) { initialize(); InSequence sequence; @@ -1751,7 +1694,7 @@ TEST_P(Http1ServerConnectionImplTest, UpgradeRequest) { EXPECT_TRUE(status.ok()); } -TEST_P(Http1ServerConnectionImplTest, UpgradeRequestWithEarlyData) { +TEST_F(Http1ServerConnectionImplTest, UpgradeRequestWithEarlyData) { initialize(); InSequence sequence; @@ -1767,7 +1710,7 @@ TEST_P(Http1ServerConnectionImplTest, UpgradeRequestWithEarlyData) { EXPECT_TRUE(status.ok()); } -TEST_P(Http1ServerConnectionImplTest, UpgradeRequestWithTEChunked) { +TEST_F(Http1ServerConnectionImplTest, UpgradeRequestWithTEChunked) { initialize(); InSequence sequence; @@ -1785,7 +1728,7 @@ TEST_P(Http1ServerConnectionImplTest, UpgradeRequestWithTEChunked) { EXPECT_TRUE(status.ok()); } -TEST_P(Http1ServerConnectionImplTest, UpgradeRequestWithNoBody) { +TEST_F(Http1ServerConnectionImplTest, UpgradeRequestWithNoBody) { initialize(); InSequence sequence; @@ -1804,7 +1747,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; @@ -1828,7 +1771,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; @@ -1852,7 +1795,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; @@ -1865,7 +1808,7 @@ TEST_P(Http1ServerConnectionImplTest, ConnectRequestAbsoluteURLNotallowed) { EXPECT_TRUE(isCodecProtocolError(status)); } -TEST_P(Http1ServerConnectionImplTest, ConnectRequestWithEarlyData) { +TEST_F(Http1ServerConnectionImplTest, ConnectRequestWithEarlyData) { initialize(); InSequence sequence; @@ -1880,7 +1823,7 @@ TEST_P(Http1ServerConnectionImplTest, ConnectRequestWithEarlyData) { EXPECT_TRUE(status.ok()); } -TEST_P(Http1ServerConnectionImplTest, ConnectRequestWithTEChunked) { +TEST_F(Http1ServerConnectionImplTest, ConnectRequestWithTEChunked) { initialize(); InSequence sequence; @@ -1897,7 +1840,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; @@ -1913,7 +1856,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; @@ -1930,7 +1873,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(); @@ -1964,51 +1907,39 @@ TEST_P(Http1ServerConnectionImplTest, WatermarkTest) { ->onUnderlyingConnectionBelowWriteBufferLowWatermark(); } -TEST_P(Http1ServerConnectionImplTest, TestSmugglingDisallowChunkedContentLength0) { +TEST_F(Http1ServerConnectionImplTest, TestSmugglingDisallowChunkedContentLength0) { testServerAllowChunkedContentLength(0, false); } -TEST_P(Http1ServerConnectionImplTest, TestSmugglingDisallowChunkedContentLength1) { +TEST_F(Http1ServerConnectionImplTest, TestSmugglingDisallowChunkedContentLength1) { // content-length less than POST body size testServerAllowChunkedContentLength(1, false); } -TEST_P(Http1ServerConnectionImplTest, TestSmugglingDisallowChunkedContentLength100) { +TEST_F(Http1ServerConnectionImplTest, TestSmugglingDisallowChunkedContentLength100) { // content-length greater than POST body size testServerAllowChunkedContentLength(100, false); } -TEST_P(Http1ServerConnectionImplTest, TestSmugglingAllowChunkedContentLength0) { +TEST_F(Http1ServerConnectionImplTest, TestSmugglingAllowChunkedContentLength0) { testServerAllowChunkedContentLength(0, true); } -TEST_P(Http1ServerConnectionImplTest, TestSmugglingAllowChunkedContentLength1) { +TEST_F(Http1ServerConnectionImplTest, TestSmugglingAllowChunkedContentLength1) { // content-length less than POST body size testServerAllowChunkedContentLength(1, true); } -TEST_P(Http1ServerConnectionImplTest, TestSmugglingAllowChunkedContentLength100) { +TEST_F(Http1ServerConnectionImplTest, TestSmugglingAllowChunkedContentLength100) { // content-length greater than POST body size testServerAllowChunkedContentLength(100, true); } -class Http1ClientConnectionImplTest : public Http1CodecTestBase, - public testing::TestWithParam { +class Http1ClientConnectionImplTest : public Http1CodecTestBase { public: - bool testingNewCodec() { return GetParam(); } - void initialize() { - if (testingNewCodec()) { - 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_); - } + codec_ = std::make_unique( + connection_, http1CodecStats(), callbacks_, codec_settings_, max_response_headers_count_); } void readDisableOnRequestEncoder(RequestEncoder* request_encoder, bool disable) { - if (testingNewCodec()) { - dynamic_cast(request_encoder)->readDisable(disable); - } else { - dynamic_cast(request_encoder)->readDisable(disable); - } + dynamic_cast(request_encoder)->readDisable(disable); } NiceMock connection_; @@ -2023,21 +1954,11 @@ class Http1ClientConnectionImplTest : public Http1CodecTestBase, 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"; - }); - void Http1ClientConnectionImplTest::testClientAllowChunkedContentLength(uint32_t content_length, bool allow_chunked_length) { codec_settings_.allow_chunked_length_ = allow_chunked_length; - if (testingNewCodec()) { - 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_); - } + codec_ = std::make_unique( + connection_, http1CodecStats(), callbacks_, codec_settings_, max_response_headers_count_); NiceMock response_decoder; Http::RequestEncoder& request_encoder = codec_->newStream(response_decoder); @@ -2074,7 +1995,7 @@ void Http1ClientConnectionImplTest::testClientAllowChunkedContentLength(uint32_t }; } -TEST_P(Http1ClientConnectionImplTest, SimpleGet) { +TEST_F(Http1ClientConnectionImplTest, SimpleGet) { initialize(); MockResponseDecoder response_decoder; @@ -2088,7 +2009,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(); @@ -2104,7 +2025,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; @@ -2118,7 +2039,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; @@ -2132,7 +2053,7 @@ 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; @@ -2161,7 +2082,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"); @@ -2169,7 +2090,7 @@ TEST_P(Http1ClientConnectionImplTest, PrematureResponse) { EXPECT_TRUE(isPrematureResponseError(status)); } -TEST_P(Http1ClientConnectionImplTest, EmptyBodyResponse503) { +TEST_F(Http1ClientConnectionImplTest, EmptyBodyResponse503) { initialize(); NiceMock response_decoder; @@ -2183,7 +2104,7 @@ TEST_P(Http1ClientConnectionImplTest, EmptyBodyResponse503) { EXPECT_TRUE(status.ok()); } -TEST_P(Http1ClientConnectionImplTest, EmptyBodyResponse200) { +TEST_F(Http1ClientConnectionImplTest, EmptyBodyResponse200) { initialize(); NiceMock response_decoder; @@ -2197,7 +2118,7 @@ TEST_P(Http1ClientConnectionImplTest, EmptyBodyResponse200) { EXPECT_TRUE(status.ok()); } -TEST_P(Http1ClientConnectionImplTest, HeadRequest) { +TEST_F(Http1ClientConnectionImplTest, HeadRequest) { initialize(); NiceMock response_decoder; @@ -2211,7 +2132,7 @@ TEST_P(Http1ClientConnectionImplTest, HeadRequest) { EXPECT_TRUE(status.ok()); } -TEST_P(Http1ClientConnectionImplTest, 204Response) { +TEST_F(Http1ClientConnectionImplTest, 204Response) { initialize(); NiceMock response_decoder; @@ -2226,7 +2147,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(); @@ -2262,7 +2183,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(); @@ -2296,7 +2217,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(); @@ -2331,7 +2252,7 @@ TEST_P(Http1ClientConnectionImplTest, 204ResponseTransferEncodingNotAllowed) { } // 100 response followed by 200 results in a [decode100ContinueHeaders, decodeHeaders] sequence. -TEST_P(Http1ClientConnectionImplTest, ContinueHeaders) { +TEST_F(Http1ClientConnectionImplTest, ContinueHeaders) { initialize(); NiceMock response_decoder; @@ -2353,7 +2274,7 @@ TEST_P(Http1ClientConnectionImplTest, ContinueHeaders) { } // Multiple 100 responses are passed to the response encoder (who is responsible for coalescing). -TEST_P(Http1ClientConnectionImplTest, MultipleContinueHeaders) { +TEST_F(Http1ClientConnectionImplTest, MultipleContinueHeaders) { initialize(); NiceMock response_decoder; @@ -2382,7 +2303,7 @@ TEST_P(Http1ClientConnectionImplTest, MultipleContinueHeaders) { // 101/102 headers etc. are passed to the response encoder (who is responsibly for deciding to // upgrade, ignore, etc.). -TEST_P(Http1ClientConnectionImplTest, 1xxNonContinueHeaders) { +TEST_F(Http1ClientConnectionImplTest, 1xxNonContinueHeaders) { initialize(); NiceMock response_decoder; @@ -2397,7 +2318,7 @@ TEST_P(Http1ClientConnectionImplTest, 1xxNonContinueHeaders) { } // 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(); @@ -2433,7 +2354,7 @@ TEST_P(Http1ClientConnectionImplTest, 101ResponseTransferEncodingNotAllowed) { } } -TEST_P(Http1ClientConnectionImplTest, BadEncodeParams) { +TEST_F(Http1ClientConnectionImplTest, BadEncodeParams) { initialize(); NiceMock response_decoder; @@ -2445,24 +2366,15 @@ TEST_P(Http1ClientConnectionImplTest, BadEncodeParams) { // old codecs will still throw an exception (that presently will be uncaught in contexts like // sendLocalReply). Http::RequestEncoder& request_encoder = codec_->newStream(response_decoder); - if (testingNewCodec()) { - EXPECT_THAT( - request_encoder.encodeHeaders(TestRequestHeaderMapImpl{{":path", "/"}}, true).message(), - testing::HasSubstr("missing required")); - EXPECT_THAT( - request_encoder.encodeHeaders(TestRequestHeaderMapImpl{{":method", "GET"}}, true).message(), - testing::HasSubstr("missing required")); - } else { - EXPECT_THROW( - request_encoder.encodeHeaders(TestRequestHeaderMapImpl{{":path", "/"}}, true).IgnoreError(), - CodecClientException); - EXPECT_THROW(request_encoder.encodeHeaders(TestRequestHeaderMapImpl{{":method", "GET"}}, true) - .IgnoreError(), - CodecClientException); - } + EXPECT_THAT( + request_encoder.encodeHeaders(TestRequestHeaderMapImpl{{":path", "/"}}, true).message(), + testing::HasSubstr("missing required")); + EXPECT_THAT( + request_encoder.encodeHeaders(TestRequestHeaderMapImpl{{":method", "GET"}}, true).message(), + testing::HasSubstr("missing required")); } -TEST_P(Http1ClientConnectionImplTest, NoContentLengthResponse) { +TEST_F(Http1ClientConnectionImplTest, NoContentLengthResponse) { initialize(); NiceMock response_decoder; @@ -2484,7 +2396,7 @@ TEST_P(Http1ClientConnectionImplTest, NoContentLengthResponse) { EXPECT_TRUE(status.ok()); } -TEST_P(Http1ClientConnectionImplTest, ResponseWithTrailers) { +TEST_F(Http1ClientConnectionImplTest, ResponseWithTrailers) { initialize(); NiceMock response_decoder; @@ -2499,7 +2411,7 @@ TEST_P(Http1ClientConnectionImplTest, ResponseWithTrailers) { EXPECT_TRUE(status.ok()); } -TEST_P(Http1ClientConnectionImplTest, GiantPath) { +TEST_F(Http1ClientConnectionImplTest, GiantPath) { initialize(); NiceMock response_decoder; @@ -2514,7 +2426,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. @@ -2524,7 +2436,7 @@ TEST_P(Http1ClientConnectionImplTest, PrematureUpgradeResponse) { EXPECT_TRUE(isPrematureResponseError(status)); } -TEST_P(Http1ClientConnectionImplTest, UpgradeResponse) { +TEST_F(Http1ClientConnectionImplTest, UpgradeResponse) { initialize(); InSequence s; @@ -2560,7 +2472,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; @@ -2584,7 +2496,7 @@ TEST_P(Http1ClientConnectionImplTest, UpgradeResponseWithEarlyData) { EXPECT_TRUE(status.ok()); } -TEST_P(Http1ClientConnectionImplTest, ConnectResponse) { +TEST_F(Http1ClientConnectionImplTest, ConnectResponse) { initialize(); InSequence s; @@ -2615,7 +2527,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; @@ -2634,7 +2546,7 @@ TEST_P(Http1ClientConnectionImplTest, ConnectResponseWithEarlyData) { EXPECT_TRUE(status.ok()); } -TEST_P(Http1ClientConnectionImplTest, ConnectRejected) { +TEST_F(Http1ClientConnectionImplTest, ConnectRejected) { initialize(); InSequence s; @@ -2652,7 +2564,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(); @@ -2687,7 +2599,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; @@ -2721,7 +2633,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; @@ -2751,43 +2663,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; @@ -2809,19 +2721,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(); @@ -2852,7 +2764,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(); @@ -2879,27 +2791,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; @@ -2918,7 +2830,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; @@ -2938,7 +2850,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; @@ -2956,7 +2868,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; @@ -2974,7 +2886,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; @@ -2990,7 +2902,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; @@ -3007,7 +2919,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; @@ -3025,7 +2937,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(); @@ -3042,27 +2954,27 @@ TEST_P(Http1ClientConnectionImplTest, ManyResponseHeadersAccepted) { status = codec_->dispatch(buffer); } -TEST_P(Http1ClientConnectionImplTest, TestResponseSplit0) { +TEST_F(Http1ClientConnectionImplTest, TestResponseSplit0) { testClientAllowChunkedContentLength(0, false); } -TEST_P(Http1ClientConnectionImplTest, TestResponseSplit1) { +TEST_F(Http1ClientConnectionImplTest, TestResponseSplit1) { testClientAllowChunkedContentLength(1, false); } -TEST_P(Http1ClientConnectionImplTest, TestResponseSplit100) { +TEST_F(Http1ClientConnectionImplTest, TestResponseSplit100) { testClientAllowChunkedContentLength(100, false); } -TEST_P(Http1ClientConnectionImplTest, TestResponseSplitAllowChunkedLength0) { +TEST_F(Http1ClientConnectionImplTest, TestResponseSplitAllowChunkedLength0) { testClientAllowChunkedContentLength(0, true); } -TEST_P(Http1ClientConnectionImplTest, TestResponseSplitAllowChunkedLength1) { +TEST_F(Http1ClientConnectionImplTest, TestResponseSplitAllowChunkedLength1) { testClientAllowChunkedContentLength(1, true); } -TEST_P(Http1ClientConnectionImplTest, TestResponseSplitAllowChunkedLength100) { +TEST_F(Http1ClientConnectionImplTest, TestResponseSplitAllowChunkedLength100) { testClientAllowChunkedContentLength(100, true); } diff --git a/test/common/http/http2/BUILD b/test/common/http/http2/BUILD index 8b95de6b6cf4..8dca77e677b5 100644 --- a/test/common/http/http2/BUILD +++ b/test/common/http/http2/BUILD @@ -10,49 +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, - deps = CODEC_TEST_DEPS, -) - -envoy_cc_test( - name = "codec_impl_legacy_test", - srcs = ["codec_impl_test.cc"], - # The default codec is the new codec. Disable runtime flag for testing old codec. - 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/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", ], - shard_count = 5, - deps = CODEC_TEST_DEPS, ) envoy_cc_test_library( @@ -60,8 +46,8 @@ 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", + "//test/mocks:common_lib", ], ) @@ -112,6 +98,7 @@ envoy_cc_test_library( "//source/common/http:utility_lib", "//source/common/http/http2:codec_lib", "//test/common/http:common_lib", + "//test/mocks:common_lib", "//test/mocks/http:http_mocks", "//test/mocks/network:network_mocks", "//test/test_common:environment_lib", diff --git a/test/common/http/http2/codec_impl_test.cc b/test/common/http/http2/codec_impl_test.cc index dc266bfa68dd..a8ab0eaf146d 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,12 @@ 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_, random_, + max_request_headers_kb_, max_response_headers_count_, ProdNghttp2SessionFactory::get()); + server_ = std::make_unique( + server_connection_, server_callbacks_, server_stats_store_, server_http2_options_, random_, + max_request_headers_kb_, max_request_headers_count_, headers_with_underscores_action_); request_encoder_ = &client_->newStream(response_decoder_); setupDefaultConnectionMocks(); @@ -240,13 +228,14 @@ 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 random_; NiceMock server_connection_; MockServerConnectionCallbacks server_callbacks_; - std::unique_ptr server_; + std::unique_ptr server_; ConnectionWrapper server_wrapper_; MockResponseDecoder response_decoder_; RequestEncoder* request_encoder_; @@ -648,16 +637,10 @@ TEST_P(Http2CodecImplTest, RefusedStreamReset) { TEST_P(Http2CodecImplTest, InvalidHeadersFrame) { initialize(); - if (Runtime::runtimeFeatureEnabled("envoy.reloadable_features.new_codec_behavior")) { - const auto status = request_encoder_->encodeHeaders(TestRequestHeaderMapImpl{}, true); + const auto status = request_encoder_->encodeHeaders(TestRequestHeaderMapImpl{}, true); - EXPECT_FALSE(status.ok()); - EXPECT_THAT(status.message(), testing::HasSubstr("missing required")); - } else { - EXPECT_THROW(request_encoder_->encodeHeaders(TestRequestHeaderMapImpl{}, true).IgnoreError(), - ServerCodecError); - EXPECT_EQ(1, server_stats_store_.counter("http2.rx_messaging_error").value()); - } + EXPECT_FALSE(status.ok()); + EXPECT_THAT(status.message(), testing::HasSubstr("missing required")); } TEST_P(Http2CodecImplTest, TrailingHeaders) { @@ -956,7 +939,7 @@ TEST_P(Http2CodecImplTest, ConnectionKeepaliveJitter) { initialize(); for (uint64_t i = 0; i < 250; i++) { - EXPECT_CALL(client_->random_generator_, random()).WillOnce(Return(i)); + EXPECT_CALL(random_, random()).WillOnce(Return(i)); send_timer->callback_(); } @@ -1084,21 +1067,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()); @@ -1143,7 +1126,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. @@ -1188,7 +1171,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_, @@ -1230,7 +1213,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')); @@ -1416,7 +1399,7 @@ TEST_P(Http2CodecImplFlowControlTest, WindowUpdateOnReadResumingFlood) { Buffer::OwnedImpl long_data(std::string(initial_stream_window / 2, 'a')); request_encoder_->encodeData(long_data, false); - EXPECT_EQ(initial_stream_window / 2, server_->getStreamUnconsumedBytes(1)); + EXPECT_EQ(initial_stream_window / 2, server_->getStream(1)->unconsumed_bytes_); // pre-fill downstream outbound frame queue TestResponseHeaderMapImpl response_headers{{":status", "200"}}; @@ -1547,23 +1530,12 @@ 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_); - - } 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_, random_, + max_request_headers_kb_, max_response_headers_count_, ProdNghttp2SessionFactory::get()); + server_ = std::make_unique( + server_connection_, server_callbacks_, server_stats_store_, server_http2_options_, random_, + 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(); @@ -2070,12 +2042,8 @@ TEST_P(Http2CodecImplTest, PingFlood) { buffer.move(frame); })); - // Legacy codec does not propagate error details and uses generic error message - EXPECT_THROW_WITH_MESSAGE( - client_->sendPendingFrames().IgnoreError(), ServerCodecError, - Runtime::runtimeFeatureEnabled("envoy.reloadable_features.new_codec_behavior") - ? "Too many control frames in the outbound queue." - : "Too many frames in the outbound queue."); + EXPECT_THROW_WITH_MESSAGE(client_->sendPendingFrames().IgnoreError(), ServerCodecError, + "Too many control frames in the outbound queue."); EXPECT_EQ(1, server_stats_store_.counter("http2.outbound_control_flood").value()); } @@ -2144,12 +2112,8 @@ TEST_P(Http2CodecImplTest, PingFloodCounterReset) { // 1 more ping frame should overflow the outbound frame limit. EXPECT_EQ(0, nghttp2_submit_ping(client_->session(), NGHTTP2_FLAG_NONE, nullptr)); - // Legacy codec does not propagate error details and uses generic error message - EXPECT_THROW_WITH_MESSAGE( - client_->sendPendingFrames().IgnoreError(), ServerCodecError, - Runtime::runtimeFeatureEnabled("envoy.reloadable_features.new_codec_behavior") - ? "Too many control frames in the outbound queue." - : "Too many frames in the outbound queue."); + EXPECT_THROW_WITH_MESSAGE(client_->sendPendingFrames().IgnoreError(), ServerCodecError, + "Too many control frames in the outbound queue."); } // Verify that codec detects flood of outbound HEADER frames @@ -2415,12 +2379,8 @@ TEST_P(Http2CodecImplTest, MetadataFlood) { TEST_P(Http2CodecImplTest, PriorityFlood) { priorityFlood(); - // Legacy codec does not propagate error details and uses generic error message - EXPECT_THROW_WITH_MESSAGE( - client_->sendPendingFrames().IgnoreError(), ServerCodecError, - Runtime::runtimeFeatureEnabled("envoy.reloadable_features.new_codec_behavior") - ? "Too many PRIORITY frames" - : "Flooding was detected in this HTTP/2 session, and it must be closed"); + EXPECT_THROW_WITH_MESSAGE(client_->sendPendingFrames().IgnoreError(), ServerCodecError, + "Too many PRIORITY frames"); } TEST_P(Http2CodecImplTest, PriorityFloodOverride) { @@ -2432,12 +2392,8 @@ TEST_P(Http2CodecImplTest, PriorityFloodOverride) { TEST_P(Http2CodecImplTest, WindowUpdateFlood) { windowUpdateFlood(); - // Legacy codec does not propagate error details and uses generic error message - EXPECT_THROW_WITH_MESSAGE( - client_->sendPendingFrames().IgnoreError(), ServerCodecError, - Runtime::runtimeFeatureEnabled("envoy.reloadable_features.new_codec_behavior") - ? "Too many WINDOW_UPDATE frames" - : "Flooding was detected in this HTTP/2 session, and it must be closed"); + EXPECT_THROW_WITH_MESSAGE(client_->sendPendingFrames().IgnoreError(), ServerCodecError, + "Too many WINDOW_UPDATE frames"); } TEST_P(Http2CodecImplTest, WindowUpdateFloodOverride) { @@ -2452,15 +2408,8 @@ TEST_P(Http2CodecImplTest, EmptyDataFlood) { EXPECT_CALL(request_decoder_, decodeData(_, false)); auto status = server_wrapper_.dispatch(data, *server_); EXPECT_FALSE(status.ok()); - if (Runtime::runtimeFeatureEnabled("envoy.reloadable_features.new_codec_behavior")) { - EXPECT_TRUE(isInboundFramesWithEmptyPayloadError(status)); - EXPECT_EQ("Too many consecutive frames with an empty payload", status.message()); - } else { - // Legacy codec does not propagate error details and uses generic error message - EXPECT_TRUE(isBufferFloodError(status)); - EXPECT_EQ("Flooding was detected in this HTTP/2 session, and it must be closed", - status.message()); - } + EXPECT_TRUE(isInboundFramesWithEmptyPayloadError(status)); + EXPECT_EQ("Too many consecutive frames with an empty payload", status.message()); } TEST_P(Http2CodecImplTest, EmptyDataFloodOverride) { @@ -2600,10 +2549,6 @@ TEST_P(Http2CodecImplTest, KeepAliveCausesOutboundFlood) { // Trigger sending a PING, which should overflow the outbound frame queue and cause // client to be disconnected - if (!Runtime::runtimeFeatureEnabled("envoy.reloadable_features.new_codec_behavior")) { - // new codec does not schedule timeout callback if the PING had triggered flood protection - EXPECT_CALL(*timeout_timer, enableTimer(std::chrono::milliseconds(timeout_ms), _)); - } send_timer->callback_(); EXPECT_TRUE(violation_callback->enabled_); @@ -2679,67 +2624,78 @@ TEST_P(Http2CodecImplTest, ConnectTest) { response_encoder_->getStream().resetStream(StreamResetReason::ConnectError); } -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) {} + Random::RandomGenerator& random, uint32_t max_request_headers_kb, + uint32_t max_request_headers_count, Nghttp2SessionFactory& http2_session_factory) + : TestClientConnectionImpl(connection, callbacks, scope, http2_options, random, + 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, - const nghttp2_option*) override; + 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*, typename Nghttp2SessionFactoryType::ConnectionImplType*, + void init(nghttp2_session*, ConnectionImpl*, const envoy::config::core::v3::Http2ProtocolOptions&) override {} private: @@ -2747,78 +2703,6 @@ class TestNghttp2SessionFactory : public Nghttp2SessionFactoryType, nghttp2_option* options_; }; -template -nghttp2_session* -TestNghttp2SessionFactory::create( - const nghttp2_session_callbacks*, - typename Nghttp2SessionFactoryType::ConnectionImplType* connection, const nghttp2_option*) { - // 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; -} - -template <> -nghttp2_session* TestNghttp2SessionFactory:: - create(const nghttp2_session_callbacks*, - Envoy::Http::Legacy::Http2::ProdNghttp2SessionFactory::ConnectionImplType* connection, - const nghttp2_option*) { - // 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< - Envoy::Http::Legacy::Http2::ProdNghttp2SessionFactory::ConnectionImplType*>( - 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; -} - -using TestNghttp2SessionFactoryNew = - TestNghttp2SessionFactory; -using TestNghttp2SessionFactoryLegacy = - TestNghttp2SessionFactory; - class Http2CodecMetadataTest : public Http2CodecImplTestFixture, public ::testing::Test { public: Http2CodecMetadataTest() = default; @@ -2828,27 +2712,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_, random_, + 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_, random_, + 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()); @@ -2860,7 +2729,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 20438d42f26e..dcaeff88c534 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" #include "test/mocks/common.h" @@ -35,7 +34,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) { @@ -60,134 +59,62 @@ class TestCodecSettingsProvider { absl::node_hash_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 TestCodecSettingsProvider, + public ServerConnectionImpl { public: TestServerConnectionImpl( Network::Connection& connection, ServerConnectionCallbacks& callbacks, Stats::Scope& scope, const envoy::config::core::v3::Http2ProtocolOptions& http2_options, - uint32_t max_request_headers_kb, uint32_t max_request_headers_count, + Random::RandomGenerator& random, 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(), random_, 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(), random, 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); } - - testing::NiceMock random_; }; -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 Status 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) {} - - testing::NiceMock random_generator_; -}; - -template -class TestClientConnectionImpl : public TestClientConnection, public CodecImplType { +class TestClientConnectionImpl : public TestCodecStatsProvider, + public TestCodecSettingsProvider, + public ClientConnectionImpl { 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(), random_generator_, 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(); - } - Status sendPendingFrames() override; + Random::RandomGenerator& random, uint32_t max_request_headers_kb, + uint32_t max_request_headers_count, + Nghttp2SessionFactory& http2_session_factory) + : TestCodecStatsProvider(scope), + ClientConnectionImpl(connection, callbacks, http2CodecStats(), random, 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); } }; -template -Status TestClientConnectionImpl::sendPendingFrames() { - return CodecImplType::sendPendingFrames(); -} - -template <> -Status -TestClientConnectionImpl::sendPendingFrames() { - Envoy::Http::Legacy::Http2::ClientConnectionImpl::sendPendingFrames(); - return okStatus(); -} - -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.h b/test/common/http/http2/frame_replay.h index 2922d6a19110..36069dfa8436 100644 --- a/test/common/http/http2/frame_replay.h +++ b/test/common/http/http2/frame_replay.h @@ -4,6 +4,7 @@ #include "common/stats/isolated_store_impl.h" +#include "test/mocks/common.h" #include "test/mocks/http/mocks.h" #include "test/mocks/network/mocks.h" #include "test/test_common/utility.h" @@ -70,6 +71,7 @@ class ClientCodecFrameInjector : public CodecFrameInjector { MockResponseDecoder response_decoder_; RequestEncoder* request_encoder_; MockStreamCallbacks client_stream_callbacks_; + Random::MockRandomGenerator random_; }; // Holds mock and environment placeholders for an HTTP/2 server codec. Sets up expectations for @@ -80,6 +82,7 @@ class ServerCodecFrameInjector : public CodecFrameInjector { ::testing::NiceMock server_connection_; MockServerConnectionCallbacks server_callbacks_; + Random::MockRandomGenerator random_; MockRequestDecoder request_decoder_; MockStreamCallbacks server_stream_callbacks_; }; diff --git a/test/common/http/http2/frame_replay_test.cc b/test/common/http/http2/frame_replay_test.cc index 510002cf3fde..91a2ba4894e1 100644 --- a/test/common/http/http2/frame_replay_test.cc +++ b/test/common/http/http2/frame_replay_test.cc @@ -27,7 +27,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. @@ -57,9 +57,9 @@ 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, + codec.random_, Http::DEFAULT_MAX_REQUEST_HEADERS_KB, Http::DEFAULT_MAX_HEADERS_COUNT, envoy::config::core::v3::HttpProtocolOptions::ALLOW); EXPECT_TRUE(codec.write(WellKnownFrames::clientConnectionPrefaceFrame(), connection).ok()); EXPECT_TRUE(codec.write(WellKnownFrames::defaultSettingsFrame(), connection).ok()); @@ -90,9 +90,9 @@ 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, + codec.random_, Http::DEFAULT_MAX_REQUEST_HEADERS_KB, Http::DEFAULT_MAX_HEADERS_COUNT, ProdNghttp2SessionFactory::get()); setupStream(codec, connection); @@ -135,9 +135,9 @@ 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, + codec.random_, Http::DEFAULT_MAX_REQUEST_HEADERS_KB, Http::DEFAULT_MAX_HEADERS_COUNT, envoy::config::core::v3::HttpProtocolOptions::ALLOW); EXPECT_TRUE(codec.write(WellKnownFrames::clientConnectionPrefaceFrame(), connection).ok()); EXPECT_TRUE(codec.write(WellKnownFrames::defaultSettingsFrame(), connection).ok()); @@ -170,9 +170,9 @@ 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, + codec.random_, Http::DEFAULT_MAX_REQUEST_HEADERS_KB, Http::DEFAULT_MAX_HEADERS_COUNT, ProdNghttp2SessionFactory::get()); setupStream(codec, connection); @@ -200,9 +200,9 @@ 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, + codec.random_, Http::DEFAULT_MAX_REQUEST_HEADERS_KB, Http::DEFAULT_MAX_HEADERS_COUNT, envoy::config::core::v3::HttpProtocolOptions::ALLOW); EXPECT_TRUE(codec.write(WellKnownFrames::clientConnectionPrefaceFrame(), connection).ok()); EXPECT_TRUE(codec.write(WellKnownFrames::defaultSettingsFrame(), connection).ok()); @@ -233,9 +233,9 @@ 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, + codec.random_, Http::DEFAULT_MAX_REQUEST_HEADERS_KB, Http::DEFAULT_MAX_HEADERS_COUNT, ProdNghttp2SessionFactory::get()); setupStream(codec, connection); @@ -268,9 +268,9 @@ 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, + codec.random_, Http::DEFAULT_MAX_REQUEST_HEADERS_KB, Http::DEFAULT_MAX_HEADERS_COUNT, envoy::config::core::v3::HttpProtocolOptions::ALLOW); EXPECT_TRUE(codec.write(WellKnownFrames::clientConnectionPrefaceFrame(), connection).ok()); EXPECT_TRUE(codec.write(WellKnownFrames::defaultSettingsFrame(), connection).ok()); @@ -306,9 +306,9 @@ 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, + codec.random_, Http::DEFAULT_MAX_REQUEST_HEADERS_KB, Http::DEFAULT_MAX_HEADERS_COUNT, ProdNghttp2SessionFactory::get()); setupStream(codec, connection); diff --git a/test/common/http/http2/request_header_fuzz_test.cc b/test/common/http/http2/request_header_fuzz_test.cc index 3af7f5c594ce..90b8cdd758e3 100644 --- a/test/common/http/http2/request_header_fuzz_test.cc +++ b/test/common/http/http2/request_header_fuzz_test.cc @@ -13,11 +13,11 @@ namespace Http { namespace Http2 { namespace { -void Replay(const Frame& frame, ServerCodecFrameInjector& codec) { +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, + codec.random_, Http::DEFAULT_MAX_REQUEST_HEADERS_KB, Http::DEFAULT_MAX_HEADERS_COUNT, envoy::config::core::v3::HttpProtocolOptions::ALLOW); Http::Status status = Http::okStatus(); status = codec.write(WellKnownFrames::clientConnectionPrefaceFrame(), connection); @@ -32,10 +32,10 @@ DEFINE_FUZZER(const uint8_t* buf, size_t len) { Frame frame; frame.assign(buf, buf + len); // Replay with the fuzzer bytes. - Replay(frame, codec); + replay(frame, codec); // Try again, but fixup the HEADERS frame to make it a valid HEADERS. FrameUtils::fixupHeaders(frame); - Replay(frame, codec); + replay(frame, codec); } } // namespace diff --git a/test/common/http/http2/response_header_fuzz_test.cc b/test/common/http/http2/response_header_fuzz_test.cc index 112f1392921a..93e1baff480b 100644 --- a/test/common/http/http2/response_header_fuzz_test.cc +++ b/test/common/http/http2/response_header_fuzz_test.cc @@ -14,11 +14,11 @@ namespace Http { namespace Http2 { namespace { -void Replay(const Frame& frame, ClientCodecFrameInjector& codec) { +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, + codec.random_, Http::DEFAULT_MAX_REQUEST_HEADERS_KB, Http::DEFAULT_MAX_HEADERS_COUNT, ProdNghttp2SessionFactory::get()); // Create a new stream. Http::Status status = Http::okStatus(); @@ -40,10 +40,10 @@ DEFINE_FUZZER(const uint8_t* buf, size_t len) { Frame frame; frame.assign(buf, buf + len); // Replay with the fuzzer bytes. - Replay(frame, codec); + replay(frame, codec); // Try again, but fixup the HEADERS frame to make it a valid HEADERS. FrameUtils::fixupHeaders(frame); - Replay(frame, codec); + replay(frame, codec); } } // namespace diff --git a/test/config/utility.cc b/test/config/utility.cc index a5fa2ddab6d0..e0e6012e520c 100644 --- a/test/config/utility.cc +++ b/test/config/utility.cc @@ -714,10 +714,6 @@ void ConfigHelper::enableDeprecatedV2Api() { addRuntimeOverride("envoy.features.enable_all_deprecated_features", "true"); } -void ConfigHelper::setNewCodecs() { - addRuntimeOverride("envoy.reloadable_features.new_codec_behavior", "true"); -} - void ConfigHelper::setProtocolOptions(envoy::config::cluster::v3::Cluster& cluster, HttpProtocolOptions& protocol_options) { if (cluster.typed_extension_protocol_options().contains( diff --git a/test/config/utility.h b/test/config/utility.h index 65e20e0a9052..b51843eb3341 100644 --- a/test/config/utility.h +++ b/test/config/utility.h @@ -279,9 +279,6 @@ class ConfigHelper { const envoy::extensions::filters::network::http_connection_manager::v3::LocalReplyConfig& config); - // Set new codecs to use for upstream and downstream codecs. - void setNewCodecs(); - using HttpProtocolOptions = envoy::extensions::upstreams::http::v3::HttpProtocolOptions; static void setProtocolOptions(envoy::config::cluster::v3::Cluster& cluster, HttpProtocolOptions& protocol_options); 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 192b7de90908..8b9724db98bd 100644 --- a/test/extensions/filters/network/http_connection_manager/config_test.cc +++ b/test/extensions/filters/network/http_connection_manager/config_test.cc @@ -1685,70 +1685,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)); - context_.cluster_manager_.initializeClusters({"cluster"}, {}); - context_.server_factory_context_.cluster_manager_.initializeClusters({"cluster"}, {}); - 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)); - context_.cluster_manager_.initializeClusters({"cluster"}, {}); - context_.server_factory_context_.cluster_manager_.initializeClusters({"cluster"}, {}); - auto http_connection_manager_factory = - HttpConnectionManagerFactory::createHttpConnectionManagerFactoryFromProto( - proto_config, context_, filter_callbacks); - http_connection_manager_factory(); -} - TEST_F(HttpConnectionManagerConfigTest, DynamicFilterWarmingNoDefault) { const std::string yaml_string = R"EOF( codec_type: http1 diff --git a/test/integration/BUILD b/test/integration/BUILD index 0778f624670d..f960c0503ae1 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_new_codecs_in_integration_tests", "envoy_sh_test", ) @@ -649,10 +648,6 @@ envoy_cc_test_library( hdrs = [ "fake_upstream.h", ], - copts = envoy_select_new_codecs_in_integration_tests( - ["-DENVOY_USE_NEW_CODECS_IN_INTEGRATION_TESTS"], - "@envoy", - ), deps = [ "//include/envoy/api:api_interface", "//include/envoy/grpc:status", @@ -670,9 +665,7 @@ envoy_cc_test_library( "//source/common/common:thread_lib", "//source/common/grpc:codec_lib", "//source/common/grpc:common_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/network:connection_balancer_lib", "//source/common/network:filter_lib", @@ -786,10 +779,6 @@ envoy_cc_test_library( "integration.h", "ssl_utility.h", ], - copts = envoy_select_new_codecs_in_integration_tests( - ["-DENVOY_USE_NEW_CODECS_IN_INTEGRATION_TESTS"], - "@envoy", - ), data = ["//test/common/runtime:filesystem_test_data"], deps = [ ":autonomous_upstream_lib", @@ -825,9 +814,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/base_integration_test.cc b/test/integration/base_integration_test.cc index 45a7019ed9ac..d4d8c2af371a 100644 --- a/test/integration/base_integration_test.cc +++ b/test/integration/base_integration_test.cc @@ -65,11 +65,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_NEW_CODECS_IN_INTEGRATION_TESTS mode, set runtime config to use legacy codecs. -#ifdef ENVOY_USE_NEW_CODECS_IN_INTEGRATION_TESTS - ENVOY_LOG_MISC(debug, "Using new codecs"); - setNewCodecs(); -#endif } BaseIntegrationTest::BaseIntegrationTest(Network::Address::IpVersion version, diff --git a/test/integration/base_integration_test.h b/test/integration/base_integration_test.h index 3180cf96ef3e..bc4b2d544097 100644 --- a/test/integration/base_integration_test.h +++ b/test/integration/base_integration_test.h @@ -80,7 +80,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 setNewCodecs() { config_helper_.setNewCodecs(); } FakeHttpConnection::Type upstreamProtocol() const { return upstream_config_.upstream_protocol_; } diff --git a/test/integration/fake_upstream.cc b/test/integration/fake_upstream.cc index c0aadbbc5f62..06234a2a7c78 100644 --- a/test/integration/fake_upstream.cc +++ b/test/integration/fake_upstream.cc @@ -8,8 +8,7 @@ #include "common/buffer/buffer_impl.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_legacy.h" +#include "common/http/http2/codec_impl.h" #include "common/network/address_impl.h" #include "common/network/listen_socket_impl.h" #include "common/network/socket_option_factory.h" @@ -311,29 +310,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, @@ -346,15 +322,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_NEW_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( @@ -362,15 +332,9 @@ FakeHttpConnection::FakeHttpConnection( http2_options.set_allow_connect(true); http2_options.set_allow_metadata(true); Http::Http2::CodecStats& stats = fake_upstream.http2CodecStats(); -#ifdef ENVOY_USE_NEW_CODECS_IN_INTEGRATION_TESTS codec_ = std::make_unique( shared_connection_.connection(), *this, stats, random_, http2_options, max_request_headers_kb, max_request_headers_count, headers_with_underscores_action); -#else - codec_ = std::make_unique( - shared_connection_.connection(), *this, stats, random_, http2_options, - max_request_headers_kb, max_request_headers_count, headers_with_underscores_action); -#endif ASSERT(type == Type::HTTP2); } shared_connection_.connection().addReadFilter( diff --git a/test/integration/http2_flood_integration_test.cc b/test/integration/http2_flood_integration_test.cc index d88ac81b2e3b..780b087e9c76 100644 --- a/test/integration/http2_flood_integration_test.cc +++ b/test/integration/http2_flood_integration_test.cc @@ -120,10 +120,6 @@ INSTANTIATE_TEST_SUITE_P(IpVersions, Http2FloodMitigationTest, TestUtility::ipTestParamsToString); bool Http2FloodMitigationTest::initializeUpstreamFloodTest() { - if (!Runtime::runtimeFeatureEnabled("envoy.reloadable_features.new_codec_behavior")) { - // Upstream flood checks are not implemented in the old codec based on exceptions - return false; - } config_helper_.addRuntimeOverride("envoy.reloadable_features.upstream_http2_flood_checks", "true"); setDownstreamProtocol(Http::CodecClient::Type::HTTP2);