From a1847adb3c1cb4bcab80e287f194fdd2efbc5f6c Mon Sep 17 00:00:00 2001 From: Alyssa Wilk Date: Wed, 5 Jun 2024 14:33:55 -0400 Subject: [PATCH] nghttp2: adding a compile-out option Signed-off-by: Alyssa Wilk --- bazel/BUILD | 5 + bazel/envoy_build_system.bzl | 2 + bazel/envoy_internal.bzl | 3 +- bazel/envoy_select.bzl | 7 + mobile/.bazelrc | 1 + source/common/http/BUILD | 8 +- source/common/http/header_utility.cc | 17 +- source/common/http/header_utility.h | 2 +- source/common/http/http2/BUILD | 15 +- source/common/http/http2/codec_impl.cc | 177 ++++++++++++------ source/common/http/http2/codec_impl.h | 28 +++ .../common/http/http2/protocol_constraints.cc | 10 +- .../common/http/http2/protocol_constraints.h | 15 ++ 13 files changed, 210 insertions(+), 80 deletions(-) diff --git a/bazel/BUILD b/bazel/BUILD index b6c11d2f029c2..e1c3805ccc1ce 100644 --- a/bazel/BUILD +++ b/bazel/BUILD @@ -476,6 +476,11 @@ selects.config_setting_group( ], ) +config_setting( + name = "disable_nghttp2", + values = {"define": "nghttp2=disabled"}, +) + config_setting( name = "disable_google_grpc", values = {"define": "google_grpc=disabled"}, diff --git a/bazel/envoy_build_system.bzl b/bazel/envoy_build_system.bzl index d2b515b28f4cd..ba0c0949d05bc 100644 --- a/bazel/envoy_build_system.bzl +++ b/bazel/envoy_build_system.bzl @@ -42,6 +42,7 @@ load( _envoy_select_envoy_mobile_xds = "envoy_select_envoy_mobile_xds", _envoy_select_google_grpc = "envoy_select_google_grpc", _envoy_select_hot_restart = "envoy_select_hot_restart", + _envoy_select_nghttp2 = "envoy_select_nghttp2", _envoy_select_signal_trace = "envoy_select_signal_trace", _envoy_select_static_extension_registration = "envoy_select_static_extension_registration", _envoy_select_wasm_cpp_tests = "envoy_select_wasm_cpp_tests", @@ -242,6 +243,7 @@ envoy_select_enable_http3 = _envoy_select_enable_http3 envoy_select_enable_yaml = _envoy_select_enable_yaml envoy_select_disable_exceptions = _envoy_select_disable_exceptions envoy_select_hot_restart = _envoy_select_hot_restart +envoy_select_nghttp2 = _envoy_select_nghttp2 envoy_select_enable_http_datagrams = _envoy_select_enable_http_datagrams envoy_select_signal_trace = _envoy_select_signal_trace envoy_select_wasm_cpp_tests = _envoy_select_wasm_cpp_tests diff --git a/bazel/envoy_internal.bzl b/bazel/envoy_internal.bzl index 93de9f910d355..45c6acb8ecbbe 100644 --- a/bazel/envoy_internal.bzl +++ b/bazel/envoy_internal.bzl @@ -1,6 +1,6 @@ # DO NOT LOAD THIS FILE. Targets from this file should be considered private # and not used outside of the @envoy//bazel package. -load(":envoy_select.bzl", "envoy_select_admin_html", "envoy_select_disable_exceptions", "envoy_select_disable_logging", "envoy_select_google_grpc", "envoy_select_hot_restart", "envoy_select_signal_trace", "envoy_select_static_extension_registration") +load(":envoy_select.bzl", "envoy_select_admin_html", "envoy_select_disable_exceptions", "envoy_select_disable_logging", "envoy_select_google_grpc", "envoy_select_hot_restart", "envoy_select_nghttp2", "envoy_select_signal_trace", "envoy_select_static_extension_registration") # Compute the final copts based on various options. def envoy_copts(repository, test = False): @@ -119,6 +119,7 @@ def envoy_copts(repository, test = False): repository + "//bazel:uhv_enabled": ["-DENVOY_ENABLE_UHV"], "//conditions:default": [], }) + envoy_select_hot_restart(["-DENVOY_HOT_RESTART"], repository) + \ + envoy_select_nghttp2(["-DENVOY_NGHTTP2"], repository) + \ envoy_select_disable_exceptions(["-fno-exceptions"], repository) + \ envoy_select_admin_html(["-DENVOY_ADMIN_HTML"], repository) + \ envoy_select_static_extension_registration(["-DENVOY_STATIC_EXTENSION_REGISTRATION"], repository) + \ diff --git a/bazel/envoy_select.bzl b/bazel/envoy_select.bzl index 8caee534fab4f..804acd13e035a 100644 --- a/bazel/envoy_select.bzl +++ b/bazel/envoy_select.bzl @@ -108,6 +108,13 @@ def envoy_select_hot_restart(xs, repository = ""): "//conditions:default": xs, }) +# Selects the given values if hot restart is enabled in the current build. +def envoy_select_nghttp2(xs, repository = ""): + return select({ + repository + "//bazel:disable_nghttp2": [], + "//conditions:default": xs, + }) + # Selects the given values if full protos are enabled in the current build. def envoy_select_enable_full_protos(xs, repository = ""): return select({ diff --git a/mobile/.bazelrc b/mobile/.bazelrc index e8efba686abb3..fc744b9b2a3b7 100644 --- a/mobile/.bazelrc +++ b/mobile/.bazelrc @@ -29,6 +29,7 @@ build --xcode_version=14.1 build --use_top_level_targets_for_symlinks build --experimental_repository_downloader_retries=2 build --define=google_grpc=disabled +build --define=nghttp2=disabled build --define=envoy_mobile_xds=disabled build --define=envoy_yaml=disabled diff --git a/source/common/http/BUILD b/source/common/http/BUILD index 0ea7a19241e72..af363d76ea485 100644 --- a/source/common/http/BUILD +++ b/source/common/http/BUILD @@ -4,6 +4,11 @@ load( "envoy_package", "envoy_select_enable_http3", "envoy_select_enable_http_datagrams", + "envoy_select_nghttp2", +) +load( + "//bazel:envoy_internal.bzl", + "envoy_external_dep_path", ) licenses(["notice"]) # Apache 2 @@ -538,7 +543,6 @@ envoy_cc_library( srcs = ["header_utility.cc"], hdrs = ["header_utility.h"], external_deps = [ - "nghttp2", "quiche_http2_adapter", ], deps = [ @@ -561,7 +565,7 @@ envoy_cc_library( "@envoy_api//envoy/type/v3:pkg_cc_proto", ] + envoy_select_enable_http_datagrams([ "@com_github_google_quiche//:quiche_common_structured_headers_lib", - ]), + ]) + envoy_select_nghttp2([envoy_external_dep_path("nghttp2")]), ) envoy_cc_library( diff --git a/source/common/http/header_utility.cc b/source/common/http/header_utility.cc index 93d682efaa0fd..18676bd706c3d 100644 --- a/source/common/http/header_utility.cc +++ b/source/common/http/header_utility.cc @@ -13,8 +13,10 @@ #include "source/common/runtime/runtime_features.h" #include "absl/strings/match.h" -#include "nghttp2/nghttp2.h" +#ifdef ENVOY_NGHTTP2 +#include "nghttp2/nghttp2.h" +#endif #ifdef ENVOY_ENABLE_HTTP_DATAGRAMS #include "quiche/common/structured_headers.h" #endif @@ -201,11 +203,15 @@ bool HeaderUtility::headerValueIsValid(const absl::string_view header_value) { http2::adapter::ObsTextOption::kAllow); } -bool HeaderUtility::headerNameIsValid(const absl::string_view header_key) { +bool HeaderUtility::headerNameIsValid(absl::string_view header_key) { if (!header_key.empty() && header_key[0] == ':') { +#ifdef ENVOY_NGHTTP2 // For HTTP/2 pseudo header, use the HTTP/2 semantics for checking validity return nghttp2_check_header_name(reinterpret_cast(header_key.data()), header_key.size()) != 0; +#else + header_key.remove_prefix(1); +#endif } // For all other header use HTTP/1 semantics. The only difference from HTTP/2 is that // uppercase characters are allowed. This allows HTTP filters to add header with mixed @@ -225,13 +231,14 @@ bool HeaderUtility::headerNameContainsUnderscore(const absl::string_view header_ } bool HeaderUtility::authorityIsValid(const absl::string_view header_value) { - if (Runtime::runtimeFeatureEnabled( +#ifdef ENVOY_NGHTTP2 + if (!Runtime::runtimeFeatureEnabled( "envoy.reloadable_features.http2_validate_authority_with_quiche")) { - return http2::adapter::HeaderValidator::IsValidAuthority(header_value); - } else { return nghttp2_check_authority(reinterpret_cast(header_value.data()), header_value.size()) != 0; } +#endif + return http2::adapter::HeaderValidator::IsValidAuthority(header_value); } bool HeaderUtility::isSpecial1xx(const ResponseHeaderMap& response_headers) { diff --git a/source/common/http/header_utility.h b/source/common/http/header_utility.h index 1a7accd9a8687..dcda03881709e 100644 --- a/source/common/http/header_utility.h +++ b/source/common/http/header_utility.h @@ -140,7 +140,7 @@ class HeaderUtility { * http://tools.ietf.org/html/rfc7230#section-3.2 * @return bool true if the header name is valid, according to the aforementioned RFC. */ - static bool headerNameIsValid(const absl::string_view header_key); + static bool headerNameIsValid(absl::string_view header_key); /** * Checks if header name contains underscore characters. diff --git a/source/common/http/http2/BUILD b/source/common/http/http2/BUILD index 653760f489b74..31db476732e32 100644 --- a/source/common/http/http2/BUILD +++ b/source/common/http/http2/BUILD @@ -2,6 +2,11 @@ load( "//bazel:envoy_build_system.bzl", "envoy_cc_library", "envoy_package", + "envoy_select_nghttp2", +) +load( + "//bazel:envoy_internal.bzl", + "envoy_external_dep_path", ) licenses(["notice"]) # Apache 2 @@ -24,7 +29,6 @@ envoy_cc_library( srcs = ["codec_impl.cc"], hdrs = ["codec_impl.h"], external_deps = [ - "nghttp2", "quiche_http2_adapter", "abseil_optional", "abseil_inlined_vector", @@ -62,7 +66,7 @@ envoy_cc_library( "//source/common/network:common_connection_filter_states_lib", "//source/common/runtime:runtime_features_lib", "@envoy_api//envoy/config/core/v3:pkg_cc_proto", - ], + ] + envoy_select_nghttp2([envoy_external_dep_path("nghttp2")]), ) envoy_cc_library( @@ -105,16 +109,13 @@ envoy_cc_library( "//source/common/common:assert_lib", "//source/common/common:minimal_logger_lib", "//source/common/runtime:runtime_features_lib", - ], + ] + envoy_select_nghttp2([envoy_external_dep_path("nghttp2")]), ) envoy_cc_library( name = "protocol_constraints_lib", srcs = ["protocol_constraints.cc"], hdrs = ["protocol_constraints.h"], - external_deps = [ - "nghttp2", - ], deps = [ ":codec_stats_lib", "//envoy/network:connection_interface", @@ -122,5 +123,5 @@ envoy_cc_library( "//source/common/common:dump_state_utils", "//source/common/http:status_lib", "@envoy_api//envoy/config/core/v3:pkg_cc_proto", - ], + ] + envoy_select_nghttp2([envoy_external_dep_path("nghttp2")]), ) diff --git a/source/common/http/http2/codec_impl.cc b/source/common/http/http2/codec_impl.cc index eaaa006bfad78..5e19a57f39c8f 100644 --- a/source/common/http/http2/codec_impl.cc +++ b/source/common/http/http2/codec_impl.cc @@ -38,6 +38,17 @@ namespace Envoy { namespace Http { namespace Http2 { +// for nghttp2 compatibility. +const int ERR_CALLBACK_FAILURE = -902; +const int INITIAL_CONNECTION_WINDOW_SIZE = ((1 << 16) - 1); +const int ERR_TEMPORAL_CALLBACK_FAILURE = -521; +const int ERR_REFUSED_STREAM = -533; +const int ERR_HTTP_HEADER = -531; +const int ERR_HTTP_MESSAGING = -532; +const int ERR_PROTO = -505; +const int ERR_STREAM_CLOSED = -510; +const int ERR_FLOW_CONTROL = -524; + // Changes or additions to details should be reflected in // docs/root/configuration/http/http_conn_man/response_code_details.rst class Http2ResponseCodeDetailValues { @@ -49,6 +60,8 @@ class Http2ResponseCodeDetailValues { 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"; + // oghttp2 does not provide details yet. + const absl::string_view oghttp2_err_unknown_ = "http2.unknown.oghttp2.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. @@ -62,6 +75,7 @@ class Http2ResponseCodeDetailValues { // The peer reset the stream. const absl::string_view remote_reset = "http2.remote_reset"; +#ifdef ENVOY_NGHTTP2 const absl::string_view errorDetails(int error_code) const { switch (error_code) { case NGHTTP2_ERR_HTTP_HEADER: @@ -73,24 +87,50 @@ class Http2ResponseCodeDetailValues { } } }; +const char* codec_strerror(int error_code) { return nghttp2_strerror(error_code); } +#else + const absl::string_view errorDetails(int) const { return oghttp2_err_unknown_; } +}; +const char* codec_strerror(int) { return "unknown_error"; } +#endif int reasonToReset(StreamResetReason reason) { switch (reason) { case StreamResetReason::LocalRefusedStreamReset: - return NGHTTP2_REFUSED_STREAM; + return REFUSED_STREAM; case StreamResetReason::ConnectError: - return NGHTTP2_CONNECT_ERROR; + return CONNECT_ERROR; default: - return NGHTTP2_NO_ERROR; + return NO_ERROR; } } using Http2ResponseCodeDetails = ConstSingleton; +enum Settings { + // SETTINGS_HEADER_TABLE_SIZE = 0x01, + // SETTINGS_ENABLE_PUSH = 0x02, + SETTINGS_MAX_CONCURRENT_STREAMS = 0x03, + // SETTINGS_INITIAL_WINDOW_SIZE = 0x04, + // SETTINGS_MAX_FRAME_SIZE = 0x05, + // SETTINGS_MAX_HEADER_LIST_SIZE = 0x06, + // SETTINGS_ENABLE_CONNECT_PROTOCOL = 0x08, + // SETTINGS_NO_RFC7540_PRIORITIES = 0x09 +}; + +enum Flags { + // FLAG_NONE = 0, + FLAG_END_STREAM = 0x01, + // FLAG_END_HEADERS = 0x04, + FLAG_ACK = 0x01, + // FLAG_PADDED = 0x08, + // FLAG_PRIORITY = 0x20 +}; + ReceivedSettingsImpl::ReceivedSettingsImpl( absl::Span settings) { for (const auto& [id, value] : settings) { - if (id == NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS) { + if (id == SETTINGS_MAX_CONCURRENT_STREAMS) { concurrent_stream_limit_ = value; break; } @@ -122,6 +162,7 @@ ProdNghttp2SessionFactory::create(ConnectionImpl* connection, return adapter; } +#ifdef ENVOY_NGHTTP2 std::unique_ptr ProdNghttp2SessionFactory::create(ConnectionImpl* connection, const nghttp2_option* options) { auto visitor = std::make_unique(connection); @@ -133,6 +174,7 @@ ProdNghttp2SessionFactory::create(ConnectionImpl* connection, const nghttp2_opti connection->setVisitor(std::move(visitor)); return adapter; } +#endif void ProdNghttp2SessionFactory::init(ConnectionImpl* connection, const envoy::config::core::v3::Http2ProtocolOptions& options) { @@ -781,7 +823,7 @@ void ConnectionImpl::StreamImpl::resetStream(StreamResetReason reason) { // reset as nghttp2 will have forgotten about the stream. if (stream_manager_.buffered_on_stream_close_) { ENVOY_CONN_LOG( - trace, "Stopped propagating reset to nghttp2 as we've buffered onStreamClose for stream {}", + trace, "Stopped propagating reset to codec as we've buffered onStreamClose for stream {}", parent_.connection_, stream_id_); // The stream didn't originally have an NGHTTP2 error, since we buffered // its stream close. @@ -792,7 +834,7 @@ void ConnectionImpl::StreamImpl::resetStream(StreamResetReason reason) { return; } - // If we submit a reset, nghttp2 will cancel outbound frames that have not yet been sent. + // If we submit a reset, the codec may 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. However, if we're resetting the stream due to // overload, we should reset the stream as soon as possible to free used @@ -979,16 +1021,19 @@ Http::Status ConnectionImpl::dispatch(Buffer::Instance& data) { if (!codec_callback_status_.ok()) { return codec_callback_status_; } +#ifdef ENVOY_NGHTTP2 // This error is returned when nghttp2 library detected a frame flood by one of its // internal mechanisms. Most flood protection is done by Envoy's codec and this error // should never be returned. However it is handled here in case nghttp2 has some flood // protections that Envoy's codec does not have. - if (rc == NGHTTP2_ERR_FLOODED) { + static const int ERR_FLOODED = -904; + if (rc == ERR_FLOODED) { return bufferFloodError( "Flooding was detected in this HTTP/2 session, and it must be closed"); // LCOV_EXCL_LINE } +#endif if (rc != static_cast(slice.len_)) { - return codecProtocolError(nghttp2_strerror(rc)); + return codecProtocolError(codec_strerror(rc)); } current_slice_ = nullptr; @@ -1072,7 +1117,7 @@ Status ConnectionImpl::onBeforeFrameReceived(int32_t stream_id, size_t length, u ASSERT(connection_.state() == Network::Connection::State::Open); current_stream_id_ = stream_id; - if (type == NGHTTP2_PING && (flags & NGHTTP2_FLAG_ACK)) { + if (type == PING && (flags & FLAG_ACK)) { return okStatus(); } @@ -1089,7 +1134,7 @@ Status ConnectionImpl::onBeforeFrameReceived(int32_t stream_id, size_t length, u // for some of them (e.g. CONTINUATION frame, frames sent on closed streams, etc.). // DATA frame is tracked in onFrameReceived(). auto status = okStatus(); - if (type != NGHTTP2_DATA) { + if (type != DATA) { status = trackInboundFrames(stream_id, length, type, flags, 0); } @@ -1099,7 +1144,7 @@ Status ConnectionImpl::onBeforeFrameReceived(int32_t stream_id, size_t length, u ABSL_MUST_USE_RESULT enum GoAwayErrorCode ngHttp2ErrorCodeToErrorCode(uint32_t code) noexcept { switch (code) { - case NGHTTP2_NO_ERROR: + case NO_ERROR: return GoAwayErrorCode::NoError; default: return GoAwayErrorCode::Other; @@ -1121,7 +1166,7 @@ Status ConnectionImpl::onPing(uint64_t opaque_data, bool is_ack) { Status ConnectionImpl::onBeginData(int32_t stream_id, size_t length, uint8_t flags, size_t padding) { ENVOY_CONN_LOG(trace, "recv frame type=DATA stream_id={}", connection_, stream_id); - RETURN_IF_ERROR(trackInboundFrames(stream_id, length, NGHTTP2_DATA, flags, padding)); + RETURN_IF_ERROR(trackInboundFrames(stream_id, length, DATA, flags, padding)); StreamImpl* stream = getStreamUnchecked(stream_id); if (!stream) { @@ -1131,7 +1176,7 @@ Status ConnectionImpl::onBeginData(int32_t stream_id, size_t length, uint8_t fla // Track bytes received. stream->bytes_meter_->addWireBytesReceived(length + H2_FRAME_HEADER_SIZE); - stream->remote_end_stream_ = flags & NGHTTP2_FLAG_END_STREAM; + stream->remote_end_stream_ = flags & FLAG_END_STREAM; stream->decodeData(); return okStatus(); } @@ -1157,7 +1202,7 @@ Status ConnectionImpl::onHeaders(int32_t stream_id, size_t length, uint8_t flags stream->bytes_meter_->addWireBytesReceived(length + H2_FRAME_HEADER_SIZE); stream->bytes_meter_->addHeaderBytesReceived(length + H2_FRAME_HEADER_SIZE); - stream->remote_end_stream_ = flags & NGHTTP2_FLAG_END_STREAM; + stream->remote_end_stream_ = flags & FLAG_END_STREAM; if (!stream->cookies_.empty()) { HeaderString key(Headers::get().Cookie); stream->headers().addViaMove(std::move(key), std::move(stream->cookies_)); @@ -1222,14 +1267,14 @@ int ConnectionImpl::onFrameSend(int32_t stream_id, size_t length, uint8_t type, if (type != METADATA_FRAME_TYPE) { stream->bytes_meter_->addWireBytesSent(length + H2_FRAME_HEADER_SIZE); } - if (type == NGHTTP2_HEADERS || type == NGHTTP2_CONTINUATION) { + if (type == HEADERS || type == CONTINUATION) { stream->bytes_meter_->addHeaderBytesSent(length + H2_FRAME_HEADER_SIZE); } } switch (type) { - case NGHTTP2_GOAWAY: { + case GOAWAY: { ENVOY_CONN_LOG(debug, "sent goaway code={}", connection_, error_code); - if (error_code != NGHTTP2_NO_ERROR) { + if (error_code != 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 @@ -1239,24 +1284,24 @@ int ConnectionImpl::onFrameSend(int32_t stream_id, size_t length, uint8_t type, for (auto& stream : active_streams_) { stream->disarmStreamIdleTimer(); } - return NGHTTP2_ERR_CALLBACK_FAILURE; + return ERR_CALLBACK_FAILURE; } break; } - case NGHTTP2_RST_STREAM: { + case RST_STREAM: { ENVOY_CONN_LOG(debug, "sent reset code={}", connection_, error_code); stats_.tx_reset_.inc(); break; } - case NGHTTP2_HEADERS: - case NGHTTP2_DATA: { + case HEADERS: + case DATA: { // This should be the case since we're sending these frames. It's possible // that codec fuzzers would incorrectly send frames for non-existent streams // which is why this is not an assert. if (stream != nullptr) { - const bool end_stream_sent = flags & NGHTTP2_FLAG_END_STREAM; + const bool end_stream_sent = flags & FLAG_END_STREAM; stream->local_end_stream_sent_ = end_stream_sent; if (end_stream_sent) { stream->onEndStreamEncoded(); @@ -1275,7 +1320,7 @@ int ConnectionImpl::onError(absl::string_view error) { } int ConnectionImpl::onInvalidFrame(int32_t stream_id, int error_code) { - ENVOY_CONN_LOG(debug, "invalid frame: {} on stream {}", connection_, nghttp2_strerror(error_code), + ENVOY_CONN_LOG(debug, "invalid frame: {} on stream {}", connection_, codec_strerror(error_code), stream_id); // Set details of error_code in the stream whenever we have one. @@ -1285,13 +1330,13 @@ int ConnectionImpl::onInvalidFrame(int32_t stream_id, int error_code) { } switch (error_code) { - case NGHTTP2_ERR_REFUSED_STREAM: + case ERR_REFUSED_STREAM: stats_.stream_refused_errors_.inc(); return 0; - case NGHTTP2_ERR_HTTP_HEADER: - case NGHTTP2_ERR_HTTP_MESSAGING: + case ERR_HTTP_HEADER: + case 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 @@ -1304,9 +1349,9 @@ int ConnectionImpl::onInvalidFrame(int32_t stream_id, int error_code) { } break; - case NGHTTP2_ERR_FLOW_CONTROL: - case NGHTTP2_ERR_PROTO: - case NGHTTP2_ERR_STREAM_CLOSED: + case ERR_FLOW_CONTROL: + case ERR_PROTO: + case ERR_STREAM_CLOSED: // Known error conditions that should trigger connection close. break; @@ -1317,7 +1362,7 @@ int ConnectionImpl::onInvalidFrame(int32_t stream_id, int error_code) { } // Cause dispatch to return with an error code. - return NGHTTP2_ERR_CALLBACK_FAILURE; + return ERR_CALLBACK_FAILURE; } int ConnectionImpl::onBeforeFrameSend(int32_t /*stream_id*/, size_t /*length*/, uint8_t type, @@ -1327,8 +1372,7 @@ int ConnectionImpl::onBeforeFrameSend(int32_t /*stream_id*/, size_t /*length*/, ASSERT(!is_outbound_flood_monitored_control_frame_); // Flag flood monitored outbound control frames. is_outbound_flood_monitored_control_frame_ = - ((type == NGHTTP2_PING || type == NGHTTP2_SETTINGS) && flags & NGHTTP2_FLAG_ACK) || - type == NGHTTP2_RST_STREAM; + ((type == PING || type == SETTINGS) && flags & FLAG_ACK) || type == RST_STREAM; return 0; } @@ -1351,8 +1395,7 @@ Status ConnectionImpl::trackInboundFrames(int32_t stream_id, size_t length, uint connection_, static_cast(type), static_cast(flags), static_cast(length), padding_length); - const bool end_stream = - (type == NGHTTP2_DATA || type == NGHTTP2_HEADERS) && (flags & NGHTTP2_FLAG_END_STREAM); + const bool end_stream = (type == DATA || type == HEADERS) && (flags & FLAG_END_STREAM); const bool is_empty = (length - padding_length) == 0; result = protocol_constraints_.trackInboundFrame(type, end_stream, is_empty); if (!result.ok()) { @@ -1424,11 +1467,11 @@ Status ConnectionImpl::onStreamClose(StreamImpl* stream, uint32_t error_code) { // depending whether the connection is upstream or downstream. reason = getMessagingErrorResetReason(); } else { - if (error_code == NGHTTP2_REFUSED_STREAM) { + if (error_code == REFUSED_STREAM) { reason = StreamResetReason::RemoteRefusedStreamReset; stream->setDetails(Http2ResponseCodeDetails::get().remote_refused); } else { - if (error_code == NGHTTP2_CONNECT_ERROR) { + if (error_code == CONNECT_ERROR) { reason = StreamResetReason::ConnectError; } else { reason = StreamResetReason::RemoteReset; @@ -1485,7 +1528,7 @@ int ConnectionImpl::onMetadataReceived(int32_t stream_id, const uint8_t* data, s } bool success = stream->getMetadataDecoder().receiveMetadata(data, len); - return success ? 0 : NGHTTP2_ERR_CALLBACK_FAILURE; + return success ? 0 : ERR_CALLBACK_FAILURE; } int ConnectionImpl::onMetadataFrameComplete(int32_t stream_id, bool end_metadata) { @@ -1502,7 +1545,7 @@ int ConnectionImpl::onMetadataFrameComplete(int32_t stream_id, bool end_metadata } bool result = stream->getMetadataDecoder().onMetadataFrameComplete(end_metadata); - return result ? 0 : NGHTTP2_ERR_CALLBACK_FAILURE; + return result ? 0 : ERR_CALLBACK_FAILURE; } int ConnectionImpl::saveHeader(int32_t stream_id, HeaderString&& name, HeaderString&& value) { @@ -1534,7 +1577,7 @@ int ConnectionImpl::saveHeader(int32_t stream_id, HeaderString&& name, HeaderStr 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; + return ERR_TEMPORAL_CALLBACK_FAILURE; } else { return 0; } @@ -1547,8 +1590,8 @@ Status ConnectionImpl::sendPendingFrames() { const int rc = adapter_->Send(); if (rc != 0) { - ASSERT(rc == NGHTTP2_ERR_CALLBACK_FAILURE); - return codecProtocolError(nghttp2_strerror(rc)); + ASSERT(rc == ERR_CALLBACK_FAILURE); + return codecProtocolError(codec_strerror(rc)); } // See ConnectionImpl::StreamImpl::resetStream() for why we do this. This is an uncommon event, @@ -1645,11 +1688,11 @@ void ConnectionImpl::sendSettings( 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) { + if (initial_connection_window_size != INITIAL_CONNECTION_WINDOW_SIZE) { ENVOY_CONN_LOG(debug, "updating connection-level initial window size to {}", connection_, initial_connection_window_size); - adapter_->SubmitWindowUpdate(0, initial_connection_window_size - - NGHTTP2_INITIAL_CONNECTION_WINDOW_SIZE); + adapter_->SubmitWindowUpdate(0, + initial_connection_window_size - INITIAL_CONNECTION_WINDOW_SIZE); } } @@ -1661,7 +1704,7 @@ int ConnectionImpl::setAndCheckCodecCallbackStatus(Status&& status) { codec_callback_status_ = codecProtocolError("Connection was closed while dispatching frames"); } - return codec_callback_status_.ok() ? 0 : NGHTTP2_ERR_CALLBACK_FAILURE; + return codec_callback_status_.ok() ? 0 : ERR_CALLBACK_FAILURE; } void ConnectionImpl::scheduleProtocolConstraintViolationCallback() { @@ -1756,7 +1799,7 @@ bool ConnectionImpl::Http2Visitor::OnFrameHeader(Http2StreamId stream_id, size_t ENVOY_CONN_LOG(debug, "Http2Visitor::OnFrameHeader({}, {}, {}, {})", connection_->connection_, stream_id, length, int(type), int(flags)); - if (type == NGHTTP2_CONTINUATION) { + if (type == CONTINUATION) { if (current_frame_.stream_id != stream_id) { return false; } @@ -1789,7 +1832,7 @@ ConnectionImpl::Http2Visitor::OnHeaderForStream(Http2StreamId stream_id, switch (result) { case 0: return HEADER_OK; - case NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE: + case ERR_TEMPORAL_CALLBACK_FAILURE: return HEADER_RST_STREAM; default: return HEADER_CONNECTION_ERROR; @@ -1809,7 +1852,7 @@ bool ConnectionImpl::Http2Visitor::OnBeginDataForStream(Http2StreamId stream_id, stream_id, payload_length); remaining_data_payload_ = payload_length; padding_length_ = 0; - if (remaining_data_payload_ == 0 && (current_frame_.flags & NGHTTP2_FLAG_END_STREAM) == 0) { + if (remaining_data_payload_ == 0 && (current_frame_.flags & FLAG_END_STREAM) == 0) { ENVOY_CONN_LOG(debug, "Http2Visitor dispatching DATA for stream {}", connection_->connection_, stream_id); Status status = connection_->onBeginData(stream_id, current_frame_.length, current_frame_.flags, @@ -1818,7 +1861,7 @@ bool ConnectionImpl::Http2Visitor::OnBeginDataForStream(Http2StreamId stream_id, } ENVOY_CONN_LOG(debug, "Http2Visitor: remaining data payload: {}, end_stream: {}", connection_->connection_, remaining_data_payload_, - bool(current_frame_.flags & NGHTTP2_FLAG_END_STREAM)); + bool(current_frame_.flags & FLAG_END_STREAM)); return true; } @@ -1826,7 +1869,7 @@ bool ConnectionImpl::Http2Visitor::OnDataPaddingLength(Http2StreamId stream_id, size_t padding_length) { padding_length_ = padding_length; remaining_data_payload_ -= padding_length; - if (remaining_data_payload_ == 0 && (current_frame_.flags & NGHTTP2_FLAG_END_STREAM) == 0) { + if (remaining_data_payload_ == 0 && (current_frame_.flags & FLAG_END_STREAM) == 0) { ENVOY_CONN_LOG(debug, "Http2Visitor dispatching DATA for stream {}", connection_->connection_, stream_id); Status status = connection_->onBeginData(stream_id, current_frame_.length, current_frame_.flags, @@ -1835,7 +1878,7 @@ bool ConnectionImpl::Http2Visitor::OnDataPaddingLength(Http2StreamId stream_id, } ENVOY_CONN_LOG(debug, "Http2Visitor: remaining data payload: {}, end_stream: {}", connection_->connection_, remaining_data_payload_, - bool(current_frame_.flags & NGHTTP2_FLAG_END_STREAM)); + bool(current_frame_.flags & FLAG_END_STREAM)); return true; } @@ -1845,7 +1888,7 @@ bool ConnectionImpl::Http2Visitor::OnDataForStream(Http2StreamId stream_id, connection_->onData(stream_id, reinterpret_cast(data.data()), data.size()); remaining_data_payload_ -= data.size(); if (result == 0 && remaining_data_payload_ == 0 && - (current_frame_.flags & NGHTTP2_FLAG_END_STREAM) == 0) { + (current_frame_.flags & FLAG_END_STREAM) == 0) { ENVOY_CONN_LOG(debug, "Http2Visitor dispatching DATA for stream {}", connection_->connection_, stream_id); Status status = connection_->onBeginData(stream_id, current_frame_.length, current_frame_.flags, @@ -1854,13 +1897,13 @@ bool ConnectionImpl::Http2Visitor::OnDataForStream(Http2StreamId stream_id, } ENVOY_CONN_LOG(debug, "Http2Visitor: remaining data payload: {}, end_stream: {}", connection_->connection_, remaining_data_payload_, - bool(current_frame_.flags & NGHTTP2_FLAG_END_STREAM)); + bool(current_frame_.flags & FLAG_END_STREAM)); return result == 0; } bool ConnectionImpl::Http2Visitor::OnEndStream(Http2StreamId stream_id) { ENVOY_CONN_LOG(debug, "Http2Visitor::OnEndStream({})", connection_->connection_, stream_id); - if (current_frame_.type == NGHTTP2_DATA) { + if (current_frame_.type == DATA) { // `onBeginData` is invoked here to ensure that the connection has successfully validated and // processed the entire DATA frame. ENVOY_CONN_LOG(debug, "Http2Visitor dispatching DATA for stream {}", connection_->connection_, @@ -1943,6 +1986,7 @@ ConnectionImpl::Http2Options::Http2Options( og_options_.validate_http_headers = false; #endif +#ifdef ENVOY_NGHTTP2 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 @@ -1984,9 +2028,14 @@ ConnectionImpl::Http2Options::Http2Options( // 512 is chosen to accommodate Envoy's 8Mb max limit of max_request_headers_kb // in both headers and trailers nghttp2_option_set_max_continuations(options_, 512); +#endif } -ConnectionImpl::Http2Options::~Http2Options() { nghttp2_option_del(options_); } +ConnectionImpl::Http2Options::~Http2Options() { +#ifdef ENVOY_NGHTTP2 + nghttp2_option_del(options_); +#endif +} ConnectionImpl::ClientHttp2Options::ClientHttp2Options( const envoy::config::core::v3::Http2ProtocolOptions& http2_options, uint32_t max_headers_kb) @@ -1994,6 +2043,8 @@ ConnectionImpl::ClientHttp2Options::ClientHttp2Options( og_options_.perspective = http2::adapter::Perspective::kClient; og_options_.remote_max_concurrent_streams = ::Envoy::Http2::Utility::OptionsLimits::DEFAULT_MAX_CONCURRENT_STREAMS; + +#ifdef ENVOY_NGHTTP2 // 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. // @@ -2005,6 +2056,7 @@ ConnectionImpl::ClientHttp2Options::ClientHttp2Options( // 1024 is chosen to accommodate Envoy's 8Mb max limit of max_request_headers_kb // in both headers and trailers nghttp2_option_set_max_continuations(options_, 1024); +#endif } ExecutionContext* ConnectionImpl::executionContext() const { @@ -2136,10 +2188,13 @@ ClientConnectionImpl::ClientConnectionImpl( max_response_headers_count), callbacks_(callbacks) { ClientHttp2Options client_http2_options(http2_options, max_response_headers_kb); - if (use_oghttp2_library_) { - adapter_ = http2_session_factory.create(base(), client_http2_options.ogOptions()); - } else { + if (!use_oghttp2_library_) { +#ifdef ENVOY_NGHTTP2 adapter_ = http2_session_factory.create(base(), client_http2_options.options()); +#endif + } + if (!adapter_) { + adapter_ = http2_session_factory.create(base(), client_http2_options.ogOptions()); } http2_session_factory.init(base(), http2_options); allow_metadata_ = http2_options.allow_metadata(); @@ -2205,9 +2260,12 @@ ServerConnectionImpl::ServerConnectionImpl( auto direct_visitor = std::make_unique(this); +#ifdef ENVOY_NGHTTP2 if (use_oghttp2_library_) { +#endif visitor_ = std::move(direct_visitor); adapter_ = http2::adapter::OgHttp2Adapter::Create(*visitor_, h2_options.ogOptions()); +#ifdef ENVOY_NGHTTP2 } else { auto adapter = http2::adapter::NgHttp2Adapter::CreateServerAdapter(*direct_visitor, h2_options.options()); @@ -2218,6 +2276,7 @@ ServerConnectionImpl::ServerConnectionImpl( visitor_ = std::move(direct_visitor); adapter_ = std::move(adapter); } +#endif sendSettings(http2_options, false); allow_metadata_ = http2_options.allow_metadata(); } @@ -2284,7 +2343,7 @@ absl::optional ServerConnectionImpl::checkHeaderNameForUnderscores( ENVOY_CONN_LOG(debug, "Rejecting request due to header name with underscores: {}", connection_, header_name); stats_.incRequestsRejectedWithUnderscoresInHeaders(); - return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE; + return ERR_TEMPORAL_CALLBACK_FAILURE; } #else // Workaround for gcc not understanding [[maybe_unused]] for class members. diff --git a/source/common/http/http2/codec_impl.h b/source/common/http/http2/codec_impl.h index a72ae97a1e326..1430d1ec7c105 100644 --- a/source/common/http/http2/codec_impl.h +++ b/source/common/http/http2/codec_impl.h @@ -34,7 +34,10 @@ #include "absl/types/optional.h" #include "absl/types/span.h" + +#ifdef ENVOY_NGHTTP2 #include "nghttp2/nghttp2.h" +#endif #include "quiche/http2/adapter/http2_adapter.h" #include "quiche/http2/adapter/http2_protocol.h" #include "quiche/http2/adapter/oghttp2_adapter.h" @@ -43,6 +46,23 @@ namespace Envoy { namespace Http { namespace Http2 { +enum ErrorType { + NO_ERROR, + PROTOCOL_ERROR, + INTERNAL_ERROR, + FLOW_CONTROL_ERROR, + SETTINGS_TIMEOUT, + STREAM_CLOSED, + FRAME_SIZE_ERROR, + REFUSED_STREAM, + CANCEL, + COMPRESSION_ERROR, + CONNECT_ERROR, + ENHANCE_YOUR_CALM, + INADEQUATE_SECURITY, + HTTP_1_1_REQUIRED, +}; + class Http2CodecImplTestFixture; // This is not the full client magic, but it's the smallest size that should be able to @@ -89,9 +109,11 @@ class Http2SessionFactory { create(ConnectionImplType* connection, const http2::adapter::OgHttp2Adapter::Options& options) PURE; +#ifdef ENVOY_NGHTTP2 // Returns a new HTTP/2 session to be used with |connection|. virtual std::unique_ptr create(ConnectionImplType* connection, const nghttp2_option* options) PURE; +#endif // Initializes the |session|. virtual void init(ConnectionImplType* connection, @@ -104,8 +126,10 @@ class ProdNghttp2SessionFactory : public Http2SessionFactory { create(ConnectionImpl* connection, const http2::adapter::OgHttp2Adapter::Options& options) override; +#ifdef ENVOY_NGHTTP2 std::unique_ptr create(ConnectionImpl* connection, const nghttp2_option* options) override; +#endif void init(ConnectionImpl* connection, const envoy::config::core::v3::Http2ProtocolOptions& options) override; @@ -241,11 +265,15 @@ class ConnectionImpl : public virtual Connection, uint32_t max_headers_kb); ~Http2Options(); +#ifdef ENVOY_NGHTTP2 const nghttp2_option* options() { return options_; } +#endif const http2::adapter::OgHttp2Adapter::Options& ogOptions() { return og_options_; } protected: +#ifdef ENVOY_NGHTTP2 nghttp2_option* options_; +#endif http2::adapter::OgHttp2Adapter::Options og_options_; }; diff --git a/source/common/http/http2/protocol_constraints.cc b/source/common/http/http2/protocol_constraints.cc index 30348af10f890..1ed2e7b824bb6 100644 --- a/source/common/http/http2/protocol_constraints.cc +++ b/source/common/http/http2/protocol_constraints.cc @@ -64,9 +64,9 @@ Status ProtocolConstraints::checkOutboundFrameLimits() { Status ProtocolConstraints::trackInboundFrame(uint8_t type, bool end_stream, bool is_empty) { switch (type) { - case NGHTTP2_HEADERS: - case NGHTTP2_CONTINUATION: - case NGHTTP2_DATA: + case FrameType::HEADERS: + case CONTINUATION: + case DATA: // Track frames with an empty payload and no end stream flag. if (is_empty && !end_stream) { consecutive_inbound_frames_with_empty_payload_++; @@ -74,10 +74,10 @@ Status ProtocolConstraints::trackInboundFrame(uint8_t type, bool end_stream, boo consecutive_inbound_frames_with_empty_payload_ = 0; } break; - case NGHTTP2_PRIORITY: + case PRIORITY: inbound_priority_frames_++; break; - case NGHTTP2_WINDOW_UPDATE: + case WINDOW_UPDATE: inbound_window_update_frames_++; break; default: diff --git a/source/common/http/http2/protocol_constraints.h b/source/common/http/http2/protocol_constraints.h index 28d9eb557bbc6..712efdb649666 100644 --- a/source/common/http/http2/protocol_constraints.h +++ b/source/common/http/http2/protocol_constraints.h @@ -9,12 +9,27 @@ #include "source/common/http/http2/codec_stats.h" #include "source/common/http/status.h" +#ifdef ENVOY_NGHTTP2 #include "nghttp2/nghttp2.h" +#endif namespace Envoy { namespace Http { namespace Http2 { +enum FrameType { + DATA, + HEADERS, + PRIORITY, + RST_STREAM, + SETTINGS, + PUSH_PROMISE, + PING, + GOAWAY, + WINDOW_UPDATE, + CONTINUATION, +}; + // Class for detecting abusive peers and validating additional constraints imposed by Envoy. // This class does not check protocol compliance with the H/2 standard, as this is checked by // protocol framer/codec. Currently implemented constraints: