diff --git a/bazel/BUILD b/bazel/BUILD index bb7072875c68..896cc8458602 100644 --- a/bazel/BUILD +++ b/bazel/BUILD @@ -471,6 +471,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 4a373b5f2687..f6492e18d2e1 100644 --- a/bazel/envoy_build_system.bzl +++ b/bazel/envoy_build_system.bzl @@ -42,6 +42,7 @@ load( _envoy_select_envoy_mobile_listener = "envoy_select_envoy_mobile_listener", _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_yaml = _envoy_select_enable_yaml envoy_select_disable_exceptions = _envoy_select_disable_exceptions envoy_select_enable_exceptions = _envoy_select_enable_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 93de9f910d35..45c6acb8ecbb 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 549794a392aa..873126819da0 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 b99513a0b26c..fe912379dda7 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_yaml=disabled build --define=envoy_full_protos=disabled build --define envoy_exceptions=disabled diff --git a/source/common/http/BUILD b/source/common/http/BUILD index b33131c4f333..44936d1ee3f3 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 @@ -545,7 +550,6 @@ envoy_cc_library( srcs = ["header_utility.cc"], hdrs = ["header_utility.h"], external_deps = [ - "nghttp2", "quiche_http2_adapter", ], deps = [ @@ -568,7 +572,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 ad6b5ba8658d..9091f8696835 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 @@ -203,12 +205,14 @@ bool HeaderUtility::headerValueIsValid(const absl::string_view header_value) { bool HeaderUtility::headerNameIsValid(absl::string_view header_key) { if (!header_key.empty() && header_key[0] == ':') { +#ifdef ENVOY_NGHTTP2 if (!Runtime::runtimeFeatureEnabled( "envoy.reloadable_features.sanitize_http2_headers_without_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; } +#endif header_key.remove_prefix(1); if (header_key.empty()) { return false; @@ -232,13 +236,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/http2/BUILD b/source/common/http/http2/BUILD index f1c9fefbc9c5..d4ba05d7591e 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")]), ) # Separate library for some nghttp2 setup stuff to avoid having tests take a @@ -117,16 +121,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", @@ -134,5 +135,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 06b1ae23428c..e856dcc7f00f 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 OGHTTP2_REFUSED_STREAM; case StreamResetReason::ConnectError: - return NGHTTP2_CONNECT_ERROR; + return OGHTTP2_CONNECT_ERROR; default: - return NGHTTP2_NO_ERROR; + return OGHTTP2_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 == OGHTTP2_PING_FRAME_TYPE && (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 != OGHTTP2_DATA_FRAME_TYPE) { 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 OGHTTP2_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, OGHTTP2_DATA_FRAME_TYPE, 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 == OGHTTP2_HEADERS_FRAME_TYPE || type == OGHTTP2_CONTINUATION_FRAME_TYPE) { stream->bytes_meter_->addHeaderBytesSent(length + H2_FRAME_HEADER_SIZE); } } switch (type) { - case NGHTTP2_GOAWAY: { + case OGHTTP2_GOAWAY_FRAME_TYPE: { ENVOY_CONN_LOG(debug, "sent goaway code={}", connection_, error_code); - if (error_code != NGHTTP2_NO_ERROR) { + if (error_code != OGHTTP2_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 OGHTTP2_RST_STREAM_FRAME_TYPE: { ENVOY_CONN_LOG(debug, "sent reset code={}", connection_, error_code); stats_.tx_reset_.inc(); break; } - case NGHTTP2_HEADERS: - case NGHTTP2_DATA: { + case OGHTTP2_HEADERS_FRAME_TYPE: + case OGHTTP2_DATA_FRAME_TYPE: { // 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,9 @@ 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 == OGHTTP2_PING_FRAME_TYPE || type == OGHTTP2_SETTINGS_FRAME_TYPE) && + flags & FLAG_ACK) || + type == OGHTTP2_RST_STREAM_FRAME_TYPE; return 0; } @@ -1351,8 +1397,8 @@ 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 == OGHTTP2_DATA_FRAME_TYPE || type == OGHTTP2_HEADERS_FRAME_TYPE) && + (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 +1470,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 == OGHTTP2_REFUSED_STREAM) { reason = StreamResetReason::RemoteRefusedStreamReset; stream->setDetails(Http2ResponseCodeDetails::get().remote_refused); } else { - if (error_code == NGHTTP2_CONNECT_ERROR) { + if (error_code == OGHTTP2_CONNECT_ERROR) { reason = StreamResetReason::ConnectError; } else { reason = StreamResetReason::RemoteReset; @@ -1485,7 +1531,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 +1548,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 +1580,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 +1593,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 +1691,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 +1707,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 +1802,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 == OGHTTP2_CONTINUATION_FRAME_TYPE) { if (current_frame_.stream_id != stream_id) { return false; } @@ -1789,7 +1835,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 +1855,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 +1864,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 +1872,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 +1881,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 +1891,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 +1900,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 == OGHTTP2_DATA_FRAME_TYPE) { // `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 +1989,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 +2031,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 +2046,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 +2059,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 +2191,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 +2263,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 +2279,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 +2346,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 a72ae97a1e32..e7603e24d690 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,24 @@ namespace Envoy { namespace Http { namespace Http2 { +// Types inherited from nghttp2 and preserved in oghttp2 +enum ErrorType { + OGHTTP2_NO_ERROR, + OGHTTP2_PROTOCOL_ERROR, + OGHTTP2_INTERNAL_ERROR, + OGHTTP2_FLOW_CONTROL_ERROR, + OGHTTP2_SETTINGS_TIMEOUT, + OGHTTP2_STREAM_CLOSED, + OGHTTP2_FRAME_SIZE_ERROR, + OGHTTP2_REFUSED_STREAM, + OGHTTP2_CANCEL, + OGHTTP2_COMPRESSION_ERROR, + OGHTTP2_CONNECT_ERROR, + OGHTTP2_ENHANCE_YOUR_CALM, + OGHTTP2_INADEQUATE_SECURITY, + OGHTTP2_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 +110,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 +127,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 +266,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 30348af10f89..367a7bb18ee5 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 OGHTTP2_HEADERS_FRAME_TYPE: + case OGHTTP2_CONTINUATION_FRAME_TYPE: + case OGHTTP2_DATA_FRAME_TYPE: // 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 OGHTTP2_PRIORITY_FRAME_TYPE: inbound_priority_frames_++; break; - case NGHTTP2_WINDOW_UPDATE: + case OGHTTP2_WINDOW_UPDATE_FRAME_TYPE: 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 28d9eb557bbc..86367d6d956c 100644 --- a/source/common/http/http2/protocol_constraints.h +++ b/source/common/http/http2/protocol_constraints.h @@ -9,12 +9,28 @@ #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 { +// Frame types as inherited from nghttp2 and preserved for oghttp2 +enum FrameType { + OGHTTP2_DATA_FRAME_TYPE, + OGHTTP2_HEADERS_FRAME_TYPE, + OGHTTP2_PRIORITY_FRAME_TYPE, + OGHTTP2_RST_STREAM_FRAME_TYPE, + OGHTTP2_SETTINGS_FRAME_TYPE, + OGHTTP2_PUSH_PROMISE_FRAME_TYPE, + OGHTTP2_PING_FRAME_TYPE, + OGHTTP2_GOAWAY_FRAME_TYPE, + OGHTTP2_WINDOW_UPDATE_FRAME_TYPE, + OGHTTP2_CONTINUATION_FRAME_TYPE, +}; + // 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: