From edfbf4bbf621dea1149259b2a147610266ac5a3f Mon Sep 17 00:00:00 2001 From: Dan Zhang Date: Fri, 4 Oct 2019 14:49:37 -0400 Subject: [PATCH 01/76] merged all commits Signed-off-by: Dan Zhang --- .../v2/http_connection_manager.proto | 1 + api/envoy/data/accesslog/v2/accesslog.proto | 1 + bazel/external/quiche.BUILD | 1 + include/envoy/http/protocol.h | 4 +- source/common/common/logger.h | 1 + source/common/http/BUILD | 2 + source/common/http/codec_client.cc | 24 +- source/common/http/codec_client.h | 2 +- source/common/http/conn_manager_config.h | 3 + source/common/http/conn_manager_impl.cc | 30 ++- source/common/http/conn_manager_impl.h | 2 +- source/common/http/headers.h | 1 + source/common/http/utility.cc | 2 + .../grpc/http_grpc_access_log_impl.cc | 3 + .../network/http_connection_manager/config.cc | 10 + .../network/http_connection_manager/config.h | 2 +- source/extensions/quic_listeners/quiche/BUILD | 70 +++++- .../quiche/active_quic_listener.cc | 6 +- .../quiche/active_quic_listener.h | 2 + .../quic_listeners/quiche/codec_impl.cc | 59 ++++- .../quic_listeners/quiche/codec_impl.h | 37 ++- .../quic_listeners/quiche/envoy_quic_alarm.h | 6 +- .../quiche/envoy_quic_client_connection.cc | 176 ++++++++++++++ .../quiche/envoy_quic_client_connection.h | 66 ++++++ .../quiche/envoy_quic_client_session.cc | 85 +++++++ .../quiche/envoy_quic_client_session.h | 80 +++++++ .../quiche/envoy_quic_client_stream.cc | 216 ++++++++++++++++++ .../quiche/envoy_quic_client_stream.h | 56 +++++ .../quiche/envoy_quic_connection.cc | 5 +- .../quiche/envoy_quic_dispatcher.cc | 3 +- .../quiche/envoy_quic_fake_proof_verifier.h | 3 +- .../quiche/envoy_quic_server_session.cc | 12 +- .../quiche/envoy_quic_server_session.h | 7 +- .../quiche/envoy_quic_server_stream.cc | 151 +++++++++--- .../quiche/envoy_quic_server_stream.h | 14 +- .../envoy_quic_simulated_watermark_buffer.h | 62 +++++ .../quic_listeners/quiche/envoy_quic_stream.h | 48 +++- .../quic_listeners/quiche/envoy_quic_utils.cc | 45 ++++ .../quic_listeners/quiche/envoy_quic_utils.h | 11 + .../quic_filter_manager_connection_impl.cc | 57 ++++- .../quic_filter_manager_connection_impl.h | 32 ++- test/config/utility.cc | 31 +++ test/config/utility.h | 3 +- test/extensions/quic_listeners/quiche/BUILD | 3 +- .../quiche/envoy_quic_dispatcher_test.cc | 10 +- .../quiche/envoy_quic_proof_source_test.cc | 4 +- .../quiche/envoy_quic_server_session_test.cc | 10 +- .../quiche/envoy_quic_server_stream_test.cc | 213 ++++++++++++++--- .../quic_listeners/quiche/integration/BUILD | 29 +++ .../integration/quic_http_integration_test.cc | 183 +++++++++++++++ test/integration/http_integration.cc | 13 +- test/integration/http_integration.h | 2 +- test/integration/integration.h | 4 +- 53 files changed, 1751 insertions(+), 152 deletions(-) create mode 100644 source/extensions/quic_listeners/quiche/envoy_quic_client_connection.cc create mode 100644 source/extensions/quic_listeners/quiche/envoy_quic_client_connection.h create mode 100644 source/extensions/quic_listeners/quiche/envoy_quic_client_session.cc create mode 100644 source/extensions/quic_listeners/quiche/envoy_quic_client_session.h create mode 100644 source/extensions/quic_listeners/quiche/envoy_quic_client_stream.cc create mode 100644 source/extensions/quic_listeners/quiche/envoy_quic_client_stream.h create mode 100644 source/extensions/quic_listeners/quiche/envoy_quic_simulated_watermark_buffer.h create mode 100644 test/extensions/quic_listeners/quiche/integration/BUILD create mode 100644 test/extensions/quic_listeners/quiche/integration/quic_http_integration_test.cc diff --git a/api/envoy/config/filter/network/http_connection_manager/v2/http_connection_manager.proto b/api/envoy/config/filter/network/http_connection_manager/v2/http_connection_manager.proto index efdfb4be9392b..2b255c8de5024 100644 --- a/api/envoy/config/filter/network/http_connection_manager/v2/http_connection_manager.proto +++ b/api/envoy/config/filter/network/http_connection_manager/v2/http_connection_manager.proto @@ -40,6 +40,7 @@ message HttpConnectionManager { // (Envoy does not require HTTP/2 to take place over TLS or to use ALPN. // Prior knowledge is allowed). HTTP2 = 2; + HTTP3 = 3; } enum ServerHeaderTransformation { diff --git a/api/envoy/data/accesslog/v2/accesslog.proto b/api/envoy/data/accesslog/v2/accesslog.proto index 1cb7d13112e57..290f69a5cd6c3 100644 --- a/api/envoy/data/accesslog/v2/accesslog.proto +++ b/api/envoy/data/accesslog/v2/accesslog.proto @@ -41,6 +41,7 @@ message HTTPAccessLogEntry { HTTP10 = 1; HTTP11 = 2; HTTP2 = 3; + HTTP3 = 4; } // Common properties shared by all Envoy access logs. diff --git a/bazel/external/quiche.BUILD b/bazel/external/quiche.BUILD index 67d7d4207ce90..47505a2d131fb 100644 --- a/bazel/external/quiche.BUILD +++ b/bazel/external/quiche.BUILD @@ -1993,6 +1993,7 @@ envoy_cc_library( copts = quiche_copt, repository = "@envoy", tags = ["nofips"], + visibility = ["//visibility:public"], deps = [ ":quic_core_alarm_interface_lib", ":quic_core_crypto_encryption_lib", diff --git a/include/envoy/http/protocol.h b/include/envoy/http/protocol.h index 2f4dcce601259..42c159fe90ece 100644 --- a/include/envoy/http/protocol.h +++ b/include/envoy/http/protocol.h @@ -9,8 +9,8 @@ namespace Http { * Possible HTTP connection/request protocols. The parallel NumProtocols constant allows defining * fixed arrays for each protocol, but does not pollute the enum. */ -enum class Protocol { Http10, Http11, Http2 }; -const size_t NumProtocols = 3; +enum class Protocol { Http10, Http11, Http2, Http3 }; +const size_t NumProtocols = 4; } // namespace Http } // namespace Envoy diff --git a/source/common/common/logger.h b/source/common/common/logger.h index 5efa9accd3404..80c9f57e574e3 100644 --- a/source/common/common/logger.h +++ b/source/common/common/logger.h @@ -49,6 +49,7 @@ namespace Logger { FUNCTION(misc) \ FUNCTION(mongo) \ FUNCTION(quic) \ + FUNCTION(quic_stream) \ FUNCTION(pool) \ FUNCTION(rbac) \ FUNCTION(redis) \ diff --git a/source/common/http/BUILD b/source/common/http/BUILD index 05553bd529169..b7b152311c4a1 100644 --- a/source/common/http/BUILD +++ b/source/common/http/BUILD @@ -51,6 +51,7 @@ envoy_cc_library( "//source/common/http/http1:codec_lib", "//source/common/http/http2:codec_lib", "//source/common/network:filter_lib", + "//source/extensions/quic_listeners/quiche:codec_lib", ], ) @@ -188,6 +189,7 @@ envoy_cc_library( "//source/common/runtime:uuid_util_lib", "//source/common/stream_info:stream_info_lib", "//source/common/tracing:http_tracer_lib", + "//source/extensions/quic_listeners/quiche:codec_lib", ], ) diff --git a/source/common/http/codec_client.cc b/source/common/http/codec_client.cc index 2523d5970b01a..da5d19eb7a16e 100644 --- a/source/common/http/codec_client.cc +++ b/source/common/http/codec_client.cc @@ -9,6 +9,8 @@ #include "common/http/http2/codec_impl.h" #include "common/http/utility.h" +#include "extensions/quic_listeners/quiche/codec_impl.h" + namespace Envoy { namespace Http { @@ -17,9 +19,12 @@ CodecClient::CodecClient(Type type, Network::ClientConnectionPtr&& connection, Event::Dispatcher& dispatcher) : type_(type), connection_(std::move(connection)), host_(host), idle_timeout_(host_->cluster().idleTimeout()) { - // Make sure upstream connections process data and then the FIN, rather than processing - // TCP disconnects immediately. (see https://github.com/envoyproxy/envoy/issues/1679 for details) - connection_->detectEarlyCloseWhenReadDisabled(false); + if (type_ != Type::HTTP3) { + // Make sure upstream connections process data and then the FIN, rather than processing + // TCP disconnects immediately. (see https://github.com/envoyproxy/envoy/issues/1679 for + // details) + connection_->detectEarlyCloseWhenReadDisabled(false); + } connection_->addConnectionCallbacks(*this); connection_->addReadFilter(Network::ReadFilterSharedPtr{new CodecReadFilter(*this)}); @@ -151,6 +156,19 @@ CodecClientProd::CodecClientProd(Type type, Network::ClientConnectionPtr&& conne Http::DEFAULT_MAX_REQUEST_HEADERS_KB); break; } + case Type::HTTP3: { + // TODO(danzh) this enforce dependency from core code to QUICHE. Is there a + // better way to aoivd such dependency in case QUICHE breaks Envoy build. + // Alternatives: + // 1) move codec creation to Network::Connection instance, in + // QUIC's case, EnvoyQuicClientSession. This is not ideal as + // Network::Connection is not necessart to speak HTTP. + // 2) make codec creation in a static registered factory again. It can be + // only necessary for QUIC and for HTTP2 and HTTP1 just use the existing + // logic. + codec_ = std::make_unique( + dynamic_cast(*connection_), *this); + } } } diff --git a/source/common/http/codec_client.h b/source/common/http/codec_client.h index 66c6c6d6bbc63..ab29c15db85a4 100644 --- a/source/common/http/codec_client.h +++ b/source/common/http/codec_client.h @@ -51,7 +51,7 @@ class CodecClient : Logger::Loggable, /** * Type of HTTP codec to use. */ - enum class Type { HTTP1, HTTP2 }; + enum class Type { HTTP1, HTTP2, HTTP3 }; ~CodecClient() override; diff --git a/source/common/http/conn_manager_config.h b/source/common/http/conn_manager_config.h index ce4682de2a157..cb577b61c2005 100644 --- a/source/common/http/conn_manager_config.h +++ b/source/common/http/conn_manager_config.h @@ -26,6 +26,7 @@ namespace Http { COUNTER(downstream_cx_drain_close) \ COUNTER(downstream_cx_http1_total) \ COUNTER(downstream_cx_http2_total) \ + COUNTER(downstream_cx_http3_total) \ COUNTER(downstream_cx_idle_timeout) \ COUNTER(downstream_cx_overload_disable_keepalive) \ COUNTER(downstream_cx_protocol_error) \ @@ -44,6 +45,7 @@ namespace Http { COUNTER(downstream_rq_completed) \ COUNTER(downstream_rq_http1_total) \ COUNTER(downstream_rq_http2_total) \ + COUNTER(downstream_rq_http3_total) \ COUNTER(downstream_rq_idle_timeout) \ COUNTER(downstream_rq_non_relative_path) \ COUNTER(downstream_rq_overload_close) \ @@ -58,6 +60,7 @@ namespace Http { GAUGE(downstream_cx_active, Accumulate) \ GAUGE(downstream_cx_http1_active, Accumulate) \ GAUGE(downstream_cx_http2_active, Accumulate) \ + GAUGE(downstream_cx_http3_active, Accumulate) \ GAUGE(downstream_cx_rx_bytes_buffered, Accumulate) \ GAUGE(downstream_cx_ssl_active, Accumulate) \ GAUGE(downstream_cx_tx_bytes_buffered, Accumulate) \ diff --git a/source/common/http/conn_manager_impl.cc b/source/common/http/conn_manager_impl.cc index 8c8151a223012..176e79a98916f 100644 --- a/source/common/http/conn_manager_impl.cc +++ b/source/common/http/conn_manager_impl.cc @@ -156,6 +156,8 @@ ConnectionManagerImpl::~ConnectionManagerImpl() { if (codec_) { if (codec_->protocol() == Protocol::Http2) { stats_.named_.downstream_cx_http2_active_.dec(); + } else if (codec_->protocol() == Protocol::Http3) { + stats_.named_.downstream_cx_http3_active_.dec(); } else { stats_.named_.downstream_cx_http1_active_.dec(); } @@ -198,7 +200,7 @@ void ConnectionManagerImpl::doEndStream(ActiveStream& stream) { doDeferredStreamDestroy(stream); } - if (reset_stream && codec_->protocol() != Protocol::Http2) { + if (reset_stream && codec_->protocol() < Protocol::Http2) { drain_state_ = DrainState::Closing; } @@ -207,7 +209,7 @@ void ConnectionManagerImpl::doEndStream(ActiveStream& stream) { // Reading may have been disabled for the non-multiplexing case, so enable it again. // Also be sure to unwind any read-disable done by the prior downstream // connection. - if (drain_state_ != DrainState::Closing && codec_->protocol() != Protocol::Http2) { + if (drain_state_ != DrainState::Closing && codec_->protocol() < Protocol::Http2) { while (!read_callbacks_->connection().readEnabled()) { read_callbacks_->connection().readDisable(false); } @@ -274,11 +276,13 @@ void ConnectionManagerImpl::handleCodecException(const char* error) { Network::FilterStatus ConnectionManagerImpl::onData(Buffer::Instance& data, bool) { if (!codec_) { + // Http3 codec should have been instantiated by now. codec_ = config_.createCodec(read_callbacks_->connection(), data, *this); if (codec_->protocol() == Protocol::Http2) { stats_.named_.downstream_cx_http2_total_.inc(); stats_.named_.downstream_cx_http2_active_.inc(); } else { + ASSERT(codec_->protocol() != Protocol::Http3); stats_.named_.downstream_cx_http1_total_.inc(); stats_.named_.downstream_cx_http1_active_.inc(); } @@ -320,7 +324,7 @@ Network::FilterStatus ConnectionManagerImpl::onData(Buffer::Instance& data, bool // either redispatch if there are no streams and we have more data. If we have a single // complete non-WebSocket stream but have not responded yet we will pause socket reads // to apply back pressure. - if (codec_->protocol() != Protocol::Http2) { + if (codec_->protocol() < Protocol::Http2) { if (read_callbacks_->connection().state() == Network::Connection::State::Open && data.length() > 0 && streams_.empty()) { redispatch = true; @@ -335,6 +339,18 @@ Network::FilterStatus ConnectionManagerImpl::onData(Buffer::Instance& data, bool return Network::FilterStatus::StopIteration; } +Network::FilterStatus ConnectionManagerImpl::onNewConnection() { + if (!read_callbacks_->connection().streamInfo().protocol()) { + return Network::FilterStatus::Continue; + } + // Only QUIC connection's stream_info_ specifies protocol. + Buffer::OwnedImpl dummy; + codec_ = config_.createCodec(read_callbacks_->connection(), dummy, *this); + stats_.named_.downstream_cx_http3_total_.inc(); + stats_.named_.downstream_cx_http3_active_.inc(); + return Network::FilterStatus::StopIteration; +} + void ConnectionManagerImpl::resetAllStreams() { while (!streams_.empty()) { // Mimic a downstream reset in this case. We must also remove callbacks here. Though we are @@ -462,6 +478,8 @@ ConnectionManagerImpl::ActiveStream::ActiveStream(ConnectionManagerImpl& connect connection_manager_.stats_.named_.downstream_rq_active_.inc(); if (connection_manager_.codec_->protocol() == Protocol::Http2) { connection_manager_.stats_.named_.downstream_rq_http2_total_.inc(); + } else if (connection_manager_.codec_->protocol() == Protocol::Http3) { + connection_manager_.stats_.named_.downstream_rq_http3_total_.inc(); } else { connection_manager_.stats_.named_.downstream_rq_http1_total_.inc(); } @@ -761,7 +779,7 @@ void ConnectionManagerImpl::ActiveStream::decodeHeaders(HeaderMapPtr&& headers, // Note: Proxy-Connection is not a standard header, but is supported here // since it is supported by http-parser the underlying parser for http // requests. - if (protocol != Protocol::Http2 && !state_.saw_connection_close_ && + if (protocol < Protocol::Http2 && !state_.saw_connection_close_ && request_headers_->ProxyConnection() && absl::EqualsIgnoreCase(request_headers_->ProxyConnection()->value().getStringView(), Http::Headers::get().ConnectionValues.Close)) { @@ -1441,7 +1459,7 @@ void ConnectionManagerImpl::ActiveStream::encodeHeaders(ActiveStreamEncoderFilte // multiplexing, we should disconnect since we don't want to wait around for the request to // finish. if (!state_.remote_complete_) { - if (connection_manager_.codec_->protocol() != Protocol::Http2) { + if (connection_manager_.codec_->protocol() < Protocol::Http2) { connection_manager_.drain_state_ = DrainState::Closing; } @@ -1449,7 +1467,7 @@ void ConnectionManagerImpl::ActiveStream::encodeHeaders(ActiveStreamEncoderFilte } if (connection_manager_.drain_state_ != DrainState::NotDraining && - connection_manager_.codec_->protocol() != Protocol::Http2) { + connection_manager_.codec_->protocol() < Protocol::Http2) { // If the connection manager is draining send "Connection: Close" on HTTP/1.1 connections. // Do not do this for H2 (which drains via GOAWAY) or Upgrade (as the upgrade // payload is no longer HTTP/1.1) diff --git a/source/common/http/conn_manager_impl.h b/source/common/http/conn_manager_impl.h index 384c18e4caff7..ab1af23a7ddbe 100644 --- a/source/common/http/conn_manager_impl.h +++ b/source/common/http/conn_manager_impl.h @@ -68,7 +68,7 @@ class ConnectionManagerImpl : Logger::Loggable, // Network::ReadFilter Network::FilterStatus onData(Buffer::Instance& data, bool end_stream) override; - Network::FilterStatus onNewConnection() override { return Network::FilterStatus::Continue; } + Network::FilterStatus onNewConnection() override; void initializeReadFilterCallbacks(Network::ReadFilterCallbacks& callbacks) override; // Http::ConnectionCallbacks diff --git a/source/common/http/headers.h b/source/common/http/headers.h index 1bdeb0909014f..c16738d8042d9 100644 --- a/source/common/http/headers.h +++ b/source/common/http/headers.h @@ -273,6 +273,7 @@ class HeaderValues { const std::string Http10String{"HTTP/1.0"}; const std::string Http11String{"HTTP/1.1"}; const std::string Http2String{"HTTP/2"}; + const std::string Http3String{"HTTP/3"}; } ProtocolStrings; struct { diff --git a/source/common/http/utility.cc b/source/common/http/utility.cc index 65a708903745c..e7c800538a943 100644 --- a/source/common/http/utility.cc +++ b/source/common/http/utility.cc @@ -388,6 +388,8 @@ const std::string& Utility::getProtocolString(const Protocol protocol) { return Headers::get().ProtocolStrings.Http11String; case Protocol::Http2: return Headers::get().ProtocolStrings.Http2String; + case Protocol::Http3: + return Headers::get().ProtocolStrings.Http3String; } NOT_REACHED_GCOVR_EXCL_LINE; diff --git a/source/extensions/access_loggers/grpc/http_grpc_access_log_impl.cc b/source/extensions/access_loggers/grpc/http_grpc_access_log_impl.cc index e5bad26a3efaf..a0d0ca4b3abf7 100644 --- a/source/extensions/access_loggers/grpc/http_grpc_access_log_impl.cc +++ b/source/extensions/access_loggers/grpc/http_grpc_access_log_impl.cc @@ -60,6 +60,9 @@ void HttpGrpcAccessLog::emitLog(const Http::HeaderMap& request_headers, case Http::Protocol::Http2: log_entry.set_protocol_version(envoy::data::accesslog::v2::HTTPAccessLogEntry::HTTP2); break; + case Http::Protocol::Http3: + log_entry.set_protocol_version(envoy::data::accesslog::v2::HTTPAccessLogEntry::HTTP3); + break; } } diff --git a/source/extensions/filters/network/http_connection_manager/config.cc b/source/extensions/filters/network/http_connection_manager/config.cc index b18316ad5217d..6a3d45bdb70ec 100644 --- a/source/extensions/filters/network/http_connection_manager/config.cc +++ b/source/extensions/filters/network/http_connection_manager/config.cc @@ -25,6 +25,8 @@ #include "common/router/rds_impl.h" #include "common/router/scoped_rds.h" +#include "extensions/quic_listeners/quiche/codec_impl.h" + namespace Envoy { namespace Extensions { namespace NetworkFilters { @@ -324,6 +326,9 @@ HttpConnectionManagerConfig::HttpConnectionManagerConfig( case envoy::config::filter::network::http_connection_manager::v2::HttpConnectionManager::HTTP2: codec_type_ = CodecType::HTTP2; break; + case envoy::config::filter::network::http_connection_manager::v2::HttpConnectionManager::HTTP3: + codec_type_ = CodecType::HTTP3; + break; default: NOT_REACHED_GCOVR_EXCL_LINE; } @@ -404,6 +409,11 @@ HttpConnectionManagerConfig::createCodec(Network::Connection& connection, case CodecType::HTTP2: return std::make_unique( connection, callbacks, context_.scope(), http2_settings_, maxRequestHeadersKb()); + case CodecType::HTTP3: + // TODO(danzh) same as client side. This enforce dependency on QUICHE. Is there a + // better way to aoivd such dependency in case QUICHE breaks this extension. + return std::make_unique( + dynamic_cast(connection), callbacks); case CodecType::AUTO: return Http::ConnectionManagerUtility::autoCreateCodec(connection, data, callbacks, context_.scope(), http1_settings_, diff --git a/source/extensions/filters/network/http_connection_manager/config.h b/source/extensions/filters/network/http_connection_manager/config.h index 0385762236c1d..8cfee8cabb686 100644 --- a/source/extensions/filters/network/http_connection_manager/config.h +++ b/source/extensions/filters/network/http_connection_manager/config.h @@ -145,7 +145,7 @@ class HttpConnectionManagerConfig : Logger::Loggable, std::chrono::milliseconds delayedCloseTimeout() const override { return delayed_close_timeout_; } private: - enum class CodecType { HTTP1, HTTP2, AUTO }; + enum class CodecType { HTTP1, HTTP2, HTTP3, AUTO }; void processFilter( const envoy::config::filter::network::http_connection_manager::v2::HttpFilter& proto_config, int i, absl::string_view prefix, FilterFactoriesList& filter_factories, bool& is_terminal); diff --git a/source/extensions/quic_listeners/quiche/BUILD b/source/extensions/quic_listeners/quiche/BUILD index d0e569e3d93da..3f6b86ab63f3b 100644 --- a/source/extensions/quic_listeners/quiche/BUILD +++ b/source/extensions/quic_listeners/quiche/BUILD @@ -97,6 +97,7 @@ envoy_cc_library( hdrs = ["envoy_quic_stream.h"], tags = ["nofips"], deps = [ + ":envoy_quic_simulated_watermark_buffer_lib", "//include/envoy/http:codec_interface", "//source/common/http:codec_helper_lib", ], @@ -123,6 +124,7 @@ envoy_cc_library( hdrs = ["codec_impl.h"], tags = ["nofips"], deps = [ + ":envoy_quic_client_session_lib", ":envoy_quic_server_session_lib", "//include/envoy/http:codec_interface", "@com_googlesource_quiche//:quic_core_http_spdy_session_lib", @@ -136,9 +138,13 @@ envoy_cc_library( tags = ["nofips"], deps = [ ":envoy_quic_connection_lib", + ":envoy_quic_simulated_watermark_buffer_lib", "//include/envoy/event:dispatcher_interface", "//include/envoy/network:connection_interface", + "//source/common/buffer:buffer_lib", + "//source/common/common:assert_lib", "//source/common/common:empty_string", + "//source/common/http:header_map_lib", "//source/common/network:filter_manager_lib", "//source/common/stream_info:stream_info_lib", ], @@ -146,16 +152,56 @@ envoy_cc_library( envoy_cc_library( name = "envoy_quic_server_session_lib", - srcs = ["envoy_quic_server_session.cc"], - hdrs = ["envoy_quic_server_session.h"], + srcs = [ + "envoy_quic_server_session.cc", + "envoy_quic_server_stream.cc", + ], + hdrs = [ + "envoy_quic_server_session.h", + "envoy_quic_server_stream.h", + ], tags = ["nofips"], deps = [ - ":envoy_quic_server_stream_lib", + ":envoy_quic_stream_lib", + ":envoy_quic_utils_lib", ":quic_filter_manager_connection_lib", + "//source/common/buffer:buffer_lib", + "//source/common/common:assert_lib", + "//source/common/http:header_map_lib", "@com_googlesource_quiche//:quic_core_http_spdy_session_lib", ], ) +envoy_cc_library( + name = "envoy_quic_client_stream_lib", + tags = ["nofips"], + deps = [ + ], +) + +envoy_cc_library( + name = "envoy_quic_client_session_lib", + srcs = [ + "envoy_quic_client_session.cc", + "envoy_quic_client_stream.cc", + ], + hdrs = [ + "envoy_quic_client_session.h", + "envoy_quic_client_stream.h", + ], + tags = ["nofips"], + deps = [ + ":envoy_quic_client_connection_lib", + ":envoy_quic_stream_lib", + ":envoy_quic_utils_lib", + ":quic_filter_manager_connection_lib", + "//source/common/buffer:buffer_lib", + "//source/common/common:assert_lib", + "//source/common/http:header_map_lib", + "@com_googlesource_quiche//:quic_core_http_client_lib", + ], +) + envoy_cc_library( name = "quic_io_handle_wrapper_lib", hdrs = ["quic_io_handle_wrapper.h"], @@ -191,6 +237,19 @@ envoy_cc_library( ], ) +envoy_cc_library( + name = "envoy_quic_client_connection_lib", + srcs = ["envoy_quic_client_connection.cc"], + hdrs = ["envoy_quic_client_connection.h"], + tags = ["nofips"], + deps = [ + ":envoy_quic_connection_lib", + ":envoy_quic_packet_writer_lib", + "//include/envoy/event:dispatcher_interface", + "//source/common/network:socket_option_factory_lib", + ], +) + envoy_cc_library( name = "envoy_quic_dispatcher_lib", srcs = ["envoy_quic_dispatcher.cc"], @@ -207,6 +266,11 @@ envoy_cc_library( ], ) +envoy_cc_library( + name = "envoy_quic_simulated_watermark_buffer_lib", + hdrs = ["envoy_quic_simulated_watermark_buffer.h"], +) + envoy_cc_library( name = "active_quic_listener_lib", srcs = ["active_quic_listener.cc"], diff --git a/source/extensions/quic_listeners/quiche/active_quic_listener.cc b/source/extensions/quic_listeners/quiche/active_quic_listener.cc index 15d1b700e0b44..af6d92fad2a17 100644 --- a/source/extensions/quic_listeners/quiche/active_quic_listener.cc +++ b/source/extensions/quic_listeners/quiche/active_quic_listener.cc @@ -43,6 +43,8 @@ ActiveQuicListener::ActiveQuicListener(Event::Dispatcher& dispatcher, quic::QuicRandom::GetInstance(), std::make_unique(), quic::KeyExchangeSource::Default()); auto connection_helper = std::make_unique(dispatcher_); + crypto_config_->AddDefaultConfig(random, connection_helper->GetClock(), + quic::QuicCryptoServerConfig::ConfigOptions()); auto alarm_factory = std::make_unique(dispatcher_, *connection_helper->GetClock()); quic_dispatcher_ = std::make_unique( @@ -52,6 +54,8 @@ ActiveQuicListener::ActiveQuicListener(Event::Dispatcher& dispatcher, quic_dispatcher_->InitializeWithWriter(writer.release()); } +ActiveQuicListener::~ActiveQuicListener() { onListenerShutdown(); } + void ActiveQuicListener::onListenerShutdown() { ENVOY_LOG(info, "Quic listener {} shutdown.", config_.name()); quic_dispatcher_->Shutdown(); @@ -63,7 +67,7 @@ void ActiveQuicListener::onData(Network::UdpRecvData& data) { envoyAddressInstanceToQuicSocketAddress(data.local_address_)); quic::QuicTime timestamp = quic::QuicTime::Zero() + - quic::QuicTime::Delta::FromMilliseconds(std::chrono::duration_cast( + quic::QuicTime::Delta::FromMicroseconds(std::chrono::duration_cast( data.receive_time_.time_since_epoch()) .count()); uint64_t num_slice = data.buffer_->getRawSlices(nullptr, 0); diff --git a/source/extensions/quic_listeners/quiche/active_quic_listener.h b/source/extensions/quic_listeners/quiche/active_quic_listener.h index 89ef57d83727d..d724790bd5299 100644 --- a/source/extensions/quic_listeners/quiche/active_quic_listener.h +++ b/source/extensions/quic_listeners/quiche/active_quic_listener.h @@ -32,6 +32,8 @@ class ActiveQuicListener : public Network::UdpListenerCallbacks, Network::UdpListenerPtr&& listener, Network::ListenerConfig& listener_config, const quic::QuicConfig& quic_config); + ~ActiveQuicListener() override; + // TODO(#7465): Make this a callback. void onListenerShutdown(); diff --git a/source/extensions/quic_listeners/quiche/codec_impl.cc b/source/extensions/quic_listeners/quiche/codec_impl.cc index fdb060cb7c159..3c16b805bfb21 100644 --- a/source/extensions/quic_listeners/quiche/codec_impl.cc +++ b/source/extensions/quic_listeners/quiche/codec_impl.cc @@ -1,23 +1,72 @@ #include "extensions/quic_listeners/quiche/codec_impl.h" +#include "extensions/quic_listeners/quiche/envoy_quic_client_stream.h" +#include "extensions/quic_listeners/quiche/envoy_quic_server_stream.h" + namespace Envoy { namespace Quic { bool QuicHttpConnectionImplBase::wantsToWrite() { return quic_session_.HasDataToWrite(); } +QuicHttpServerConnectionImpl::QuicHttpServerConnectionImpl( + EnvoyQuicServerSession& quic_session, Http::ServerConnectionCallbacks& callbacks) + : QuicHttpConnectionImplBase(quic_session), quic_server_session_(quic_session) { + quic_session.setHttpConnectionCallbacks(callbacks); +} + // TODO(danzh): modify QUIC stack to react based on aggregated bytes across all -// the streams. And call StreamCallbackHelper::runHighWatermarkCallbacks() for each stream. -void QuicHttpConnectionImplBase::onUnderlyingConnectionAboveWriteBufferHighWatermark() { - NOT_IMPLEMENTED_GCOVR_EXCL_LINE; +// streams. +void QuicHttpServerConnectionImpl::onUnderlyingConnectionAboveWriteBufferHighWatermark() { + for (auto& it : quic_server_session_.stream_map()) { + if (!it.second->is_static()) { + dynamic_cast(it.second.get())->runHighWatermarkCallbacks(); + } + } } -void QuicHttpConnectionImplBase::onUnderlyingConnectionBelowWriteBufferLowWatermark() { - NOT_IMPLEMENTED_GCOVR_EXCL_LINE; +void QuicHttpServerConnectionImpl::onUnderlyingConnectionBelowWriteBufferLowWatermark() { + for (const auto& it : quic_server_session_.stream_map()) { + if (!it.second->is_static()) { + dynamic_cast(it.second.get())->runLowWatermarkCallbacks(); + } + } } void QuicHttpServerConnectionImpl::goAway() { quic_server_session_.SendGoAway(quic::QUIC_PEER_GOING_AWAY, "server shutdown imminent"); } +QuicHttpClientConnectionImpl::QuicHttpClientConnectionImpl(EnvoyQuicClientSession& session, + Http::ConnectionCallbacks& callbacks) + : QuicHttpConnectionImplBase(session), quic_client_session_(session) { + session.setHttpConnectionCallbacks(callbacks); +} + +Http::StreamEncoder& +QuicHttpClientConnectionImpl::newStream(Http::StreamDecoder& response_decoder) { + auto stream = dynamic_cast( + quic_client_session_.CreateOutgoingBidirectionalStream()); + stream->setDecoder(response_decoder); + return *stream; +} + +// TODO(danzh): modify QUIC stack to react based on aggregated bytes across all +// streams. +void QuicHttpClientConnectionImpl::onUnderlyingConnectionAboveWriteBufferHighWatermark() { + for (auto& it : quic_client_session_.stream_map()) { + if (!it.second->is_static()) { + dynamic_cast(it.second.get())->runHighWatermarkCallbacks(); + } + } +} + +void QuicHttpClientConnectionImpl::onUnderlyingConnectionBelowWriteBufferLowWatermark() { + for (const auto& it : quic_client_session_.stream_map()) { + if (!it.second->is_static()) { + dynamic_cast(it.second.get())->runLowWatermarkCallbacks(); + } + } +} + } // namespace Quic } // namespace Envoy diff --git a/source/extensions/quic_listeners/quiche/codec_impl.h b/source/extensions/quic_listeners/quiche/codec_impl.h index debff738cb044..141d73220d98a 100644 --- a/source/extensions/quic_listeners/quiche/codec_impl.h +++ b/source/extensions/quic_listeners/quiche/codec_impl.h @@ -3,6 +3,7 @@ #include "common/common/assert.h" #include "common/common/logger.h" +#include "extensions/quic_listeners/quiche/envoy_quic_client_session.h" #include "extensions/quic_listeners/quiche/envoy_quic_server_session.h" namespace Envoy { @@ -21,17 +22,11 @@ class QuicHttpConnectionImplBase : public virtual Http::Connection, // Bypassed. QUIC connection already hands all data to streams. NOT_REACHED_GCOVR_EXCL_LINE; } - Http::Protocol protocol() override { - // From HCM's view, QUIC should behave the same as Http2, only the stats - // should be different. - // TODO(danzh) add Http3 enum value for QUIC. - return Http::Protocol::Http2; - } + Http::Protocol protocol() override { return Http::Protocol::Http3; } + // Returns true if the session has data to send but queued in connection or // stream send buffer. bool wantsToWrite() override; - void onUnderlyingConnectionAboveWriteBufferHighWatermark() override; - void onUnderlyingConnectionBelowWriteBufferLowWatermark() override; protected: quic::QuicSpdySession& quic_session_; @@ -41,10 +36,7 @@ class QuicHttpServerConnectionImpl : public QuicHttpConnectionImplBase, public Http::ServerConnection { public: QuicHttpServerConnectionImpl(EnvoyQuicServerSession& quic_session, - Http::ServerConnectionCallbacks& callbacks) - : QuicHttpConnectionImplBase(quic_session), quic_server_session_(quic_session) { - quic_session.setHttpConnectionCallbacks(callbacks); - } + Http::ServerConnectionCallbacks& callbacks); // Http::Connection void goAway() override; @@ -52,10 +44,31 @@ class QuicHttpServerConnectionImpl : public QuicHttpConnectionImplBase, // TODO(danzh): Add double-GOAWAY support in QUIC. ENVOY_CONN_LOG(error, "Shutdown notice is not propagated to QUIC.", quic_server_session_); } + void onUnderlyingConnectionAboveWriteBufferHighWatermark() override; + void onUnderlyingConnectionBelowWriteBufferLowWatermark() override; private: EnvoyQuicServerSession& quic_server_session_; }; +class QuicHttpClientConnectionImpl : public QuicHttpConnectionImplBase, + public Http::ClientConnection { +public: + QuicHttpClientConnectionImpl(EnvoyQuicClientSession& session, + Http::ConnectionCallbacks& callbacks); + + // Http::ClientConnection + Http::StreamEncoder& newStream(Http::StreamDecoder& response_decoder) override; + + // Http::Connection + void goAway() override { NOT_REACHED_GCOVR_EXCL_LINE; } + void shutdownNotice() override { NOT_REACHED_GCOVR_EXCL_LINE; } + void onUnderlyingConnectionAboveWriteBufferHighWatermark() override; + void onUnderlyingConnectionBelowWriteBufferLowWatermark() override; + +private: + EnvoyQuicClientSession& quic_client_session_; +}; + } // namespace Quic } // namespace Envoy diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_alarm.h b/source/extensions/quic_listeners/quiche/envoy_quic_alarm.h index 4152f4c101c3f..22010987c4def 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_alarm.h +++ b/source/extensions/quic_listeners/quiche/envoy_quic_alarm.h @@ -20,7 +20,11 @@ class EnvoyQuicAlarm : public quic::QuicAlarm { EnvoyQuicAlarm(Event::Dispatcher& dispatcher, const quic::QuicClock& clock, quic::QuicArenaScopedPtr delegate); - ~EnvoyQuicAlarm() override { ASSERT(!IsSet()); }; + ~EnvoyQuicAlarm() override { + if (IsSet()) { + Cancel(); + } + }; // quic::QuicAlarm void CancelImpl() override; diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_client_connection.cc b/source/extensions/quic_listeners/quiche/envoy_quic_client_connection.cc new file mode 100644 index 0000000000000..9a6dfb01028cf --- /dev/null +++ b/source/extensions/quic_listeners/quiche/envoy_quic_client_connection.cc @@ -0,0 +1,176 @@ +#include "extensions/quic_listeners/quiche/envoy_quic_client_connection.h" + +#include "common/network/listen_socket_impl.h" +#include "common/network/socket_option_factory.h" + +#include "extensions/quic_listeners/quiche/envoy_quic_packet_writer.h" +#include "extensions/quic_listeners/quiche/envoy_quic_utils.h" +#include "extensions/transport_sockets/well_known_names.h" + +namespace Envoy { +namespace Quic { + +EnvoyQuicClientConnection::EnvoyQuicClientConnection( + const quic::QuicConnectionId& server_connection_id, + Network::Address::InstanceConstSharedPtr& initial_peer_address, + quic::QuicConnectionHelperInterface& helper, quic::QuicAlarmFactory& alarm_factory, + const quic::ParsedQuicVersionVector& supported_versions, + Network::Address::InstanceConstSharedPtr local_addr, Event::Dispatcher& dispatcher, + const Network::ConnectionSocket::OptionsSharedPtr& options) + : EnvoyQuicClientConnection(server_connection_id, helper, alarm_factory, supported_versions, + dispatcher, + createConnectionSocket(initial_peer_address, local_addr, options)) { +} + +EnvoyQuicClientConnection::EnvoyQuicClientConnection( + const quic::QuicConnectionId& server_connection_id, quic::QuicConnectionHelperInterface& helper, + quic::QuicAlarmFactory& alarm_factory, const quic::ParsedQuicVersionVector& supported_versions, + Event::Dispatcher& dispatcher, Network::ConnectionSocketPtr&& connection_socket) + : EnvoyQuicClientConnection(server_connection_id, helper, alarm_factory, + new EnvoyQuicPacketWriter(*connection_socket), true, + supported_versions, dispatcher, std::move(connection_socket)) {} + +EnvoyQuicClientConnection::EnvoyQuicClientConnection( + const quic::QuicConnectionId& server_connection_id, quic::QuicConnectionHelperInterface& helper, + quic::QuicAlarmFactory& alarm_factory, quic::QuicPacketWriter* writer, bool owns_writer, + const quic::ParsedQuicVersionVector& supported_versions, Event::Dispatcher& dispatcher, + Network::ConnectionSocketPtr&& connection_socket) + : EnvoyQuicConnection( + server_connection_id, + envoyAddressInstanceToQuicSocketAddress(connection_socket->remoteAddress()), helper, + alarm_factory, writer, owns_writer, quic::Perspective::IS_CLIENT, supported_versions, + std::move(connection_socket)), + dispatcher_(dispatcher) {} + +EnvoyQuicClientConnection::~EnvoyQuicClientConnection() { file_event_->setEnabled(0); } + +void EnvoyQuicClientConnection::processPacket( + Network::Address::InstanceConstSharedPtr local_address, + Network::Address::InstanceConstSharedPtr peer_address, Buffer::InstancePtr buffer, + MonotonicTime receive_time) { + quic::QuicTime timestamp = + quic::QuicTime::Zero() + + quic::QuicTime::Delta::FromMicroseconds( + std::chrono::duration_cast(receive_time.time_since_epoch()) + .count()); + uint64_t num_slice = buffer->getRawSlices(nullptr, 0); + ASSERT(num_slice == 1); + Buffer::RawSlice slice; + buffer->getRawSlices(&slice, 1); + quic::QuicReceivedPacket packet(reinterpret_cast(slice.mem_), slice.len_, timestamp, + /*owns_buffer=*/false, /*ttl=*/0, /*ttl_valid=*/true, + /*packet_headers=*/nullptr, /*headers_length=*/0, + /*owns_header_buffer*/ false); + ProcessUdpPacket(envoyAddressInstanceToQuicSocketAddress(local_address), + envoyAddressInstanceToQuicSocketAddress(peer_address), packet); +} + +uint64_t EnvoyQuicClientConnection::maxPacketSize() const { + // TODO(danzh) make this variable configurable to support jumbo frames. + return Network::MAX_UDP_PACKET_SIZE; +} + +void EnvoyQuicClientConnection::setUpConnectionSocket() { + if (connectionSocket()->ioHandle().isOpen()) { + file_event_ = dispatcher_.createFileEvent( + connectionSocket()->ioHandle().fd(), + [this](uint32_t events) -> void { onFileEvent(events); }, Event::FileTriggerType::Edge, + Event::FileReadyType::Read | Event::FileReadyType::Write); + + if (!Network::Socket::applyOptions(connectionSocket()->options(), *connectionSocket(), + envoy::api::v2::core::SocketOption::STATE_LISTENING)) { + ENVOY_LOG_MISC(error, "Fail to apply listening options"); + connectionSocket()->close(); + } + } + if (!connectionSocket()->ioHandle().isOpen()) { + CloseConnection(quic::QUIC_CONNECTION_CANCELLED, "Fail to setup connection socket.", + quic::ConnectionCloseBehavior::SILENT_CLOSE); + } +} + +void EnvoyQuicClientConnection::onFileEvent(uint32_t events) { + ENVOY_CONN_LOG(trace, "socket event: {}", *this, events); + ASSERT(events & (Event::FileReadyType::Read | Event::FileReadyType::Write)); + + if (events & Event::FileReadyType::Write) { + OnCanWrite(); + } + + // It's possible for a write event callback to close the connection, in such case ignore read + // event processing. + if (connected() && (events & Event::FileReadyType::Read)) { + uint32_t old_packets_dropped = packets_dropped_; + while (connected()) { + // Read till socket is drained. + // TODO(danzh): limit read times here. + MonotonicTime receive_time = dispatcher_.timeSource().monotonicTime(); + Api::IoCallUint64Result result = Network::Utility::readFromSocket( + *connectionSocket(), *this, receive_time, &packets_dropped_); + if (!result.ok()) { + if (result.err_->getErrorCode() != Api::IoError::IoErrorCode::Again) { + ENVOY_CONN_LOG(error, "recvmsg result {}: {}", *this, + static_cast(result.err_->getErrorCode()), + result.err_->getErrorDetails()); + } + // Stop reading. + break; + } + + if (result.rc_ == 0) { + // TODO(conqerAtapple): Is zero length packet interesting? If so add stats + // for it. Otherwise remove the warning log below. + ENVOY_CONN_LOG(trace, "received 0-length packet", *this); + } + + if (packets_dropped_ != old_packets_dropped) { + // The kernel tracks SO_RXQ_OVFL as a uint32 which can overflow to a smaller + // value. So as long as this count differs from previously recorded value, + // more packets are dropped by kernel. + uint32_t delta = (packets_dropped_ > old_packets_dropped) + ? (packets_dropped_ - old_packets_dropped) + : (packets_dropped_ + + (std::numeric_limits::max() - old_packets_dropped) + 1); + // TODO(danzh) add stats for this. + ENVOY_CONN_LOG(debug, + "Kernel dropped {} more packets. Consider increase receive buffer size.", + *this, delta); + } + } + } +} + +Network::ConnectionSocketPtr EnvoyQuicClientConnection::createConnectionSocket( + Network::Address::InstanceConstSharedPtr& peer_addr, + Network::Address::InstanceConstSharedPtr& local_addr, + const Network::ConnectionSocket::OptionsSharedPtr& options) { + Network::IoHandlePtr io_handle = peer_addr->socket(Network::Address::SocketType::Datagram); + auto connection_socket = + std::make_unique(std::move(io_handle), local_addr, peer_addr); + connection_socket->addOptions(Network::SocketOptionFactory::buildIpPacketInfoOptions()); + connection_socket->addOptions(Network::SocketOptionFactory::buildRxQueueOverFlowOptions()); + if (options != nullptr) { + connection_socket->addOptions(options); + } + if (!Network::Socket::applyOptions(connection_socket->options(), *connection_socket, + envoy::api::v2::core::SocketOption::STATE_PREBIND)) { + connection_socket->close(); + ENVOY_LOG_MISC(error, "Fail to apply pre-bind options"); + return connection_socket; + } + local_addr->bind(connection_socket->ioHandle().fd()); + ASSERT(local_addr->ip()); + if (local_addr->ip()->port() == 0) { + // Get ephemeral port number. + local_addr = Network::Address::addressFromFd(connection_socket->ioHandle().fd()); + } + if (!Network::Socket::applyOptions(connection_socket->options(), *connection_socket, + envoy::api::v2::core::SocketOption::STATE_BOUND)) { + ENVOY_LOG_MISC(error, "Fail to apply post-bind options"); + connection_socket->close(); + } + return connection_socket; +} + +} // namespace Quic +} // namespace Envoy diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_client_connection.h b/source/extensions/quic_listeners/quiche/envoy_quic_client_connection.h new file mode 100644 index 0000000000000..f9d79af6acca8 --- /dev/null +++ b/source/extensions/quic_listeners/quiche/envoy_quic_client_connection.h @@ -0,0 +1,66 @@ +#pragma once + +#include "envoy/event/dispatcher.h" + +#include "common/network/utility.h" + +#include "extensions/quic_listeners/quiche/envoy_quic_connection.h" + +namespace Envoy { +namespace Quic { + +// A client QuicConnection instance manages its own I/O events. +class EnvoyQuicClientConnection : public EnvoyQuicConnection, public Network::UdpPacketProcessor { +public: + // A connection socket will be created with given |local_addr|. If binding + // port not provided in |local_addr|, pick up a random port. + EnvoyQuicClientConnection(const quic::QuicConnectionId& server_connection_id, + Network::Address::InstanceConstSharedPtr& initial_peer_address, + quic::QuicConnectionHelperInterface& helper, + quic::QuicAlarmFactory& alarm_factory, + const quic::ParsedQuicVersionVector& supported_versions, + Network::Address::InstanceConstSharedPtr local_addr, + Event::Dispatcher& dispatcher, + const Network::ConnectionSocket::OptionsSharedPtr& options); + + // Overridden to un-register all file events. + ~EnvoyQuicClientConnection() override; + + void processPacket(Network::Address::InstanceConstSharedPtr local_address, + Network::Address::InstanceConstSharedPtr peer_address, + Buffer::InstancePtr buffer, MonotonicTime receive_time) override; + + uint64_t maxPacketSize() const override; + + // Register file event and apply socket options. + void setUpConnectionSocket(); + +private: + EnvoyQuicClientConnection(const quic::QuicConnectionId& server_connection_id, + quic::QuicConnectionHelperInterface& helper, + quic::QuicAlarmFactory& alarm_factory, + const quic::ParsedQuicVersionVector& supported_versions, + Event::Dispatcher& dispatcher, + Network::ConnectionSocketPtr&& connection_socket); + + EnvoyQuicClientConnection(const quic::QuicConnectionId& server_connection_id, + quic::QuicConnectionHelperInterface& helper, + quic::QuicAlarmFactory& alarm_factory, quic::QuicPacketWriter* writer, + bool owns_writer, + const quic::ParsedQuicVersionVector& supported_versions, + Event::Dispatcher& dispatcher, + Network::ConnectionSocketPtr&& connection_socket); + + Network::ConnectionSocketPtr + createConnectionSocket(Network::Address::InstanceConstSharedPtr& peer_addr, + Network::Address::InstanceConstSharedPtr& local_addr, + const Network::ConnectionSocket::OptionsSharedPtr& options); + + void onFileEvent(uint32_t events); + uint32_t packets_dropped_{0}; + Event::Dispatcher& dispatcher_; + Event::FileEventPtr file_event_; +}; + +} // namespace Quic +} // namespace Envoy diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_client_session.cc b/source/extensions/quic_listeners/quiche/envoy_quic_client_session.cc new file mode 100644 index 0000000000000..81813668ed7e6 --- /dev/null +++ b/source/extensions/quic_listeners/quiche/envoy_quic_client_session.cc @@ -0,0 +1,85 @@ +#include "extensions/quic_listeners/quiche/envoy_quic_client_session.h" + +namespace Envoy { +namespace Quic { + +EnvoyQuicClientSession::EnvoyQuicClientSession( + const quic::QuicConfig& config, const quic::ParsedQuicVersionVector& supported_versions, + std::unique_ptr connection, const quic::QuicServerId& server_id, + quic::QuicCryptoClientConfig* crypto_config, + quic::QuicClientPushPromiseIndex* push_promise_index, Event::Dispatcher& dispatcher, + uint32_t send_buffer_limit) + : QuicFilterManagerConnectionImpl(*connection, dispatcher, send_buffer_limit), + quic::QuicSpdyClientSession(config, supported_versions, connection.release(), server_id, + crypto_config, push_promise_index) { + Initialize(); +} + +EnvoyQuicClientSession::~EnvoyQuicClientSession() { + ASSERT(!connection()->connected()); + QuicFilterManagerConnectionImpl::quic_connection_ = nullptr; +} + +absl::string_view EnvoyQuicClientSession::requestedServerName() const { + return {GetCryptoStream()->crypto_negotiated_params().sni}; +} + +void EnvoyQuicClientSession::connect() { + dynamic_cast(quic_connection_)->setUpConnectionSocket(); +} + +void EnvoyQuicClientSession::OnConnectionClosed(const quic::QuicConnectionCloseFrame& frame, + quic::ConnectionCloseSource source) { + quic::QuicSpdyClientSession::OnConnectionClosed(frame, source); + onConnectionCloseEvent(frame, source); +} + +void EnvoyQuicClientSession::Initialize() { + quic::QuicSpdyClientSession::Initialize(); + quic_connection_->setEnvoyConnection(*this); +} + +void EnvoyQuicClientSession::OnGoAway(const quic::QuicGoAwayFrame& frame) { + ENVOY_CONN_LOG(debug, "GOAWAY received with error {}: {}", *this, + quic::QuicErrorCodeToString(frame.error_code), frame.reason_phrase); + quic::QuicSpdyClientSession::OnGoAway(frame); + if (http_connection_callbacks_ != nullptr) { + http_connection_callbacks_->onGoAway(); + } +} + +void EnvoyQuicClientSession::OnCryptoHandshakeEvent(CryptoHandshakeEvent event) { + quic::QuicSpdyClientSession::OnCryptoHandshakeEvent(event); + if (event == HANDSHAKE_CONFIRMED) { + raiseEvent(Network::ConnectionEvent::Connected); + } +} + +void EnvoyQuicClientSession::cryptoConnect() { + CryptoConnect(); + set_max_allowed_push_id(0u); +} + +std::unique_ptr EnvoyQuicClientSession::CreateClientStream() { + auto stream = std::make_unique(GetNextOutgoingBidirectionalStreamId(), + this, quic::BIDIRECTIONAL); + return stream; +} + +quic::QuicSpdyStream* EnvoyQuicClientSession::CreateIncomingStream(quic::QuicStreamId id) { + if (!ShouldCreateIncomingStream(id)) { + return nullptr; + } + auto stream = new EnvoyQuicClientStream(id, this, quic::READ_UNIDIRECTIONAL); + ActivateStream(std::unique_ptr(stream)); + return stream; +} + +quic::QuicSpdyStream* EnvoyQuicClientSession::CreateIncomingStream(quic::PendingStream* pending) { + auto stream = new EnvoyQuicClientStream(pending, this, quic::READ_UNIDIRECTIONAL); + ActivateStream(std::unique_ptr(stream)); + return stream; +} + +} // namespace Quic +} // namespace Envoy diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_client_session.h b/source/extensions/quic_listeners/quiche/envoy_quic_client_session.h new file mode 100644 index 0000000000000..e686fed2d4383 --- /dev/null +++ b/source/extensions/quic_listeners/quiche/envoy_quic_client_session.h @@ -0,0 +1,80 @@ +#pragma once + +#pragma GCC diagnostic push +// QUICHE allows unused parameters. +#pragma GCC diagnostic ignored "-Wunused-parameter" +// QUICHE uses offsetof(). +#pragma GCC diagnostic ignored "-Winvalid-offsetof" +#pragma GCC diagnostic ignored "-Wtype-limits" + +#include "quiche/quic/core/http/quic_spdy_client_session.h" + +#pragma GCC diagnostic pop + +#include "extensions/quic_listeners/quiche/envoy_quic_client_stream.h" +#include "extensions/quic_listeners/quiche/envoy_quic_client_connection.h" +#include "extensions/quic_listeners/quiche/quic_filter_manager_connection_impl.h" + +namespace Envoy { +namespace Quic { + +// Act as a Network::ClientConnection to ClientCodec. +// TODO(danzh) This class doesn't need to inherit Network::FilterManager +// interface but need all other Network::Connection implementation in +// QuicFilterManagerConnectionImpl. Refactor QuicFilterManagerConnectionImpl to +// move FilterManager interface to EnvoyQuicServerSession. +class EnvoyQuicClientSession : public QuicFilterManagerConnectionImpl, + public quic::QuicSpdyClientSession, + public Network::ClientConnection { +public: + EnvoyQuicClientSession(const quic::QuicConfig& config, + const quic::ParsedQuicVersionVector& supported_versions, + std::unique_ptr connection, + const quic::QuicServerId& server_id, + quic::QuicCryptoClientConfig* crypto_config, + quic::QuicClientPushPromiseIndex* push_promise_index, + Event::Dispatcher& dispatcher, uint32_t send_buffer_limit); + + ~EnvoyQuicClientSession() override; + + // Called by QuicHttpClientConnectionImpl before creating data streams. + void setHttpConnectionCallbacks(Http::ConnectionCallbacks& callbacks) { + http_connection_callbacks_ = &callbacks; + } + + // Network::Connection + absl::string_view requestedServerName() const override; + + // Network::ClientConnection + // Only register socket and set socket options. + void connect() override; + + // quic::QuicSession + void OnConnectionClosed(const quic::QuicConnectionCloseFrame& frame, + quic::ConnectionCloseSource source) override; + void Initialize() override; + void OnGoAway(const quic::QuicGoAwayFrame& frame) override; + // quic::QuicSpdyClientSessionBase + void OnCryptoHandshakeEvent(CryptoHandshakeEvent event) override; + + // Do version negotiation and crypto handshake. Fail the connection if server + // doesn't support the one and only supported version. + void cryptoConnect(); + + using quic::QuicSpdyClientSession::stream_map; + +protected: + // quic::QuicSpdyClientSession + std::unique_ptr CreateClientStream() override; + // quic::QuicSpdySession + quic::QuicSpdyStream* CreateIncomingStream(quic::QuicStreamId id) override; + quic::QuicSpdyStream* CreateIncomingStream(quic::PendingStream* pending) override; + +private: + // These callbacks are owned by network filters and quic session should out live + // them. + Http::ConnectionCallbacks* http_connection_callbacks_{nullptr}; +}; + +} // namespace Quic +} // namespace Envoy diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_client_stream.cc b/source/extensions/quic_listeners/quiche/envoy_quic_client_stream.cc new file mode 100644 index 0000000000000..07e69ef9f9400 --- /dev/null +++ b/source/extensions/quic_listeners/quiche/envoy_quic_client_stream.cc @@ -0,0 +1,216 @@ +#include "extensions/quic_listeners/quiche/envoy_quic_client_stream.h" + +#pragma GCC diagnostic push +// QUICHE allows unused parameters. +#pragma GCC diagnostic ignored "-Wunused-parameter" +// QUICHE uses offsetof(). +#pragma GCC diagnostic ignored "-Winvalid-offsetof" + +#include "quiche/quic/core/quic_session.h" +#include "quiche/quic/core/http/quic_header_list.h" +#include "quiche/quic/core/quic_session.h" +#include "quiche/spdy/core/spdy_header_block.h" +#include "quiche/quic/platform/api/quic_mem_slice_span.h" + +#pragma GCC diagnostic pop + +#include "extensions/quic_listeners/quiche/envoy_quic_utils.h" +#include "extensions/quic_listeners/quiche/envoy_quic_client_session.h" + +#include "common/buffer/buffer_impl.h" +#include "common/http/header_map_impl.h" +#include "common/common/assert.h" + +namespace Envoy { +namespace Quic { + +EnvoyQuicClientStream::EnvoyQuicClientStream(quic::QuicStreamId id, + quic::QuicSpdyClientSession* client_session, + quic::StreamType type) + : quic::QuicSpdyClientStream(id, client_session, type), + EnvoyQuicStream( + session()->config()->GetInitialStreamFlowControlWindowToSend(), + [this]() { runLowWatermarkCallbacks(); }, [this]() { runHighWatermarkCallbacks(); }) {} + +EnvoyQuicClientStream::EnvoyQuicClientStream(quic::PendingStream* pending, + quic::QuicSpdyClientSession* client_session, + quic::StreamType type) + : quic::QuicSpdyClientStream(pending, client_session, type), + EnvoyQuicStream( + session()->config()->GetInitialStreamFlowControlWindowToSend(), + [this]() { runLowWatermarkCallbacks(); }, [this]() { runHighWatermarkCallbacks(); }) {} + +void EnvoyQuicClientStream::encode100ContinueHeaders(const Http::HeaderMap& headers) { + ASSERT(headers.Status()->value() == "100"); + encodeHeaders(headers, false); +} + +void EnvoyQuicClientStream::encodeHeaders(const Http::HeaderMap& headers, bool end_stream) { + ENVOY_STREAM_LOG(debug, "encodeHeaders: (end_stream={}) {}.", *this, end_stream, headers); + WriteHeaders(envoyHeadersToSpdyHeaderBlock(headers), end_stream, nullptr); + local_end_stream_ = end_stream; +} + +void EnvoyQuicClientStream::encodeData(Buffer::Instance& data, bool end_stream) { + ENVOY_STREAM_LOG(debug, "encodeData (end_stream={}) of {} bytes.", *this, end_stream, + data.length()); + local_end_stream_ = end_stream; + // This is counting not serialized bytes in the send buffer. + uint64_t bytes_to_send_old = BufferedDataBytes(); + // QUIC stream must take all. + WriteBodySlices(quic::QuicMemSliceSpan(quic::QuicMemSliceSpanImpl(data)), end_stream); + ASSERT(data.length() == 0); + + uint64_t bytes_to_send_new = BufferedDataBytes(); + ASSERT(bytes_to_send_old <= bytes_to_send_new); + if (bytes_to_send_new > bytes_to_send_old) { + // If buffered bytes changed, update stream and session's watermark book + // keeping. + sendBufferSimulation().checkHighWatermark(bytes_to_send_new); + dynamic_cast(session())->adjustBytesToSend(bytes_to_send_new - + bytes_to_send_old); + } +} + +void EnvoyQuicClientStream::encodeTrailers(const Http::HeaderMap& trailers) { + ASSERT(!local_end_stream_); + local_end_stream_ = true; + ENVOY_STREAM_LOG(debug, "encodeTrailers: {}.", *this, trailers); + WriteTrailers(envoyHeadersToSpdyHeaderBlock(trailers), nullptr); +} + +void EnvoyQuicClientStream::encodeMetadata(const Http::MetadataMapVector& /*metadata_map_vector*/) { + ASSERT(false, "Metadata Frame is not supported in QUIC"); +} + +void EnvoyQuicClientStream::resetStream(Http::StreamResetReason reason) { + Reset(envoyResetReasonToQuicRstError(reason)); +} + +void EnvoyQuicClientStream::switchStreamBlockState(bool should_block) { + ASSERT(FinishedReadingHeaders(), + "codec buffer limit is reached before response body is delivered."); + if (should_block) { + sequencer()->SetBlockedUntilFlush(); + } else { + sequencer()->SetUnblocked(); + } +} + +void EnvoyQuicClientStream::OnInitialHeadersComplete(bool fin, size_t frame_len, + const quic::QuicHeaderList& header_list) { + quic::QuicSpdyStream::OnInitialHeadersComplete(fin, frame_len, header_list); + if (rst_sent()) { + return; + } + ASSERT(decoder() != nullptr); + ASSERT(headers_decompressed()); + decoder()->decodeHeaders(quicHeadersToEnvoyHeaders(header_list), /*end_stream=*/fin); + if (fin) { + end_stream_decoded_ = true; + } + ConsumeHeaderList(); +} + +void EnvoyQuicClientStream::OnBodyAvailable() { + ASSERT(FinishedReadingHeaders()); + ASSERT(read_disable_counter_ == 0); + ASSERT(!in_encode_data_callstack_); + in_encode_data_callstack_ = true; + + Buffer::InstancePtr buffer = std::make_unique(); + // TODO(danzh): check Envoy per stream buffer limit. + // Currently read out all the data. + while (HasBytesToRead()) { + struct iovec iov; + int num_regions = GetReadableRegions(&iov, 1); + ASSERT(num_regions > 0); + size_t bytes_read = iov.iov_len; + Buffer::RawSlice slice; + buffer->reserve(bytes_read, &slice, 1); + ASSERT(slice.len_ >= bytes_read); + slice.len_ = bytes_read; + memcpy(slice.mem_, iov.iov_base, iov.iov_len); + buffer->commit(&slice, 1); + MarkConsumed(bytes_read); + } + + // True if no trailer and FIN read. + bool finished_reading = IsDoneReading(); + bool empty_payload_with_fin = buffer->length() == 0 && finished_reading; + if (!empty_payload_with_fin || !end_stream_decoded_) { + ASSERT(decoder() != nullptr); + decoder()->decodeData(*buffer, finished_reading); + if (finished_reading) { + end_stream_decoded_ = true; + } + } + + if (!sequencer()->IsClosed()) { + in_encode_data_callstack_ = false; + if (read_disable_counter_ > 0) { + // If readDisable() was ever called during decodeData() and it meant to disable + // reading from downstream, the call must have been deferred. Call it now. + switchStreamBlockState(true); + } + return; + } + + if (!quic::VersionUsesQpack(transport_version()) && !FinishedReadingTrailers()) { + // For Google QUIC implementation, trailers may arrived earlier and wait to + // be consumed after reading all the body. Consume it here. + // IETF QUIC shouldn't reach here because trailers are sent on same stream. + decoder()->decodeTrailers(spdyHeaderBlockToEnvoyHeaders(received_trailers())); + MarkTrailersConsumed(); + } + OnFinRead(); + in_encode_data_callstack_ = false; +} + +void EnvoyQuicClientStream::OnTrailingHeadersComplete(bool fin, size_t frame_len, + const quic::QuicHeaderList& header_list) { + quic::QuicSpdyStream::OnTrailingHeadersComplete(fin, frame_len, header_list); + if (session()->connection()->connected() && + (quic::VersionUsesQpack(transport_version()) || sequencer()->IsClosed()) && + !FinishedReadingTrailers()) { + // Before QPack trailers can arrive before body. Only decode trailers after finishing decoding + // body. + ASSERT(decoder() != nullptr); + decoder()->decodeTrailers(spdyHeaderBlockToEnvoyHeaders(received_trailers())); + MarkTrailersConsumed(); + } +} + +void EnvoyQuicClientStream::OnStreamReset(const quic::QuicRstStreamFrame& frame) { + quic::QuicSpdyClientStream::OnStreamReset(frame); + runResetCallbacks(quicRstErrorToEnvoyResetReason(frame.error_code)); +} + +void EnvoyQuicClientStream::OnConnectionClosed(quic::QuicErrorCode error, + quic::ConnectionCloseSource source) { + quic::QuicSpdyClientStream::OnConnectionClosed(error, source); + runResetCallbacks(quicErrorCodeToEnvoyResetReason(error)); +} + +void EnvoyQuicClientStream::OnCanWrite() { + uint64_t buffered_data_old = BufferedDataBytes(); + quic::QuicSpdyClientStream::OnCanWrite(); + uint64_t buffered_data_new = BufferedDataBytes(); + // As long as OnCanWriteNewData() is no-op, data to sent in buffer shouldn't + // increase. + ASSERT(buffered_data_new <= buffered_data_old); + if (buffered_data_new < buffered_data_old) { + sendBufferSimulation().checkLowWatermark(buffered_data_new); + dynamic_cast(session())->adjustBytesToSend(buffered_data_new - + buffered_data_old); + } +} + +uint32_t EnvoyQuicClientStream::streamId() { return id(); } + +Network::Connection* EnvoyQuicClientStream::connection() { + return dynamic_cast(session()); +} + +} // namespace Quic +} // namespace Envoy diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_client_stream.h b/source/extensions/quic_listeners/quiche/envoy_quic_client_stream.h new file mode 100644 index 0000000000000..6bc88ff8a9e81 --- /dev/null +++ b/source/extensions/quic_listeners/quiche/envoy_quic_client_stream.h @@ -0,0 +1,56 @@ +#pragma once + +#pragma GCC diagnostic push +// QUICHE allows unused parameters. +#pragma GCC diagnostic ignored "-Wunused-parameter" +// QUICHE uses offsetof(). +#pragma GCC diagnostic ignored "-Winvalid-offsetof" +#include "quiche/quic/core/http/quic_spdy_client_stream.h" + +#pragma GCC diagnostic pop + +#include "extensions/quic_listeners/quiche/envoy_quic_stream.h" + +namespace Envoy { +namespace Quic { + +// This class is a quic stream and also a request encoder. +class EnvoyQuicClientStream : public quic::QuicSpdyClientStream, public EnvoyQuicStream { +public: + EnvoyQuicClientStream(quic::QuicStreamId id, quic::QuicSpdyClientSession* client_session, + quic::StreamType type); + EnvoyQuicClientStream(quic::PendingStream* pending, quic::QuicSpdyClientSession* client_session, + quic::StreamType type); + + // Http::StreamEncoder + void encode100ContinueHeaders(const Http::HeaderMap& headers) override; + void encodeHeaders(const Http::HeaderMap& headers, bool end_stream) override; + void encodeData(Buffer::Instance& data, bool end_stream) override; + void encodeTrailers(const Http::HeaderMap& trailers) override; + void encodeMetadata(const Http::MetadataMapVector& metadata_map_vector) override; + + // Http::Stream + void resetStream(Http::StreamResetReason reason) override; + // quic::QuicSpdyStream + void OnBodyAvailable() override; + void OnStreamReset(const quic::QuicRstStreamFrame& frame) override; + void OnCanWrite() override; + // quic::Stream + void OnConnectionClosed(quic::QuicErrorCode error, quic::ConnectionCloseSource source) override; + +protected: + // EnvoyQuicStream + void switchStreamBlockState(bool should_block) override; + uint32_t streamId() override; + Network::Connection* connection() override; + + // quic::QuicSpdyStream + // Overridden to pass headers to decoder. + void OnInitialHeadersComplete(bool fin, size_t frame_len, + const quic::QuicHeaderList& header_list) override; + void OnTrailingHeadersComplete(bool fin, size_t frame_len, + const quic::QuicHeaderList& header_list) override; +}; + +} // namespace Quic +} // namespace Envoy diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_connection.cc b/source/extensions/quic_listeners/quiche/envoy_quic_connection.cc index dcc311a6eaac6..f2459bf79a190 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_connection.cc +++ b/source/extensions/quic_listeners/quiche/envoy_quic_connection.cc @@ -19,7 +19,10 @@ EnvoyQuicConnection::EnvoyQuicConnection(const quic::QuicConnectionId& server_co EnvoyQuicConnection::~EnvoyQuicConnection() { connection_socket_->close(); } -uint64_t EnvoyQuicConnection::id() const { return envoy_connection_->id(); } +uint64_t EnvoyQuicConnection::id() const { + ASSERT(envoy_connection_ != nullptr); + return envoy_connection_->id(); +} } // namespace Quic } // namespace Envoy diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_dispatcher.cc b/source/extensions/quic_listeners/quiche/envoy_quic_dispatcher.cc index 07126a2908588..b4f103469d818 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_dispatcher.cc +++ b/source/extensions/quic_listeners/quiche/envoy_quic_dispatcher.cc @@ -44,7 +44,8 @@ quic::QuicSession* EnvoyQuicDispatcher::CreateQuicSession( listener_stats_); auto quic_session = new EnvoyQuicServerSession( config(), quic::ParsedQuicVersionVector{version}, std::move(quic_connection), this, - session_helper(), crypto_config(), compressed_certs_cache(), dispatcher_); + session_helper(), crypto_config(), compressed_certs_cache(), dispatcher_, + listener_config_.perConnectionBufferLimitBytes()); quic_session->Initialize(); // Filter chain can't be retrieved here as self address is unknown at this // point. diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_fake_proof_verifier.h b/source/extensions/quic_listeners/quiche/envoy_quic_fake_proof_verifier.h index 0861e09fb4d9b..c8355717bccb8 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_fake_proof_verifier.h +++ b/source/extensions/quic_listeners/quiche/envoy_quic_fake_proof_verifier.h @@ -48,7 +48,8 @@ class EnvoyQuicFakeProofVerifier : public quic::ProofVerifier { const quic::ProofVerifyContext* /*context*/, std::string* /*error_details*/, std::unique_ptr* /*details*/, std::unique_ptr /*callback*/) override { - if (cert_sct == "Fake timestamp" && certs.size() == 1 && certs[0] == "Fake cert") { + // Cert SCT support is not enabled for fake ProofSource. + if (cert_sct == "" && certs.size() == 1 && certs[0] == "Fake cert") { return quic::QUIC_SUCCESS; } return quic::QUIC_FAILURE; diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_server_session.cc b/source/extensions/quic_listeners/quiche/envoy_quic_server_session.cc index 021c390e474d5..456cc77da297e 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_server_session.cc +++ b/source/extensions/quic_listeners/quiche/envoy_quic_server_session.cc @@ -9,6 +9,7 @@ #include "quiche/quic/core/quic_crypto_server_stream.h" #pragma GCC diagnostic pop +#include "common/common/assert.h" #include "extensions/quic_listeners/quiche/envoy_quic_server_stream.h" namespace Envoy { @@ -18,10 +19,17 @@ EnvoyQuicServerSession::EnvoyQuicServerSession( const quic::QuicConfig& config, const quic::ParsedQuicVersionVector& supported_versions, std::unique_ptr connection, quic::QuicSession::Visitor* visitor, quic::QuicCryptoServerStream::Helper* helper, const quic::QuicCryptoServerConfig* crypto_config, - quic::QuicCompressedCertsCache* compressed_certs_cache, Event::Dispatcher& dispatcher) + quic::QuicCompressedCertsCache* compressed_certs_cache, Event::Dispatcher& dispatcher, + uint32_t send_buffer_limit) : quic::QuicServerSessionBase(config, supported_versions, connection.get(), visitor, helper, crypto_config, compressed_certs_cache), - QuicFilterManagerConnectionImpl(std::move(connection), dispatcher) {} + QuicFilterManagerConnectionImpl(*connection, dispatcher, send_buffer_limit), + quic_connection_(std::move(connection)) {} + +EnvoyQuicServerSession::~EnvoyQuicServerSession() { + ASSERT(!quic_connection_->connected()); + QuicFilterManagerConnectionImpl::quic_connection_ = nullptr; +} absl::string_view EnvoyQuicServerSession::requestedServerName() const { return {GetCryptoStream()->crypto_negotiated_params().sni}; diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_server_session.h b/source/extensions/quic_listeners/quiche/envoy_quic_server_session.h index d849aa0161cac..5c5561cbb7f52 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_server_session.h +++ b/source/extensions/quic_listeners/quiche/envoy_quic_server_session.h @@ -30,7 +30,9 @@ class EnvoyQuicServerSession : public quic::QuicServerSessionBase, quic::QuicCryptoServerStream::Helper* helper, const quic::QuicCryptoServerConfig* crypto_config, quic::QuicCompressedCertsCache* compressed_certs_cache, - Event::Dispatcher& dispatcher); + Event::Dispatcher& dispatcher, uint32_t send_buffer_limit); + + ~EnvoyQuicServerSession() override; // Network::Connection absl::string_view requestedServerName() const override; @@ -48,6 +50,8 @@ class EnvoyQuicServerSession : public quic::QuicServerSessionBase, // quic::QuicSpdySession void OnCryptoHandshakeEvent(CryptoHandshakeEvent event) override; + using quic::QuicSession::stream_map; + protected: // quic::QuicServerSessionBase quic::QuicCryptoServerStreamBase* @@ -64,6 +68,7 @@ class EnvoyQuicServerSession : public quic::QuicServerSessionBase, private: void setUpRequestDecoder(EnvoyQuicStream& stream); + std::unique_ptr quic_connection_; // These callbacks are owned by network filters and quic session should out live // them. Http::ServerConnectionCallbacks* http_connection_callbacks_{nullptr}; diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_server_stream.cc b/source/extensions/quic_listeners/quiche/envoy_quic_server_stream.cc index 35bd63f811355..15060992b520b 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_server_stream.cc +++ b/source/extensions/quic_listeners/quiche/envoy_quic_server_stream.cc @@ -1,9 +1,10 @@ #include "extensions/quic_listeners/quiche/envoy_quic_server_stream.h" +#include #include #include -#include +#include #pragma GCC diagnostic push // QUICHE allows unused parameters. @@ -14,10 +15,12 @@ #include "quiche/quic/core/http/quic_header_list.h" #include "quiche/quic/core/quic_session.h" #include "quiche/spdy/core/spdy_header_block.h" - +#include "quiche/quic/platform/api/quic_mem_slice_span.h" #pragma GCC diagnostic pop #include "extensions/quic_listeners/quiche/envoy_quic_utils.h" +#include "extensions/quic_listeners/quiche/envoy_quic_server_session.h" + #include "common/buffer/buffer_impl.h" #include "common/http/header_map_impl.h" #include "common/common/assert.h" @@ -25,28 +28,84 @@ namespace Envoy { namespace Quic { +EnvoyQuicServerStream::EnvoyQuicServerStream(quic::QuicStreamId id, quic::QuicSpdySession* session, + quic::StreamType type) + : quic::QuicSpdyServerStreamBase(id, session, type), + EnvoyQuicStream( + session->config()->GetInitialStreamFlowControlWindowToSend(), + [this]() { runLowWatermarkCallbacks(); }, [this]() { runHighWatermarkCallbacks(); }) {} + +EnvoyQuicServerStream::EnvoyQuicServerStream(quic::PendingStream* pending, + quic::QuicSpdySession* session, quic::StreamType type) + : quic::QuicSpdyServerStreamBase(pending, session, type), + EnvoyQuicStream( + session->config()->GetInitialStreamFlowControlWindowToSend(), + [this]() { runLowWatermarkCallbacks(); }, [this]() { runHighWatermarkCallbacks(); }) {} + void EnvoyQuicServerStream::encode100ContinueHeaders(const Http::HeaderMap& headers) { ASSERT(headers.Status()->value() == "100"); encodeHeaders(headers, false); } -void EnvoyQuicServerStream::encodeHeaders(const Http::HeaderMap& /*headers*/, bool /*end_stream*/) { - NOT_IMPLEMENTED_GCOVR_EXCL_LINE; + +void EnvoyQuicServerStream::encodeHeaders(const Http::HeaderMap& headers, bool end_stream) { + ENVOY_STREAM_LOG(debug, "encodeHeaders (end_stream={}) {}.", *this, end_stream, headers); + WriteHeaders(envoyHeadersToSpdyHeaderBlock(headers), end_stream, nullptr); + local_end_stream_ = end_stream; } -void EnvoyQuicServerStream::encodeData(Buffer::Instance& /*data*/, bool /*end_stream*/) { - NOT_IMPLEMENTED_GCOVR_EXCL_LINE; + +void EnvoyQuicServerStream::encodeData(Buffer::Instance& data, bool end_stream) { + ENVOY_STREAM_LOG(debug, "encodeData (end_stream={}) of {} bytes.", *this, end_stream, + data.length()); + local_end_stream_ = end_stream; + // This is counting not serialized bytes in the send buffer. + uint64_t bytes_to_send_old = BufferedDataBytes(); + // QUIC stream must take all. + WriteBodySlices(quic::QuicMemSliceSpan(quic::QuicMemSliceSpanImpl(data)), end_stream); + ASSERT(data.length() == 0); + + uint64_t bytes_to_send_new = BufferedDataBytes(); + ASSERT(bytes_to_send_old <= bytes_to_send_new); + if (bytes_to_send_new > bytes_to_send_old) { + // If buffered bytes changed, update stream and session's watermark book + // keeping. + sendBufferSimulation().checkHighWatermark(bytes_to_send_new); + dynamic_cast(session())->adjustBytesToSend(bytes_to_send_new - + bytes_to_send_old); + } } -void EnvoyQuicServerStream::encodeTrailers(const Http::HeaderMap& /*trailers*/) { - NOT_IMPLEMENTED_GCOVR_EXCL_LINE; + +void EnvoyQuicServerStream::encodeTrailers(const Http::HeaderMap& trailers) { + ASSERT(!local_end_stream_); + local_end_stream_ = true; + ENVOY_STREAM_LOG(debug, "encodeTrailers: {}.", *this, trailers); + WriteTrailers(envoyHeadersToSpdyHeaderBlock(trailers), nullptr); } + void EnvoyQuicServerStream::encodeMetadata(const Http::MetadataMapVector& /*metadata_map_vector*/) { + // Metadata Frame is not supported in QUIC. NOT_IMPLEMENTED_GCOVR_EXCL_LINE; } -void EnvoyQuicServerStream::resetStream(Http::StreamResetReason /*reason*/) { - NOT_IMPLEMENTED_GCOVR_EXCL_LINE; +void EnvoyQuicServerStream::resetStream(Http::StreamResetReason reason) { + if (local_end_stream_ && !reading_stopped()) { + // This is after 200 early response. Reset with QUIC_STREAM_NO_ERROR instead + // of propagating original reset reason. + StopReading(); + } else { + Reset(envoyResetReasonToQuicRstError(reason)); + } } -void EnvoyQuicServerStream::readDisable(bool /*disable*/) { NOT_IMPLEMENTED_GCOVR_EXCL_LINE; } +void EnvoyQuicServerStream::switchStreamBlockState(bool should_block) { + ASSERT(FinishedReadingHeaders(), + "Upperstream buffer limit is reached before request body is delivered."); + if (should_block) { + sequencer()->SetBlockedUntilFlush(); + } else { + ASSERT(read_disable_counter_ == 0, "readDisable called in btw."); + sequencer()->SetUnblocked(); + } +} void EnvoyQuicServerStream::OnInitialHeadersComplete(bool fin, size_t frame_len, const quic::QuicHeaderList& header_list) { @@ -54,10 +113,18 @@ void EnvoyQuicServerStream::OnInitialHeadersComplete(bool fin, size_t frame_len, ASSERT(decoder() != nullptr); ASSERT(headers_decompressed()); decoder()->decodeHeaders(quicHeadersToEnvoyHeaders(header_list), /*end_stream=*/fin); + if (fin) { + end_stream_decoded_ = true; + } ConsumeHeaderList(); } void EnvoyQuicServerStream::OnBodyAvailable() { + ASSERT(FinishedReadingHeaders()); + ASSERT(read_disable_counter_ == 0); + ASSERT(!in_encode_data_callstack_); + in_encode_data_callstack_ = true; + Buffer::InstancePtr buffer = std::make_unique(); // TODO(danzh): check Envoy per stream buffer limit. // Currently read out all the data. @@ -77,18 +144,34 @@ void EnvoyQuicServerStream::OnBodyAvailable() { // True if no trailer and FIN read. bool finished_reading = IsDoneReading(); - // If this is the last stream data, set end_stream if there is no - // trailers. - ASSERT(decoder() != nullptr); - decoder()->decodeData(*buffer, finished_reading); - if (!quic::VersionUsesQpack(transport_version()) && sequencer()->IsClosed() && - !FinishedReadingTrailers()) { + bool empty_payload_with_fin = buffer->length() == 0 && finished_reading; + if (!empty_payload_with_fin || !end_stream_decoded_) { + ASSERT(decoder() != nullptr); + decoder()->decodeData(*buffer, finished_reading); + if (finished_reading) { + end_stream_decoded_ = true; + } + } + + if (!sequencer()->IsClosed()) { + in_encode_data_callstack_ = false; + if (read_disable_counter_ > 0) { + // If readDisable() was ever called during decodeData() and it meant to disable + // reading from downstream, the call must have been deferred. Call it now. + switchStreamBlockState(true); + } + return; + } + + if (!quic::VersionUsesQpack(transport_version()) && !FinishedReadingTrailers()) { // For Google QUIC implementation, trailers may arrived earlier and wait to // be consumed after reading all the body. Consume it here. // IETF QUIC shouldn't reach here because trailers are sent on same stream. decoder()->decodeTrailers(spdyHeaderBlockToEnvoyHeaders(received_trailers())); MarkTrailersConsumed(); } + OnFinRead(); + in_encode_data_callstack_ = false; } void EnvoyQuicServerStream::OnTrailingHeadersComplete(bool fin, size_t frame_len, @@ -107,25 +190,33 @@ void EnvoyQuicServerStream::OnTrailingHeadersComplete(bool fin, size_t frame_len void EnvoyQuicServerStream::OnStreamReset(const quic::QuicRstStreamFrame& frame) { quic::QuicSpdyServerStreamBase::OnStreamReset(frame); - Http::StreamResetReason reason; - if (frame.error_code == quic::QUIC_REFUSED_STREAM) { - reason = Http::StreamResetReason::RemoteRefusedStreamReset; - } else { - reason = Http::StreamResetReason::RemoteReset; - } - runResetCallbacks(reason); + runResetCallbacks(quicRstErrorToEnvoyResetReason(frame.error_code)); } void EnvoyQuicServerStream::OnConnectionClosed(quic::QuicErrorCode error, quic::ConnectionCloseSource source) { quic::QuicSpdyServerStreamBase::OnConnectionClosed(error, source); - Http::StreamResetReason reason; - if (error == quic::QUIC_NO_ERROR) { - reason = Http::StreamResetReason::ConnectionTermination; - } else { - reason = Http::StreamResetReason::ConnectionFailure; + runResetCallbacks(quicErrorCodeToEnvoyResetReason(error)); +} + +void EnvoyQuicServerStream::OnCanWrite() { + uint64_t buffered_data_old = BufferedDataBytes(); + quic::QuicSpdyServerStreamBase::OnCanWrite(); + uint64_t buffered_data_new = BufferedDataBytes(); + // As long as OnCanWriteNewData() is no-op, data to sent in buffer shouldn't + // increase. + ASSERT(buffered_data_new <= buffered_data_old); + if (buffered_data_new < buffered_data_old) { + sendBufferSimulation().checkLowWatermark(buffered_data_new); + dynamic_cast(session())->adjustBytesToSend(buffered_data_new - + buffered_data_old); } - runResetCallbacks(reason); +} + +uint32_t EnvoyQuicServerStream::streamId() { return id(); } + +Network::Connection* EnvoyQuicServerStream::connection() { + return dynamic_cast(session()); } } // namespace Quic diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_server_stream.h b/source/extensions/quic_listeners/quiche/envoy_quic_server_stream.h index 047970f4fdbed..e38317907d8ec 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_server_stream.h +++ b/source/extensions/quic_listeners/quiche/envoy_quic_server_stream.h @@ -18,11 +18,10 @@ namespace Quic { class EnvoyQuicServerStream : public quic::QuicSpdyServerStreamBase, public EnvoyQuicStream { public: EnvoyQuicServerStream(quic::QuicStreamId id, quic::QuicSpdySession* session, - quic::StreamType type) - : quic::QuicSpdyServerStreamBase(id, session, type) {} + quic::StreamType type); + EnvoyQuicServerStream(quic::PendingStream* pending, quic::QuicSpdySession* session, - quic::StreamType type) - : quic::QuicSpdyServerStreamBase(pending, session, type) {} + quic::StreamType type); // Http::StreamEncoder void encode100ContinueHeaders(const Http::HeaderMap& headers) override; @@ -33,14 +32,19 @@ class EnvoyQuicServerStream : public quic::QuicSpdyServerStreamBase, public Envo // Http::Stream void resetStream(Http::StreamResetReason reason) override; - void readDisable(bool disable) override; // quic::QuicSpdyStream void OnBodyAvailable() override; void OnStreamReset(const quic::QuicRstStreamFrame& frame) override; + void OnCanWrite() override; // quic::QuicServerSessionBase void OnConnectionClosed(quic::QuicErrorCode error, quic::ConnectionCloseSource source) override; protected: + // EnvoyQuicStream + void switchStreamBlockState(bool should_block) override; + uint32_t streamId() override; + Network::Connection* connection() override; + // quic::QuicSpdyStream void OnInitialHeadersComplete(bool fin, size_t frame_len, const quic::QuicHeaderList& header_list) override; diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_simulated_watermark_buffer.h b/source/extensions/quic_listeners/quiche/envoy_quic_simulated_watermark_buffer.h new file mode 100644 index 0000000000000..577b1f91abbd7 --- /dev/null +++ b/source/extensions/quic_listeners/quiche/envoy_quic_simulated_watermark_buffer.h @@ -0,0 +1,62 @@ +#pragma once + +#include + +#include "spdlog/spdlog.h" + +namespace Envoy { +namespace Quic { + +// A class, together with a stand alone buffer, used to achieve the purpose of WatermarkBuffer. +// Itself doesn't have buffer or do bookeeping of buffered bytes. But provided buffered_bytes, it +// re-acts upon crossing high/low watermarks. +class EnvoyQuicSimulatedWatermarkBuffer { +public: + EnvoyQuicSimulatedWatermarkBuffer(uint32_t low_watermark, uint32_t high_watermark, + std::function below_low_watermark, + std::function above_high_watermark, + spdlog::logger& logger) + : low_watermark_(low_watermark), high_watermark_(high_watermark), + below_low_watermark_(std::move(below_low_watermark)), + above_high_watermark_(std::move(above_high_watermark)), logger_(logger) { + ASSERT(high_watermark == 0 && low_watermark == 0 || high_watermark_ > low_watermark_); + } + + void checkHighWatermark(uint32_t bytes_buffered) { + if (high_watermark_ > 0 && !is_above_high_watermark_ && bytes_buffered > high_watermark_) { + // Just exceeds high watermark. + ENVOY_LOG_TO_LOGGER(logger_, debug, "Buffered {} bytes, cross high watermark {}", + bytes_buffered, high_watermark_); + is_above_high_watermark_ = true; + is_below_low_watermark_ = false; + above_high_watermark_(); + } + } + + void checkLowWatermark(uint32_t bytes_buffered) { + if (low_watermark_ > 0 && !is_below_low_watermark_ && bytes_buffered < low_watermark_) { + // Just cross low watermark. + ENVOY_LOG_TO_LOGGER(logger_, debug, "Buffered {} bytes, cross low watermark {}", + bytes_buffered, low_watermark_); + is_below_low_watermark_ = true; + is_above_high_watermark_ = false; + below_low_watermark_(); + } + } + + bool isAboveHighWatermark() const { return is_above_high_watermark_; } + + bool isBelowLowWatermark() const { return is_below_low_watermark_; } + +private: + uint32_t low_watermark_{0}; + bool is_below_low_watermark_{true}; + uint32_t high_watermark_{0}; + bool is_above_high_watermark_{false}; + std::function below_low_watermark_; + std::function above_high_watermark_; + spdlog::logger& logger_; +}; + +} // namespace Quic +} // namespace Envoy diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_stream.h b/source/extensions/quic_listeners/quiche/envoy_quic_stream.h index a6126224cc688..919b9818b0f0d 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_stream.h +++ b/source/extensions/quic_listeners/quiche/envoy_quic_stream.h @@ -1,41 +1,85 @@ #pragma once +#include "envoy/event/dispatcher.h" #include "envoy/http/codec.h" #include "common/http/codec_helper.h" +#include "extensions/quic_listeners/quiche/envoy_quic_simulated_watermark_buffer.h" + namespace Envoy { namespace Quic { // Base class for EnvoyQuicServer|ClientStream. class EnvoyQuicStream : public Http::StreamEncoder, public Http::Stream, - public Http::StreamCallbackHelper { + public Http::StreamCallbackHelper, + protected Logger::Loggable { public: + EnvoyQuicStream(uint32_t buffer_limit, std::function below_low_watermark, + std::function above_high_watermark) + : send_buffer_simulation_(buffer_limit / 2, buffer_limit, std::move(below_low_watermark), + std::move(above_high_watermark), ENVOY_LOGGER()) {} + // Http::StreamEncoder Stream& getStream() override { return *this; } // Http::Stream + void readDisable(bool disable) override { + bool status_changed{false}; + if (disable) { + ++read_disable_counter_; + ASSERT(read_disable_counter_ == 1); + if (read_disable_counter_ == 1) { + status_changed = true; + } + } else { + ASSERT(read_disable_counter_ > 0); + --read_disable_counter_; + if (read_disable_counter_ == 0) { + status_changed = true; + } + } + + if (status_changed && !in_encode_data_callstack_) { + switchStreamBlockState(disable); + } + } + void addCallbacks(Http::StreamCallbacks& callbacks) override { ASSERT(!local_end_stream_); addCallbacks_(callbacks); } void removeCallbacks(Http::StreamCallbacks& callbacks) override { removeCallbacks_(callbacks); } - uint32_t bufferLimit() override { NOT_IMPLEMENTED_GCOVR_EXCL_LINE; } + uint32_t bufferLimit() override { return quic::kStreamReceiveWindowLimit; } // Needs to be called during quic stream creation before the stream receives // any headers and data. void setDecoder(Http::StreamDecoder& decoder) { decoder_ = &decoder; } protected: + virtual void switchStreamBlockState(bool should_block) PURE; + + // Needed for ENVOY_STREAM_LOG. + virtual uint32_t streamId() PURE; + virtual Network::Connection* connection() PURE; + Http::StreamDecoder* decoder() { ASSERT(decoder_ != nullptr); return decoder_; } + EnvoyQuicSimulatedWatermarkBuffer& sendBufferSimulation() { return send_buffer_simulation_; } + bool end_stream_decoded_{false}; + int32_t read_disable_counter_{0}; + // If true, switchStreamBlockState() should be deferred till this variable + // becomes false. + bool in_encode_data_callstack_{false}; + private: // Not owned. Http::StreamDecoder* decoder_{nullptr}; + EnvoyQuicSimulatedWatermarkBuffer send_buffer_simulation_; }; } // namespace Quic diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_utils.cc b/source/extensions/quic_listeners/quiche/envoy_quic_utils.cc index 33e0c43fc035b..6be73faef9546 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_utils.cc +++ b/source/extensions/quic_listeners/quiche/envoy_quic_utils.cc @@ -62,5 +62,50 @@ Http::HeaderMapImplPtr spdyHeaderBlockToEnvoyHeaders(const spdy::SpdyHeaderBlock return headers; } +spdy::SpdyHeaderBlock envoyHeadersToSpdyHeaderBlock(const Http::HeaderMap& headers) { + spdy::SpdyHeaderBlock header_block; + headers.iterate( + [](const Http::HeaderEntry& header, void* context) -> Http::HeaderMap::Iterate { + auto spdy_headers = static_cast(context); + // The key-value pairs are copied. + spdy_headers->insert({header.key().getStringView(), header.value().getStringView()}); + return Http::HeaderMap::Iterate::Continue; + }, + &header_block); + return header_block; +} + +quic::QuicRstStreamErrorCode envoyResetReasonToQuicRstError(Http::StreamResetReason reason) { + switch (reason) { + case Http::StreamResetReason::LocalRefusedStreamReset: + return quic::QUIC_REFUSED_STREAM; + case Http::StreamResetReason::ConnectionFailure: + return quic::QUIC_STREAM_CONNECTION_ERROR; + case Http::StreamResetReason::LocalReset: + return quic::QUIC_STREAM_CANCELLED; + case Http::StreamResetReason::ConnectionTermination: + return quic::QUIC_STREAM_NO_ERROR; + default: + return quic::QUIC_BAD_APPLICATION_PAYLOAD; + } +} + +Http::StreamResetReason quicRstErrorToEnvoyResetReason(quic::QuicRstStreamErrorCode rst_err) { + switch (rst_err) { + case quic::QUIC_REFUSED_STREAM: + return Http::StreamResetReason::RemoteRefusedStreamReset; + default: + return Http::StreamResetReason::RemoteReset; + } +} + +Http::StreamResetReason quicErrorCodeToEnvoyResetReason(quic::QuicErrorCode error) { + if (error == quic::QUIC_NO_ERROR) { + return Http::StreamResetReason::ConnectionTermination; + } else { + return Http::StreamResetReason::ConnectionFailure; + } +} + } // namespace Quic } // namespace Envoy diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_utils.h b/source/extensions/quic_listeners/quiche/envoy_quic_utils.h index 54b1bf07f6036..6505b22a66e28 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_utils.h +++ b/source/extensions/quic_listeners/quiche/envoy_quic_utils.h @@ -25,5 +25,16 @@ Http::HeaderMapImplPtr quicHeadersToEnvoyHeaders(const quic::QuicHeaderList& hea Http::HeaderMapImplPtr spdyHeaderBlockToEnvoyHeaders(const spdy::SpdyHeaderBlock& header_block); +spdy::SpdyHeaderBlock envoyHeadersToSpdyHeaderBlock(const Http::HeaderMap& headers); + +// Called when Envoy wants to reset the underlying QUIC stream. +quic::QuicRstStreamErrorCode envoyResetReasonToQuicRstError(Http::StreamResetReason reason); + +// Called when a RST_STREAM frame is received. +Http::StreamResetReason quicRstErrorToEnvoyResetReason(quic::QuicRstStreamErrorCode rst_err); + +// Called when underlying QUIC connection is closed either locally or by peer. +Http::StreamResetReason quicErrorCodeToEnvoyResetReason(quic::QuicErrorCode error); + } // namespace Quic } // namespace Envoy diff --git a/source/extensions/quic_listeners/quiche/quic_filter_manager_connection_impl.cc b/source/extensions/quic_listeners/quiche/quic_filter_manager_connection_impl.cc index edfe62051d3cf..4baaed3a2c8dd 100644 --- a/source/extensions/quic_listeners/quiche/quic_filter_manager_connection_impl.cc +++ b/source/extensions/quic_listeners/quiche/quic_filter_manager_connection_impl.cc @@ -5,14 +5,17 @@ namespace Envoy { namespace Quic { -QuicFilterManagerConnectionImpl::QuicFilterManagerConnectionImpl( - std::unique_ptr connection, Event::Dispatcher& dispatcher) - : quic_connection_(std::move(connection)), filter_manager_(*this), dispatcher_(dispatcher), +QuicFilterManagerConnectionImpl::QuicFilterManagerConnectionImpl(EnvoyQuicConnection& connection, + Event::Dispatcher& dispatcher, + uint32_t send_buffer_limit) + : quic_connection_(&connection), dispatcher_(dispatcher), filter_manager_(*this), // QUIC connection id can be 18 bytes. It's easier to use hash value instead // of trying to map it into a 64-bit space. - stream_info_(dispatcher.timeSource()), id_(quic_connection_->connection_id().Hash()) { - // TODO(danzh): Use QUIC specific enum value. - stream_info_.protocol(Http::Protocol::Http2); + stream_info_(dispatcher.timeSource()), id_(quic_connection_->connection_id().Hash()), + write_buffer_watermark_simulation_( + send_buffer_limit / 2, send_buffer_limit, [this]() { onSendBufferLowWatermark(); }, + [this]() { onSendBufferHighWatermark(); }, ENVOY_LOGGER()) { + stream_info_.protocol(Http::Protocol::Http3); } void QuicFilterManagerConnectionImpl::addWriteFilter(Network::WriteFilterSharedPtr filter) { @@ -42,16 +45,26 @@ void QuicFilterManagerConnectionImpl::enableHalfClose(bool enabled) { void QuicFilterManagerConnectionImpl::setBufferLimits(uint32_t /*limit*/) { // TODO(danzh): add interface to quic for connection level buffer throttling. // Currently read buffer is capped by connection level flow control. And - // write buffer is not capped. + // write buffer limit is set during construction. Change buffer limit during + // the life time of connection is not supported. NOT_REACHED_GCOVR_EXCL_LINE; } +bool QuicFilterManagerConnectionImpl::aboveHighWatermark() const { + return write_buffer_watermark_simulation_.isAboveHighWatermark(); +} + void QuicFilterManagerConnectionImpl::close(Network::ConnectionCloseType type) { if (type != Network::ConnectionCloseType::NoFlush) { // TODO(danzh): Implement FlushWrite and FlushWriteAndDelay mode. } + if (quic_connection_ == nullptr) { + // Already detached from quic connection. + return; + } quic_connection_->CloseConnection(quic::QUIC_NO_ERROR, "Closed by application", quic::ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET); + quic_connection_ = nullptr; } void QuicFilterManagerConnectionImpl::setDelayedCloseTimeout(std::chrono::milliseconds timeout) { @@ -96,14 +109,22 @@ void QuicFilterManagerConnectionImpl::rawWrite(Buffer::Instance& /*data*/, bool NOT_REACHED_GCOVR_EXCL_LINE; } +void QuicFilterManagerConnectionImpl::adjustBytesToSend(int64_t delta) { + bytes_to_send_ += delta; + write_buffer_watermark_simulation_.checkHighWatermark(bytes_to_send_); + write_buffer_watermark_simulation_.checkLowWatermark(bytes_to_send_); +} + void QuicFilterManagerConnectionImpl::onConnectionCloseEvent( const quic::QuicConnectionCloseFrame& frame, quic::ConnectionCloseSource source) { - // Tell network callbacks about connection close. - raiseEvent(source == quic::ConnectionCloseSource::FROM_PEER - ? Network::ConnectionEvent::RemoteClose - : Network::ConnectionEvent::LocalClose); transport_failure_reason_ = absl::StrCat(quic::QuicErrorCodeToString(frame.quic_error_code), " with details: ", frame.error_details); + if (quic_connection_ != nullptr) { + // Tell network callbacks about connection close if not detached yet. + raiseEvent(source == quic::ConnectionCloseSource::FROM_PEER + ? Network::ConnectionEvent::RemoteClose + : Network::ConnectionEvent::LocalClose); + } } void QuicFilterManagerConnectionImpl::raiseEvent(Network::ConnectionEvent event) { @@ -112,5 +133,19 @@ void QuicFilterManagerConnectionImpl::raiseEvent(Network::ConnectionEvent event) } } +void QuicFilterManagerConnectionImpl::onSendBufferHighWatermark() { + ENVOY_CONN_LOG(trace, "onSendBufferHighWatermark", *this); + for (auto callback : network_connection_callbacks_) { + callback->onAboveWriteBufferHighWatermark(); + } +} + +void QuicFilterManagerConnectionImpl::onSendBufferLowWatermark() { + ENVOY_CONN_LOG(trace, "onSendBufferLowWatermark", *this); + for (auto callback : network_connection_callbacks_) { + callback->onBelowWriteBufferLowWatermark(); + } +} + } // namespace Quic } // namespace Envoy diff --git a/source/extensions/quic_listeners/quiche/quic_filter_manager_connection_impl.h b/source/extensions/quic_listeners/quiche/quic_filter_manager_connection_impl.h index fc8f57df6a039..a72364d334e53 100644 --- a/source/extensions/quic_listeners/quiche/quic_filter_manager_connection_impl.h +++ b/source/extensions/quic_listeners/quiche/quic_filter_manager_connection_impl.h @@ -9,6 +9,7 @@ #include "common/stream_info/stream_info_impl.h" #include "extensions/quic_listeners/quiche/envoy_quic_connection.h" +#include "extensions/quic_listeners/quiche/envoy_quic_simulated_watermark_buffer.h" namespace Envoy { namespace Quic { @@ -17,8 +18,8 @@ namespace Quic { class QuicFilterManagerConnectionImpl : public Network::FilterManagerConnection, protected Logger::Loggable { public: - QuicFilterManagerConnectionImpl(std::unique_ptr connection, - Event::Dispatcher& dispatcher); + QuicFilterManagerConnectionImpl(EnvoyQuicConnection& connection, Event::Dispatcher& dispatcher, + uint32_t send_buffer_limit); // Network::FilterManager // Overridden to delegate calls to filter_manager_. @@ -60,8 +61,10 @@ class QuicFilterManagerConnectionImpl : public Network::FilterManagerConnection, } Ssl::ConnectionInfoConstSharedPtr ssl() const override; Network::Connection::State state() const override { - return quic_connection_->connected() ? Network::Connection::State::Open - : Network::Connection::State::Closed; + if (quic_connection_ != nullptr && quic_connection_->connected()) { + return Network::Connection::State::Open; + } + return Network::Connection::State::Closed; } void write(Buffer::Instance& /*data*/, bool /*end_stream*/) override { // All writes should be handled by Quic internally. @@ -76,11 +79,8 @@ class QuicFilterManagerConnectionImpl : public Network::FilterManagerConnection, // SO_ORIGINAL_DST not supported by QUIC. NOT_REACHED_GCOVR_EXCL_LINE; } - bool aboveHighWatermark() const override { - // TODO(danzh) Aggregate the write buffer usage cross all the streams and - // add an upper limit for this connection. - NOT_IMPLEMENTED_GCOVR_EXCL_LINE; - } + bool aboveHighWatermark() const override; + const Network::ConnectionSocket::OptionsSharedPtr& socketOptions() const override; StreamInfo::StreamInfo& streamInfo() override { return stream_info_; } const StreamInfo::StreamInfo& streamInfo() const override { return stream_info_; } @@ -97,6 +97,8 @@ class QuicFilterManagerConnectionImpl : public Network::FilterManagerConnection, // Network::WriteBufferSource Network::StreamBuffer getWriteBuffer() override { NOT_REACHED_GCOVR_EXCL_LINE; } + void adjustBytesToSend(int64_t delta); + protected: // Propagate connection close to network_connection_callbacks_. void onConnectionCloseEvent(const quic::QuicConnectionCloseFrame& frame, @@ -104,23 +106,31 @@ class QuicFilterManagerConnectionImpl : public Network::FilterManagerConnection, void raiseEvent(Network::ConnectionEvent event); - std::unique_ptr quic_connection_; + EnvoyQuicConnection* quic_connection_{nullptr}; // TODO(danzh): populate stats. std::unique_ptr stats_; + Event::Dispatcher& dispatcher_; private: + // Called when aggregated buffered bytes across all the streams exceeds high watermark. + void onSendBufferHighWatermark(); + // Called when aggregated buffered bytes across all the streams declines to low watermark. + void onSendBufferLowWatermark(); + // Currently ConnectionManagerImpl is the one and only filter. If more network // filters are added, ConnectionManagerImpl should always be the last one. // Its onRead() is only called once to trigger ReadFilter::onNewConnection() // and the rest incoming data bypasses these filters. Network::FilterManagerImpl filter_manager_; - Event::Dispatcher& dispatcher_; + StreamInfo::StreamInfoImpl stream_info_; // These callbacks are owned by network filters and quic session should out live // them. std::list network_connection_callbacks_; std::string transport_failure_reason_; const uint64_t id_; + uint32_t bytes_to_send_{0}; + EnvoyQuicSimulatedWatermarkBuffer write_buffer_watermark_simulation_; }; } // namespace Quic diff --git a/test/config/utility.cc b/test/config/utility.cc index 2cd563e33e62b..d98be4ec59524 100644 --- a/test/config/utility.cc +++ b/test/config/utility.cc @@ -113,6 +113,37 @@ const std::string ConfigHelper::HTTP_PROXY_CONFIG = BASE_CONFIG + R"EOF( name: route_config_0 )EOF"; +const std::string ConfigHelper::QUIC_HTTP_PROXY_CONFIG = BASE_UDP_LISTENER_CONFIG + R"EOF( + filter_chains: + transport_socket: + name: quic + filters: + name: envoy.http_connection_manager + config: + stat_prefix: config_test + http_filters: + name: envoy.router + codec_type: HTTP3 + access_log: + name: envoy.file_access_log + filter: + not_health_check_filter: {} + config: + path: /dev/null + route_config: + virtual_hosts: + name: integration + routes: + route: + cluster: cluster_0 + match: + prefix: "/" + domains: "*" + name: route_config_0 + udp_listener_config: + udp_listener_name: "quiche_quic_listener" +)EOF"; + const std::string ConfigHelper::DEFAULT_BUFFER_FILTER = R"EOF( name: envoy.buffer diff --git a/test/config/utility.h b/test/config/utility.h index aa03d8583a6f3..ada7f71906f51 100644 --- a/test/config/utility.h +++ b/test/config/utility.h @@ -78,7 +78,8 @@ class ConfigHelper { static const std::string TCP_PROXY_CONFIG; // A basic configuration for L7 proxying. static const std::string HTTP_PROXY_CONFIG; - + // A basic configuration for L7 proxying with QUIC transport. + static const std::string QUIC_HTTP_PROXY_CONFIG; // A string for a basic buffer filter, which can be used with addFilter() static const std::string DEFAULT_BUFFER_FILTER; // A string for a small buffer filter, which can be used with addFilter() diff --git a/test/extensions/quic_listeners/quiche/BUILD b/test/extensions/quic_listeners/quiche/BUILD index 74659b5829587..1af4834e518ec 100644 --- a/test/extensions/quic_listeners/quiche/BUILD +++ b/test/extensions/quic_listeners/quiche/BUILD @@ -62,7 +62,8 @@ envoy_cc_test( "//source/extensions/quic_listeners/quiche:envoy_quic_alarm_factory_lib", "//source/extensions/quic_listeners/quiche:envoy_quic_connection_helper_lib", "//source/extensions/quic_listeners/quiche:envoy_quic_server_connection_lib", - "//source/extensions/quic_listeners/quiche:envoy_quic_server_stream_lib", + "//source/extensions/quic_listeners/quiche:envoy_quic_server_session_lib", + "//test/mocks/http:http_mocks", "//test/mocks/http:stream_decoder_mock", "//test/mocks/network:network_mocks", "//test/test_common:utility_lib", diff --git a/test/extensions/quic_listeners/quiche/envoy_quic_dispatcher_test.cc b/test/extensions/quic_listeners/quiche/envoy_quic_dispatcher_test.cc index 2d1c44a4ab4f7..d8d0146f0e1ff 100644 --- a/test/extensions/quic_listeners/quiche/envoy_quic_dispatcher_test.cc +++ b/test/extensions/quic_listeners/quiche/envoy_quic_dispatcher_test.cc @@ -84,6 +84,8 @@ class EnvoyQuicDispatcherTest : public testing::TestWithParam read_filter(new Network::MockReadFilter()); Network::MockConnectionCallbacks network_connection_callbacks; + testing::StrictMock read_total; + testing::StrictMock read_current; + testing::StrictMock write_total; + testing::StrictMock write_current; + std::vector filter_factory( {[&](Network::FilterManager& filter_manager) { filter_manager.addReadFilter(read_filter); read_filter->callbacks_->connection().addConnectionCallbacks(network_connection_callbacks); + read_filter->callbacks_->connection().setConnectionStats( + {read_total, read_current, write_total, write_current, nullptr, nullptr}); }}); EXPECT_CALL(filter_chain, networkFilterFactories()).WillOnce(ReturnRef(filter_factory)); EXPECT_CALL(listener_config_, filterChainFactory()); diff --git a/test/extensions/quic_listeners/quiche/envoy_quic_proof_source_test.cc b/test/extensions/quic_listeners/quiche/envoy_quic_proof_source_test.cc index 4737a532f558b..57bdf94e9e1f0 100644 --- a/test/extensions/quic_listeners/quiche/envoy_quic_proof_source_test.cc +++ b/test/extensions/quic_listeners/quiche/envoy_quic_proof_source_test.cc @@ -62,8 +62,8 @@ TEST_F(EnvoyQuicFakeProofSourceTest, TestGetProof) { TEST_F(EnvoyQuicFakeProofSourceTest, TestVerifyProof) { EXPECT_EQ(quic::QUIC_SUCCESS, proof_verifier_.VerifyProof(hostname_, /*port=*/0, server_config_, version_, chlo_hash_, - expected_certs_, "Fake timestamp", expected_signature_, - nullptr, nullptr, nullptr, nullptr)); + expected_certs_, "", expected_signature_, nullptr, nullptr, + nullptr, nullptr)); std::vector wrong_certs{"wrong cert"}; EXPECT_EQ(quic::QUIC_FAILURE, proof_verifier_.VerifyProof(hostname_, /*port=*/0, server_config_, version_, chlo_hash_, diff --git a/test/extensions/quic_listeners/quiche/envoy_quic_server_session_test.cc b/test/extensions/quic_listeners/quiche/envoy_quic_server_session_test.cc index 013c98851be4d..419953ca523f8 100644 --- a/test/extensions/quic_listeners/quiche/envoy_quic_server_session_test.cc +++ b/test/extensions/quic_listeners/quiche/envoy_quic_server_session_test.cc @@ -87,7 +87,8 @@ class EnvoyQuicServerSessionTest : public testing::TestWithParam { envoy_quic_session_(quic_config_, quic_version_, std::unique_ptr(quic_connection_), /*visitor=*/nullptr, &crypto_stream_helper_, &crypto_config_, - &compressed_certs_cache_, *dispatcher_), + &compressed_certs_cache_, *dispatcher_, + /*send_buffer_limit*/ 1024 * 1024), read_filter_(new Network::MockReadFilter()) { EXPECT_EQ(time_system_.systemTime(), envoy_quic_session_.streamInfo().startTime()); EXPECT_EQ(EMPTY_STRING, envoy_quic_session_.nextProtocol()); @@ -102,7 +103,7 @@ class EnvoyQuicServerSessionTest : public testing::TestWithParam { bool installReadFilter() { // Setup read filter. envoy_quic_session_.addReadFilter(read_filter_); - EXPECT_EQ(Http::Protocol::Http2, + EXPECT_EQ(Http::Protocol::Http3, read_filter_->callbacks_->connection().streamInfo().protocol().value()); EXPECT_EQ(envoy_quic_session_.id(), read_filter_->callbacks_->connection().id()); EXPECT_EQ(&envoy_quic_session_, &read_filter_->callbacks_->connection()); @@ -114,7 +115,7 @@ class EnvoyQuicServerSessionTest : public testing::TestWithParam { // Create ServerConnection instance and setup callbacks for it. http_connection_ = std::make_unique(envoy_quic_session_, http_connection_callbacks_); - EXPECT_EQ(Http::Protocol::Http2, http_connection_->protocol()); + EXPECT_EQ(Http::Protocol::Http3, http_connection_->protocol()); // Stop iteration to avoid calling getRead/WriteBuffer(). return Network::FilterStatus::StopIteration; })); @@ -314,7 +315,6 @@ TEST_P(EnvoyQuicServerSessionTest, ShutdownNotice) { // Not verifying dummy implementation, just to have coverage. EXPECT_DEATH(envoy_quic_session_.enableHalfClose(true), ""); EXPECT_EQ(nullptr, envoy_quic_session_.ssl()); - EXPECT_DEATH(envoy_quic_session_.aboveHighWatermark(), ""); EXPECT_DEATH(envoy_quic_session_.setDelayedCloseTimeout(std::chrono::milliseconds(1)), ""); http_connection_->shutdownNotice(); } @@ -362,6 +362,8 @@ TEST_P(EnvoyQuicServerSessionTest, InitializeFilterChain) { Network::FilterManager& filter_manager) { filter_manager.addReadFilter(read_filter_); read_filter_->callbacks_->connection().addConnectionCallbacks(network_connection_callbacks_); + read_filter_->callbacks_->connection().setConnectionStats( + {read_total_, read_current_, write_total_, write_current_, nullptr, nullptr}); }}; EXPECT_CALL(filter_chain, networkFilterFactories()).WillOnce(ReturnRef(filter_factory)); EXPECT_CALL(*read_filter_, onNewConnection()) diff --git a/test/extensions/quic_listeners/quiche/envoy_quic_server_stream_test.cc b/test/extensions/quic_listeners/quiche/envoy_quic_server_stream_test.cc index b77714985cc17..290096b9c8408 100644 --- a/test/extensions/quic_listeners/quiche/envoy_quic_server_stream_test.cc +++ b/test/extensions/quic_listeners/quiche/envoy_quic_server_stream_test.cc @@ -9,6 +9,7 @@ #include "quiche/quic/core/quic_versions.h" #include "quiche/quic/core/http/quic_server_session_base.h" #include "quiche/quic/test_tools/quic_test_utils.h" +#include "quiche/quic/core/quic_utils.h" #pragma GCC diagnostic pop @@ -18,29 +19,36 @@ #include "common/http/headers.h" #include "test/test_common/utility.h" #include "extensions/quic_listeners/quiche/envoy_quic_alarm_factory.h" +#include "extensions/quic_listeners/quiche/envoy_quic_server_session.h" #include "extensions/quic_listeners/quiche/envoy_quic_server_connection.h" #include "extensions/quic_listeners/quiche/envoy_quic_connection_helper.h" +#include "test/test_common/utility.h" #include "test/mocks/http/stream_decoder.h" +#include "test/mocks/http/mocks.h" #include "test/mocks/network/mocks.h" #include "gmock/gmock.h" #include "gtest/gtest.h" using testing::_; using testing::Invoke; +using testing::Return; namespace Envoy { namespace Quic { -class MockQuicServerSession : public quic::QuicServerSessionBase { +class MockQuicServerSession : public EnvoyQuicServerSession { public: MockQuicServerSession(const quic::QuicConfig& config, const quic::ParsedQuicVersionVector& supported_versions, - quic::QuicConnection* connection, quic::QuicSession::Visitor* visitor, + std::unique_ptr connection, + quic::QuicSession::Visitor* visitor, quic::QuicCryptoServerStream::Helper* helper, const quic::QuicCryptoServerConfig* crypto_config, - quic::QuicCompressedCertsCache* compressed_certs_cache) - : quic::QuicServerSessionBase(config, supported_versions, connection, visitor, helper, - crypto_config, compressed_certs_cache) {} + quic::QuicCompressedCertsCache* compressed_certs_cache, + Event::Dispatcher& dispatcher, uint32_t send_buffer_limit) + : EnvoyQuicServerSession(config, supported_versions, std::move(connection), visitor, helper, + crypto_config, compressed_certs_cache, dispatcher, + send_buffer_limit) {} MOCK_METHOD1(CreateIncomingStream, quic::QuicSpdyStream*(quic::QuicStreamId id)); MOCK_METHOD1(CreateIncomingStream, quic::QuicSpdyStream*(quic::PendingStream* pending)); @@ -49,9 +57,20 @@ class MockQuicServerSession : public quic::QuicServerSessionBase { MOCK_METHOD1(ShouldCreateIncomingStream, bool(quic::QuicStreamId id)); MOCK_METHOD0(ShouldCreateOutgoingBidirectionalStream, bool()); MOCK_METHOD0(ShouldCreateOutgoingUnidirectionalStream, bool()); - MOCK_METHOD2(CreateQuicCryptoServerStream, - quic::QuicCryptoServerStream*(const quic::QuicCryptoServerConfig*, - quic::QuicCompressedCertsCache*)); + MOCK_METHOD1(ShouldYield, bool(quic::QuicStreamId stream_id)); + MOCK_METHOD5(WritevData, + quic::QuicConsumedData(quic::QuicStream* stream, quic::QuicStreamId id, + size_t write_length, quic::QuicStreamOffset offset, + quic::StreamSendingState state)); + + quic::QuicCryptoServerStream* + CreateQuicCryptoServerStream(const quic::QuicCryptoServerConfig* crypto_config, + quic::QuicCompressedCertsCache* compressed_certs_cache) override { + return new quic::QuicCryptoServerStream(crypto_config, compressed_certs_cache, this, + stream_helper()); + } + + using quic::QuicServerSessionBase::ActivateStream; }; class EnvoyQuicServerStreamTest : public testing::TestWithParam { @@ -66,26 +85,45 @@ class EnvoyQuicServerStreamTest : public testing::TestWithParam { listener_stats_({ALL_LISTENER_STATS(POOL_COUNTER(listener_config_.listenerScope()), POOL_GAUGE(listener_config_.listenerScope()), POOL_HISTOGRAM(listener_config_.listenerScope()))}), - quic_connection_(quic::test::TestConnectionId(), - quic::QuicSocketAddress(quic::QuicIpAddress::Any6(), 12345), - connection_helper_, alarm_factory_, &writer_, - /*owns_writer=*/false, {quic_version_}, listener_config_, listener_stats_), - quic_session_(quic_config_, {quic_version_}, &quic_connection_, /*visitor=*/nullptr, + quic_connection_(new EnvoyQuicServerConnection( + quic::test::TestConnectionId(), + quic::QuicSocketAddress(quic::QuicIpAddress::Any6(), 12345), connection_helper_, + alarm_factory_, &writer_, + /*owns_writer=*/false, {quic_version_}, listener_config_, listener_stats_)), + quic_session_(quic_config_, {quic_version_}, + std::unique_ptr(quic_connection_), + /*visitor=*/nullptr, /*helper=*/nullptr, /*crypto_config=*/nullptr, - /*compressed_certs_cache=*/nullptr), + /*compressed_certs_cache=*/nullptr, *dispatcher_, + quic_config_.GetInitialStreamFlowControlWindowToSend()), stream_id_(quic_version_.transport_version == quic::QUIC_VERSION_99 ? 4u : 5u), - quic_stream_(stream_id_, &quic_session_, quic::BIDIRECTIONAL) { + quic_stream_(new EnvoyQuicServerStream(stream_id_, &quic_session_, quic::BIDIRECTIONAL)), + response_headers_{{":status", "200"}} { quic::SetVerbosityLogThreshold(3); - - quic_stream_.setDecoder(stream_decoder_); + quic_stream_->setDecoder(stream_decoder_); + quic_stream_->addCallbacks(stream_callbacks_); + quic_session_.ActivateStream(std::unique_ptr(quic_stream_)); + EXPECT_CALL(quic_session_, ShouldYield(_)).WillRepeatedly(Return(false)); + EXPECT_CALL(quic_session_, WritevData(_, _, _, _, _)) + .WillRepeatedly(Invoke([](quic::QuicStream*, quic::QuicStreamId, size_t write_length, + quic::QuicStreamOffset, quic::StreamSendingState) { + return quic::QuicConsumedData{write_length, true}; + })); + EXPECT_CALL(writer_, WritePacket(_, _, _, _, _)) + .WillRepeatedly(Invoke([](const char*, size_t buf_len, const quic::QuicIpAddress&, + const quic::QuicSocketAddress&, quic::PerPacketOptions*) { + return quic::WriteResult{quic::WRITE_STATUS_OK, static_cast(buf_len)}; + })); } void SetUp() override { - headers_.OnHeaderBlockStart(); - headers_.OnHeader(":authority", host_); - headers_.OnHeader(":method", "GET"); - headers_.OnHeader(":path", "/"); - headers_.OnHeaderBlockEnd(/*uncompressed_header_bytes=*/0, /*compressed_header_bytes=*/0); + quic_session_.Initialize(); + request_headers_.OnHeaderBlockStart(); + request_headers_.OnHeader(":authority", host_); + request_headers_.OnHeader(":method", "GET"); + request_headers_.OnHeader(":path", "/"); + request_headers_.OnHeaderBlockEnd(/*uncompressed_header_bytes=*/0, + /*compressed_header_bytes=*/0); trailers_.OnHeaderBlockStart(); trailers_.OnHeader("key1", "value1"); @@ -96,6 +134,12 @@ class EnvoyQuicServerStreamTest : public testing::TestWithParam { trailers_.OnHeaderBlockEnd(/*uncompressed_header_bytes=*/0, /*compressed_header_bytes=*/0); } + void TearDown() override { + if (quic_connection_->connected()) { + quic_session_.close(Network::ConnectionCloseType::NoFlush); + } + } + protected: Api::ApiPtr api_; Event::DispatcherPtr dispatcher_; @@ -106,12 +150,14 @@ class EnvoyQuicServerStreamTest : public testing::TestWithParam { quic::QuicConfig quic_config_; testing::NiceMock listener_config_; Server::ListenerStats listener_stats_; - EnvoyQuicServerConnection quic_connection_; + EnvoyQuicServerConnection* quic_connection_; MockQuicServerSession quic_session_; quic::QuicStreamId stream_id_; - EnvoyQuicServerStream quic_stream_; + EnvoyQuicServerStream* quic_stream_; Http::MockStreamDecoder stream_decoder_; - quic::QuicHeaderList headers_; + Http::MockStreamCallbacks stream_callbacks_; + quic::QuicHeaderList request_headers_; + Http::TestHeaderMapImpl response_headers_; quic::QuicHeaderList trailers_; std::string host_{"www.abc.com"}; std::string request_body_{"Hello world"}; @@ -120,7 +166,7 @@ class EnvoyQuicServerStreamTest : public testing::TestWithParam { INSTANTIATE_TEST_SUITE_P(EnvoyQuicServerStreamTests, EnvoyQuicServerStreamTest, testing::ValuesIn({true, false})); -TEST_P(EnvoyQuicServerStreamTest, DecodeHeadersAndBody) { +TEST_P(EnvoyQuicServerStreamTest, PostRequestAndResponse) { EXPECT_CALL(stream_decoder_, decodeHeaders_(_, /*end_stream=*/false)) .WillOnce(Invoke([this](const Http::HeaderMapPtr& headers, bool) { EXPECT_EQ(host_, headers->Host()->value().getStringView()); @@ -129,11 +175,12 @@ TEST_P(EnvoyQuicServerStreamTest, DecodeHeadersAndBody) { headers->Method()->value().getStringView()); })); if (quic_version_.transport_version == quic::QUIC_VERSION_99) { - quic_stream_.OnHeadersDecoded(headers_); + quic_stream_->OnHeadersDecoded(request_headers_); } else { - quic_stream_.OnStreamHeaderList(/*fin=*/false, headers_.uncompressed_header_bytes(), headers_); + quic_stream_->OnStreamHeaderList(/*fin=*/false, request_headers_.uncompressed_header_bytes(), + request_headers_); } - EXPECT_TRUE(quic_stream_.FinishedReadingHeaders()); + EXPECT_TRUE(quic_stream_->FinishedReadingHeaders()); EXPECT_CALL(stream_decoder_, decodeData(_, _)) .WillOnce(Invoke([this](Buffer::Instance& buffer, bool finished_reading) { @@ -150,7 +197,9 @@ TEST_P(EnvoyQuicServerStreamTest, DecodeHeadersAndBody) { data = absl::StrCat(data_frame_header, request_body_); } quic::QuicStreamFrame frame(stream_id_, true, 0, data); - quic_stream_.OnStreamFrame(frame); + quic_stream_->OnStreamFrame(frame); + + quic_stream_->encodeHeaders(response_headers_, /*end_stream=*/true); } TEST_P(EnvoyQuicServerStreamTest, DecodeHeadersBodyAndTrailers) { @@ -161,8 +210,9 @@ TEST_P(EnvoyQuicServerStreamTest, DecodeHeadersBodyAndTrailers) { EXPECT_EQ(Http::Headers::get().MethodValues.Get, headers->Method()->value().getStringView()); })); - quic_stream_.OnStreamHeaderList(/*fin=*/false, headers_.uncompressed_header_bytes(), headers_); - EXPECT_TRUE(quic_stream_.FinishedReadingHeaders()); + quic_stream_->OnStreamHeaderList(/*fin=*/false, request_headers_.uncompressed_header_bytes(), + request_headers_); + EXPECT_TRUE(quic_stream_->FinishedReadingHeaders()); std::string data = request_body_; if (quic_version_.transport_version == quic::QUIC_VERSION_99) { @@ -186,7 +236,7 @@ TEST_P(EnvoyQuicServerStreamTest, DecodeHeadersBodyAndTrailers) { EXPECT_FALSE(finished_reading); EXPECT_EQ(0, buffer.length()); })); - quic_stream_.OnStreamFrame(frame); + quic_stream_->OnStreamFrame(frame); EXPECT_CALL(stream_decoder_, decodeTrailers_(_)) .WillOnce(Invoke([](const Http::HeaderMapPtr& headers) { @@ -195,10 +245,12 @@ TEST_P(EnvoyQuicServerStreamTest, DecodeHeadersBodyAndTrailers) { EXPECT_EQ("value1", headers->get(key1)->value().getStringView()); EXPECT_EQ(nullptr, headers->get(key2)); })); - quic_stream_.OnStreamHeaderList(/*fin=*/true, trailers_.uncompressed_header_bytes(), trailers_); + quic_stream_->OnStreamHeaderList(/*fin=*/true, trailers_.uncompressed_header_bytes(), trailers_); + EXPECT_CALL(stream_callbacks_, onResetStream(_, _)); } TEST_P(EnvoyQuicServerStreamTest, OutOfOrderTrailers) { + EXPECT_CALL(stream_callbacks_, onResetStream(_, _)); if (quic::VersionUsesQpack(quic_version_.transport_version)) { return; } @@ -209,11 +261,12 @@ TEST_P(EnvoyQuicServerStreamTest, OutOfOrderTrailers) { EXPECT_EQ(Http::Headers::get().MethodValues.Get, headers->Method()->value().getStringView()); })); - quic_stream_.OnStreamHeaderList(/*fin=*/false, headers_.uncompressed_header_bytes(), headers_); - EXPECT_TRUE(quic_stream_.FinishedReadingHeaders()); + quic_stream_->OnStreamHeaderList(/*fin=*/false, request_headers_.uncompressed_header_bytes(), + request_headers_); + EXPECT_TRUE(quic_stream_->FinishedReadingHeaders()); // Trailer should be delivered to HCM later after body arrives. - quic_stream_.OnStreamHeaderList(/*fin=*/true, trailers_.uncompressed_header_bytes(), trailers_); + quic_stream_->OnStreamHeaderList(/*fin=*/true, trailers_.uncompressed_header_bytes(), trailers_); std::string data = request_body_; if (quic_version_.transport_version == quic::QUIC_VERSION_99) { @@ -245,7 +298,91 @@ TEST_P(EnvoyQuicServerStreamTest, OutOfOrderTrailers) { EXPECT_EQ("value1", headers->get(key1)->value().getStringView()); EXPECT_EQ(nullptr, headers->get(key2)); })); - quic_stream_.OnStreamFrame(frame); + quic_stream_->OnStreamFrame(frame); +} + +TEST_P(EnvoyQuicServerStreamTest, WatermarkSendBuffer) { + EXPECT_CALL(stream_decoder_, decodeHeaders_(_, /*end_stream=*/false)) + .WillOnce(Invoke([this](const Http::HeaderMapPtr& headers, bool) { + EXPECT_EQ(host_, headers->Host()->value().getStringView()); + EXPECT_EQ("/", headers->Path()->value().getStringView()); + EXPECT_EQ(Http::Headers::get().MethodValues.Get, + headers->Method()->value().getStringView()); + })); + if (quic_version_.transport_version == quic::QUIC_VERSION_99) { + quic_stream_->OnHeadersDecoded(request_headers_); + } else { + quic_stream_->OnStreamHeaderList(/*fin=*/false, request_headers_.uncompressed_header_bytes(), + request_headers_); + } + EXPECT_TRUE(quic_stream_->FinishedReadingHeaders()); + + EXPECT_CALL(stream_decoder_, decodeData(_, _)) + .WillOnce(Invoke([this](Buffer::Instance& buffer, bool finished_reading) { + EXPECT_EQ(request_body_, buffer.toString()); + EXPECT_TRUE(finished_reading); + })); + std::string data = request_body_; + if (quic_version_.transport_version == quic::QUIC_VERSION_99) { + std::unique_ptr data_buffer; + quic::HttpEncoder encoder; + quic::QuicByteCount data_frame_header_length = + encoder.SerializeDataFrameHeader(request_body_.length(), &data_buffer); + quic::QuicStringPiece data_frame_header(data_buffer.get(), data_frame_header_length); + data = absl::StrCat(data_frame_header, request_body_); + } + quic::QuicStreamFrame frame(stream_id_, true, 0, data); + quic_stream_->OnStreamFrame(frame); + + response_headers_.addCopy(":content-length", "32770"); // 32KB + 2 byte + quic_stream_->encodeHeaders(response_headers_, /*end_stream=*/false); + // encode 32kB response body. first 16KB shoudl be written out right away. The + // rest should be buffered. The high watermark is 16KB, so this call should + // make the send buffer reach its high watermark. + std::string response(32 * 1024 + 1, 'a'); + Buffer::OwnedImpl buffer(response); + EXPECT_CALL(stream_callbacks_, onAboveWriteBufferHighWatermark()); + quic_stream_->encodeData(buffer, false); + + EXPECT_EQ(0u, buffer.length()); + EXPECT_TRUE(quic_stream_->flow_controller()->IsBlocked()); + // Bump connection flow control window large enough not to cause connection + // level flow control blocked. + quic::QuicWindowUpdateFrame window_update( + quic::kInvalidControlFrameId, + quic::QuicUtils::GetInvalidStreamId(quic_version_.transport_version), 1024 * 1024); + quic_session_.OnWindowUpdateFrame(window_update); + + // Receive a WINDOW_UPDATE frame not large enough to drain half of the send + // buffer. + quic::QuicWindowUpdateFrame window_update1(quic::kInvalidControlFrameId, quic_stream_->id(), + 16 * 1024 + 8 * 1024); + quic_stream_->OnWindowUpdateFrame(window_update1); + EXPECT_FALSE(quic_stream_->flow_controller()->IsBlocked()); + quic_session_.OnCanWrite(); + EXPECT_TRUE(quic_stream_->flow_controller()->IsBlocked()); + + // Receive another WINDOW_UPDATE frame to drain the send buffer till below low + // watermark. + quic::QuicWindowUpdateFrame window_update2(quic::kInvalidControlFrameId, quic_stream_->id(), + 16 * 1024 + 8 * 1024 + 1024); + quic_stream_->OnWindowUpdateFrame(window_update2); + EXPECT_FALSE(quic_stream_->flow_controller()->IsBlocked()); + EXPECT_CALL(stream_callbacks_, onBelowWriteBufferLowWatermark()).WillOnce(Invoke([this]() { + std::string rest_response(1, 'a'); + Buffer::OwnedImpl buffer(rest_response); + quic_stream_->encodeData(buffer, true); + })); + quic_session_.OnCanWrite(); + EXPECT_TRUE(quic_stream_->flow_controller()->IsBlocked()); + + quic::QuicWindowUpdateFrame window_update3(quic::kInvalidControlFrameId, quic_stream_->id(), + 32 * 1024 + 1024); + quic_stream_->OnWindowUpdateFrame(window_update3); + quic_session_.OnCanWrite(); + + EXPECT_TRUE(quic_stream_->local_end_stream_); + EXPECT_TRUE(quic_stream_->write_side_closed()); } } // namespace Quic diff --git a/test/extensions/quic_listeners/quiche/integration/BUILD b/test/extensions/quic_listeners/quiche/integration/BUILD new file mode 100644 index 0000000000000..4e2cde2d9bd8d --- /dev/null +++ b/test/extensions/quic_listeners/quiche/integration/BUILD @@ -0,0 +1,29 @@ +licenses(["notice"]) # Apache 2 + +load( + "//bazel:envoy_build_system.bzl", + "envoy_cc_fuzz_test", + "envoy_cc_test", + "envoy_cc_test_binary", + "envoy_cc_test_library", + "envoy_package", + "envoy_proto_library", +) + +envoy_package() + +envoy_cc_test( + name = "quic_http_integration_test", + srcs = ["quic_http_integration_test.cc"], + data = ["//test/config/integration/certs"], + tags = ["nofips"], + deps = [ + "//source/extensions/quic_listeners/quiche:active_quic_listener_config_lib", + "//source/extensions/quic_listeners/quiche:envoy_quic_client_connection_lib", + "//source/extensions/quic_listeners/quiche:envoy_quic_client_session_lib", + "//source/extensions/quic_listeners/quiche:envoy_quic_connection_helper_lib", + "//source/extensions/quic_listeners/quiche:envoy_quic_proof_verifier_lib", + "//source/extensions/quic_listeners/quiche:quic_transport_socket_factory_lib", + "//test/integration:http_integration_lib", + ], +) diff --git a/test/extensions/quic_listeners/quiche/integration/quic_http_integration_test.cc b/test/extensions/quic_listeners/quiche/integration/quic_http_integration_test.cc new file mode 100644 index 0000000000000..34a0c8f450b9d --- /dev/null +++ b/test/extensions/quic_listeners/quiche/integration/quic_http_integration_test.cc @@ -0,0 +1,183 @@ +#include "test/config/utility.h" +#include "test/integration/http_integration.h" +#include "test/test_common/utility.h" + +#pragma GCC diagnostic push +// QUICHE allows unused parameters. +#pragma GCC diagnostic ignored "-Wunused-parameter" +// QUICHE uses offsetof(). +#pragma GCC diagnostic ignored "-Winvalid-offsetof" + +#include "quiche/quic/core/http/quic_client_push_promise_index.h" +#include "quiche/quic/core/quic_utils.h" + +#pragma GCC diagnostic pop + +#include "extensions/quic_listeners/quiche/envoy_quic_client_session.h" +#include "extensions/quic_listeners/quiche/envoy_quic_client_connection.h" +#include "extensions/quic_listeners/quiche/envoy_quic_fake_proof_verifier.h" +#include "extensions/quic_listeners/quiche/envoy_quic_connection_helper.h" +#include "extensions/quic_listeners/quiche/envoy_quic_alarm_factory.h" +#include "extensions/quic_listeners/quiche/envoy_quic_packet_writer.h" + +namespace Envoy { +namespace Quic { + +class CodecClientCallbacksForTest : public Http::CodecClientCallbacks { +public: + void onStreamDestroy() override {} + + void onStreamReset(Http::StreamResetReason reason) override { + last_stream_reset_reason_ = reason; + } + + Http::StreamResetReason last_stream_reset_reason_{Http::StreamResetReason::LocalReset}; +}; + +class QuicHttpIntegrationTest : public testing::TestWithParam, + public HttpIntegrationTest { +public: + QuicHttpIntegrationTest() + : HttpIntegrationTest(Http::CodecClient::Type::HTTP3, GetParam(), + ConfigHelper::QUIC_HTTP_PROXY_CONFIG), + supported_versions_(quic::CurrentSupportedVersions()), + crypto_config_(std::make_unique()), conn_helper_(*dispatcher_), + alarm_factory_(*dispatcher_, *conn_helper_.GetClock()) { + quic::SetVerbosityLogThreshold(1); + } + + Network::ClientConnectionPtr makeClientConnection(uint32_t port) override { + Network::Address::InstanceConstSharedPtr server_addr = Network::Utility::resolveUrl( + fmt::format("udp://{}:{}", Network::Test::getLoopbackAddressUrlString(version_), port)); + Network::Address::InstanceConstSharedPtr local_addr = + Network::Test::getCanonicalLoopbackAddress(version_); + auto connection = std::make_unique( + getNextServerDesignatedConnectionId(), server_addr, conn_helper_, alarm_factory_, + supported_versions_, local_addr, *dispatcher_, nullptr); + auto session = std::make_unique( + quic_config_, supported_versions_, std::move(connection), server_id_, &crypto_config_, + &push_promise_index_, *dispatcher_, 0); + return session; + } + + // This call may fail because of INVALID_VERSION, because QUIC connection doesn't support + // in-connection version negotiation. + // TODO(#8479) Popagate INVALID_VERSION error to caller and let caller to use server advertised + // version list to create a new connection with mutually supported version and make client codec + // again. + IntegrationCodecClientPtr makeRawHttpConnection(Network::ClientConnectionPtr&& conn) override { + IntegrationCodecClientPtr codec = HttpIntegrationTest::makeRawHttpConnection(std::move(conn)); + ASSERT(!codec->connected()); + dynamic_cast(codec->connection())->cryptoConnect(); + // Wait for finishing handshake with server. + dispatcher_->run(Event::Dispatcher::RunType::Block); + if (codec->disconnected()) { + // Connection may get closed during version negotiation or handshake. + ENVOY_LOG(error, "Fail to connect to server with error: {}", + codec->connection()->transportFailureReason()); + } else { + codec->setCodecClientCallbacks(client_codec_callback_); + } + return codec; + } + + quic::QuicConnectionId getNextServerDesignatedConnectionId() { + quic::QuicCryptoClientConfig::CachedState* cached = crypto_config_.LookupOrCreate(server_id_); + // If the cached state indicates that we should use a server-designated + // connection ID, then return that connection ID. + quic::QuicConnectionId conn_id = cached->has_server_designated_connection_id() + ? cached->GetNextServerDesignatedConnectionId() + : quic::EmptyQuicConnectionId(); + return conn_id.IsEmpty() ? quic::QuicUtils::CreateRandomConnectionId() : conn_id; + } + + void initialize() override { + config_helper_.addConfigModifier([](envoy::config::bootstrap::v2::Bootstrap& bootstrap) { + envoy::api::v2::auth::DownstreamTlsContext tls_context; + ConfigHelper::initializeTls({}, *tls_context.mutable_common_tls_context()); + auto* filter_chain = + bootstrap.mutable_static_resources()->mutable_listeners(0)->mutable_filter_chains(0); + auto* transport_socket = filter_chain->mutable_transport_socket(); + TestUtility::jsonConvert(tls_context, *transport_socket->mutable_config()); + }); + config_helper_.addConfigModifier( + [](envoy::config::filter::network::http_connection_manager::v2::HttpConnectionManager& + hcm) { + hcm.mutable_delayed_close_timeout()->set_nanos(0); + EXPECT_EQ(hcm.codec_type(), envoy::config::filter::network::http_connection_manager::v2:: + HttpConnectionManager::HTTP3); + }); + + HttpIntegrationTest::initialize(); + registerTestServerPorts({"http"}); + } + +protected: + quic::QuicConfig quic_config_; + quic::QuicServerId server_id_; + quic::QuicClientPushPromiseIndex push_promise_index_; + quic::ParsedQuicVersionVector supported_versions_; + quic::QuicCryptoClientConfig crypto_config_; + EnvoyQuicConnectionHelper conn_helper_; + EnvoyQuicAlarmFactory alarm_factory_; + CodecClientCallbacksForTest client_codec_callback_; +}; + +INSTANTIATE_TEST_SUITE_P(IpVersions, QuicHttpIntegrationTest, + testing::ValuesIn(TestEnvironment::getIpVersionsForTest()), + TestUtility::ipTestParamsToString); + +TEST_P(QuicHttpIntegrationTest, GetRequestAndEmptyResponse) { + testRouterHeaderOnlyRequestAndResponse(); +} + +TEST_P(QuicHttpIntegrationTest, GetRequestAndResponseWithBody) { + initialize(); + sendRequestAndVerifyResponse(default_request_headers_, /*request_size=*/0, + default_response_headers_, /*response_size=*/1024, + /*backend_index*/ 0); +} + +TEST_P(QuicHttpIntegrationTest, PostRequestAndResponseWithBody) { + testRouterRequestAndResponseWithBody(1024, 512, false); +} + +TEST_P(QuicHttpIntegrationTest, PostRequestWithBigHeadersAndResponseWithBody) { + testRouterRequestAndResponseWithBody(1024, 512, true); +} + +TEST_P(QuicHttpIntegrationTest, RouterUpstreamDisconnectBeforeRequestcomplete) { + testRouterUpstreamDisconnectBeforeRequestComplete(); +} + +TEST_P(QuicHttpIntegrationTest, RouterUpstreamDisconnectBeforeResponseComplete) { + testRouterUpstreamDisconnectBeforeResponseComplete(); + EXPECT_EQ(Http::StreamResetReason::RemoteReset, client_codec_callback_.last_stream_reset_reason_); +} + +TEST_P(QuicHttpIntegrationTest, RouterDownstreamDisconnectBeforeRequestComplete) { + testRouterDownstreamDisconnectBeforeRequestComplete(); +} + +TEST_P(QuicHttpIntegrationTest, RouterDownstreamDisconnectBeforeResponseComplete) { + testRouterDownstreamDisconnectBeforeResponseComplete(); +} + +TEST_P(QuicHttpIntegrationTest, RouterUpstreamResponseBeforeRequestComplete) { + testRouterUpstreamResponseBeforeRequestComplete(); +} + +TEST_P(QuicHttpIntegrationTest, Retry) { testRetry(); } + +TEST_P(QuicHttpIntegrationTest, UpstreamThrottlingOnGiantResponseBody) { + config_helper_.setBufferLimits(/*upstream_buffer_limit=*/1024, /*downstream_buffer_limit=*/1024); + testRouterRequestAndResponseWithBody(/*request_size=*/512, /*response_size=*/1024 * 1024, false); +} + +TEST_P(QuicHttpIntegrationTest, DownstreamThrottlingOnGiantPost) { + config_helper_.setBufferLimits(/*upstream_buffer_limit=*/1024, /*downstream_buffer_limit=*/1024); + testRouterRequestAndResponseWithBody(/*request_size=*/1024 * 1024, /*response_size=*/1024, false); +} + +} // namespace Quic +} // namespace Envoy diff --git a/test/integration/http_integration.cc b/test/integration/http_integration.cc index be622d93f3b81..c61a3d511d8f4 100644 --- a/test/integration/http_integration.cc +++ b/test/integration/http_integration.cc @@ -46,6 +46,9 @@ typeToCodecType(Http::CodecClient::Type type) { case Http::CodecClient::Type::HTTP2: return envoy::config::filter::network::http_connection_manager::v2::HttpConnectionManager:: HTTP2; + case Http::CodecClient::Type::HTTP3: + return envoy::config::filter::network::http_connection_manager::v2::HttpConnectionManager:: + HTTP3; default: RELEASE_ASSERT(0, ""); } @@ -60,7 +63,12 @@ IntegrationCodecClient::IntegrationCodecClient( callbacks_(*this), codec_callbacks_(*this) { connection_->addConnectionCallbacks(callbacks_); setCodecConnectionCallbacks(codec_callbacks_); - dispatcher.run(Event::Dispatcher::RunType::Block); + if (type != CodecClient::Type::HTTP3) { + // Only expect to have IO event if it's not QUIC. Because call to connect() in CodecClientProd + // for QUIC doesn't send anything to server, but just register file event. QUIC connection won't + // have any IO event till cryptoConnect() is called later. + dispatcher.run(Event::Dispatcher::RunType::Block); + } } void IntegrationCodecClient::flushWrite() { @@ -190,6 +198,7 @@ IntegrationCodecClientPtr HttpIntegrationTest::makeHttpConnection(uint32_t port) IntegrationCodecClientPtr HttpIntegrationTest::makeRawHttpConnection(Network::ClientConnectionPtr&& conn) { + std::cerr << "============ HttpIntegrationTest::makeRawHttpConnection\n"; std::shared_ptr cluster{new NiceMock()}; cluster->http2_settings_.allow_connect_ = true; cluster->http2_settings_.allow_metadata_ = true; @@ -569,7 +578,9 @@ void HttpIntegrationTest::testRouterUpstreamResponseBeforeRequestComplete() { ASSERT_TRUE(fake_upstreams_[0]->waitForHttpConnection(*dispatcher_, fake_upstream_connection_)); ASSERT_TRUE(fake_upstream_connection_->waitForNewStream(*dispatcher_, upstream_request_)); ASSERT_TRUE(upstream_request_->waitForHeadersComplete()); + upstream_request_->encodeHeaders(default_response_headers_, false); + upstream_request_->encodeData(512, true); response->waitForEndStream(); diff --git a/test/integration/http_integration.h b/test/integration/http_integration.h index 1a2193556e654..35871f0e47073 100644 --- a/test/integration/http_integration.h +++ b/test/integration/http_integration.h @@ -108,7 +108,7 @@ class HttpIntegrationTest : public BaseIntegrationTest { IntegrationCodecClientPtr makeHttpConnection(uint32_t port); // Makes a http connection object without checking its connected state. - IntegrationCodecClientPtr makeRawHttpConnection(Network::ClientConnectionPtr&& conn); + virtual IntegrationCodecClientPtr makeRawHttpConnection(Network::ClientConnectionPtr&& conn); // Makes a http connection object with asserting a connected state. IntegrationCodecClientPtr makeHttpConnection(Network::ClientConnectionPtr&& conn); diff --git a/test/integration/integration.h b/test/integration/integration.h index 7cd2109cc8593..0e2b7d48c6fe1 100644 --- a/test/integration/integration.h +++ b/test/integration/integration.h @@ -138,7 +138,7 @@ struct ApiFilesystemConfig { /** * Test fixture for all integration tests. */ -class BaseIntegrationTest : Logger::Loggable { +class BaseIntegrationTest : protected Logger::Loggable { public: using TestTimeSystemPtr = std::unique_ptr; using InstanceConstSharedPtrFn = std::function; @@ -191,7 +191,7 @@ class BaseIntegrationTest : Logger::Loggable { void setUpstreamAddress(uint32_t upstream_index, envoy::api::v2::endpoint::LbEndpoint& endpoint) const; - Network::ClientConnectionPtr makeClientConnection(uint32_t port); + virtual Network::ClientConnectionPtr makeClientConnection(uint32_t port); void registerTestServerPorts(const std::vector& port_names); void createTestServer(const std::string& json_path, const std::vector& port_names); From f574a58c18d21b9e2b1c0a682eace5d66d1bf5fd Mon Sep 17 00:00:00 2001 From: Dan Zhang Date: Fri, 4 Oct 2019 15:07:21 -0400 Subject: [PATCH 02/76] fix mem slice warning Signed-off-by: Dan Zhang --- source/extensions/quic_listeners/quiche/BUILD | 2 ++ .../quic_listeners/quiche/envoy_quic_client_stream.cc | 2 +- .../quic_listeners/quiche/envoy_quic_server_stream.cc | 2 +- .../quiche/envoy_quic_simulated_watermark_buffer.h | 2 +- 4 files changed, 5 insertions(+), 3 deletions(-) diff --git a/source/extensions/quic_listeners/quiche/BUILD b/source/extensions/quic_listeners/quiche/BUILD index 3f6b86ab63f3b..f59b96ced7708 100644 --- a/source/extensions/quic_listeners/quiche/BUILD +++ b/source/extensions/quic_listeners/quiche/BUILD @@ -168,6 +168,7 @@ envoy_cc_library( "//source/common/buffer:buffer_lib", "//source/common/common:assert_lib", "//source/common/http:header_map_lib", + "//source/extensions/quic_listeners/quiche/platform:quic_platform_mem_slice_storage_impl_lib", "@com_googlesource_quiche//:quic_core_http_spdy_session_lib", ], ) @@ -198,6 +199,7 @@ envoy_cc_library( "//source/common/buffer:buffer_lib", "//source/common/common:assert_lib", "//source/common/http:header_map_lib", + "//source/extensions/quic_listeners/quiche/platform:quic_platform_mem_slice_storage_impl_lib", "@com_googlesource_quiche//:quic_core_http_client_lib", ], ) diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_client_stream.cc b/source/extensions/quic_listeners/quiche/envoy_quic_client_stream.cc index 07e69ef9f9400..3b4005de7cfb0 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_client_stream.cc +++ b/source/extensions/quic_listeners/quiche/envoy_quic_client_stream.cc @@ -10,7 +10,7 @@ #include "quiche/quic/core/http/quic_header_list.h" #include "quiche/quic/core/quic_session.h" #include "quiche/spdy/core/spdy_header_block.h" -#include "quiche/quic/platform/api/quic_mem_slice_span.h" +#include "extensions/quic_listeners/quiche/platform/quic_mem_slice_span_impl.h" #pragma GCC diagnostic pop diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_server_stream.cc b/source/extensions/quic_listeners/quiche/envoy_quic_server_stream.cc index 15060992b520b..f90eeb807e468 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_server_stream.cc +++ b/source/extensions/quic_listeners/quiche/envoy_quic_server_stream.cc @@ -15,7 +15,7 @@ #include "quiche/quic/core/http/quic_header_list.h" #include "quiche/quic/core/quic_session.h" #include "quiche/spdy/core/spdy_header_block.h" -#include "quiche/quic/platform/api/quic_mem_slice_span.h" +#include "extensions/quic_listeners/quiche/platform/quic_mem_slice_span_impl.h" #pragma GCC diagnostic pop #include "extensions/quic_listeners/quiche/envoy_quic_utils.h" diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_simulated_watermark_buffer.h b/source/extensions/quic_listeners/quiche/envoy_quic_simulated_watermark_buffer.h index 577b1f91abbd7..1e2fc72c7867a 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_simulated_watermark_buffer.h +++ b/source/extensions/quic_listeners/quiche/envoy_quic_simulated_watermark_buffer.h @@ -19,7 +19,7 @@ class EnvoyQuicSimulatedWatermarkBuffer { : low_watermark_(low_watermark), high_watermark_(high_watermark), below_low_watermark_(std::move(below_low_watermark)), above_high_watermark_(std::move(above_high_watermark)), logger_(logger) { - ASSERT(high_watermark == 0 && low_watermark == 0 || high_watermark_ > low_watermark_); + ASSERT((high_watermark == 0 && low_watermark == 0) || (high_watermark_ > low_watermark_)); } void checkHighWatermark(uint32_t bytes_buffered) { From 5cc1622958b22d0aa553f39a5032a43ce6f5feae Mon Sep 17 00:00:00 2001 From: Dan Zhang Date: Fri, 4 Oct 2019 15:18:55 -0400 Subject: [PATCH 03/76] remove broken include Signed-off-by: Dan Zhang --- .../extensions/quic_listeners/quiche/envoy_quic_server_stream.cc | 1 - 1 file changed, 1 deletion(-) diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_server_stream.cc b/source/extensions/quic_listeners/quiche/envoy_quic_server_stream.cc index f90eeb807e468..89e4ec51c32b9 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_server_stream.cc +++ b/source/extensions/quic_listeners/quiche/envoy_quic_server_stream.cc @@ -1,6 +1,5 @@ #include "extensions/quic_listeners/quiche/envoy_quic_server_stream.h" -#include #include #include From 469dc2a483ca7e869ac7145c98a094f3c61f6190 Mon Sep 17 00:00:00 2001 From: Dan Zhang Date: Fri, 4 Oct 2019 16:33:15 -0400 Subject: [PATCH 04/76] cryptoConnect Signed-off-by: Dan Zhang --- source/extensions/quic_listeners/quiche/codec_impl.cc | 4 ---- .../quic_listeners/quiche/envoy_quic_client_session.cc | 2 ++ .../quic_listeners/quiche/envoy_quic_client_session.h | 2 ++ .../quiche/envoy_quic_simulated_watermark_buffer.h | 4 ++-- .../extensions/quic_listeners/quiche/envoy_quic_stream.h | 7 +++++++ .../quiche/integration/quic_http_integration_test.cc | 8 +++++--- 6 files changed, 18 insertions(+), 9 deletions(-) diff --git a/source/extensions/quic_listeners/quiche/codec_impl.cc b/source/extensions/quic_listeners/quiche/codec_impl.cc index 3c16b805bfb21..4d00c6a8d679f 100644 --- a/source/extensions/quic_listeners/quiche/codec_impl.cc +++ b/source/extensions/quic_listeners/quiche/codec_impl.cc @@ -14,8 +14,6 @@ QuicHttpServerConnectionImpl::QuicHttpServerConnectionImpl( quic_session.setHttpConnectionCallbacks(callbacks); } -// TODO(danzh): modify QUIC stack to react based on aggregated bytes across all -// streams. void QuicHttpServerConnectionImpl::onUnderlyingConnectionAboveWriteBufferHighWatermark() { for (auto& it : quic_server_session_.stream_map()) { if (!it.second->is_static()) { @@ -50,8 +48,6 @@ QuicHttpClientConnectionImpl::newStream(Http::StreamDecoder& response_decoder) { return *stream; } -// TODO(danzh): modify QUIC stack to react based on aggregated bytes across all -// streams. void QuicHttpClientConnectionImpl::onUnderlyingConnectionAboveWriteBufferHighWatermark() { for (auto& it : quic_client_session_.stream_map()) { if (!it.second->is_static()) { diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_client_session.cc b/source/extensions/quic_listeners/quiche/envoy_quic_client_session.cc index 81813668ed7e6..f9aeb81df3230 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_client_session.cc +++ b/source/extensions/quic_listeners/quiche/envoy_quic_client_session.cc @@ -58,6 +58,8 @@ void EnvoyQuicClientSession::OnCryptoHandshakeEvent(CryptoHandshakeEvent event) void EnvoyQuicClientSession::cryptoConnect() { CryptoConnect(); set_max_allowed_push_id(0u); + // Wait for finishing handshake with server. + dispatcher_.run(Event::Dispatcher::RunType::Block); } std::unique_ptr EnvoyQuicClientSession::CreateClientStream() { diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_client_session.h b/source/extensions/quic_listeners/quiche/envoy_quic_client_session.h index e686fed2d4383..e5dd9862173db 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_client_session.h +++ b/source/extensions/quic_listeners/quiche/envoy_quic_client_session.h @@ -59,6 +59,8 @@ class EnvoyQuicClientSession : public QuicFilterManagerConnectionImpl, // Do version negotiation and crypto handshake. Fail the connection if server // doesn't support the one and only supported version. + // This call will block till the handshake finished with either success to + // failure. void cryptoConnect(); using quic::QuicSpdyClientSession::stream_map; diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_simulated_watermark_buffer.h b/source/extensions/quic_listeners/quiche/envoy_quic_simulated_watermark_buffer.h index 1e2fc72c7867a..0602bd39477f5 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_simulated_watermark_buffer.h +++ b/source/extensions/quic_listeners/quiche/envoy_quic_simulated_watermark_buffer.h @@ -8,8 +8,8 @@ namespace Envoy { namespace Quic { // A class, together with a stand alone buffer, used to achieve the purpose of WatermarkBuffer. -// Itself doesn't have buffer or do bookeeping of buffered bytes. But provided buffered_bytes, it -// re-acts upon crossing high/low watermarks. +// Itself doesn't have buffer or do bookeeping of buffered bytes. But provided with buffered_bytes, +// it re-acts upon crossing high/low watermarks. class EnvoyQuicSimulatedWatermarkBuffer { public: EnvoyQuicSimulatedWatermarkBuffer(uint32_t low_watermark, uint32_t high_watermark, diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_stream.h b/source/extensions/quic_listeners/quiche/envoy_quic_stream.h index 919b9818b0f0d..0ff2768386251 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_stream.h +++ b/source/extensions/quic_listeners/quiche/envoy_quic_stream.h @@ -70,6 +70,9 @@ class EnvoyQuicStream : public Http::StreamEncoder, } EnvoyQuicSimulatedWatermarkBuffer& sendBufferSimulation() { return send_buffer_simulation_; } + // True once end of stream is propergated to Envoy. Envoy doesn't expect to be + // notified more than once about end of stream. So once this is true, no need + // to set it in the callback to Envoy stream any more. bool end_stream_decoded_{false}; int32_t read_disable_counter_{0}; // If true, switchStreamBlockState() should be deferred till this variable @@ -79,6 +82,10 @@ class EnvoyQuicStream : public Http::StreamEncoder, private: // Not owned. Http::StreamDecoder* decoder_{nullptr}; + // Keeps the buffer state of the connection, and react upon the changes of how many bytes are + // buffered cross all streams' send buffer. The state is evaluated and may be changed upon each + // stream write. QUICHE doesn't buffer data in connection, all the data is buffered in stream's + // send buffer. EnvoyQuicSimulatedWatermarkBuffer send_buffer_simulation_; }; diff --git a/test/extensions/quic_listeners/quiche/integration/quic_http_integration_test.cc b/test/extensions/quic_listeners/quiche/integration/quic_http_integration_test.cc index 34a0c8f450b9d..86d5f08b48f58 100644 --- a/test/extensions/quic_listeners/quiche/integration/quic_http_integration_test.cc +++ b/test/extensions/quic_listeners/quiche/integration/quic_http_integration_test.cc @@ -51,9 +51,13 @@ class QuicHttpIntegrationTest : public testing::TestWithParam( getNextServerDesignatedConnectionId(), server_addr, conn_helper_, alarm_factory_, - supported_versions_, local_addr, *dispatcher_, nullptr); + quic::ParsedQuicVersionVector{supported_versions_[0]}, local_addr, *dispatcher_, nullptr); auto session = std::make_unique( quic_config_, supported_versions_, std::move(connection), server_id_, &crypto_config_, &push_promise_index_, *dispatcher_, 0); @@ -69,8 +73,6 @@ class QuicHttpIntegrationTest : public testing::TestWithParamconnected()); dynamic_cast(codec->connection())->cryptoConnect(); - // Wait for finishing handshake with server. - dispatcher_->run(Event::Dispatcher::RunType::Block); if (codec->disconnected()) { // Connection may get closed during version negotiation or handshake. ENVOY_LOG(error, "Fail to connect to server with error: {}", From 68b650154e7461f564f94b9603302645c0fbf513 Mon Sep 17 00:00:00 2001 From: Dan Zhang Date: Fri, 4 Oct 2019 16:41:17 -0400 Subject: [PATCH 05/76] fix xfcc test Signed-off-by: Dan Zhang --- test/integration/http_integration.cc | 1 - test/integration/xfcc_integration_test.cc | 4 ++-- test/integration/xfcc_integration_test.h | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/test/integration/http_integration.cc b/test/integration/http_integration.cc index c61a3d511d8f4..99ff9d5f91b5d 100644 --- a/test/integration/http_integration.cc +++ b/test/integration/http_integration.cc @@ -198,7 +198,6 @@ IntegrationCodecClientPtr HttpIntegrationTest::makeHttpConnection(uint32_t port) IntegrationCodecClientPtr HttpIntegrationTest::makeRawHttpConnection(Network::ClientConnectionPtr&& conn) { - std::cerr << "============ HttpIntegrationTest::makeRawHttpConnection\n"; std::shared_ptr cluster{new NiceMock()}; cluster->http2_settings_.allow_connect_ = true; cluster->http2_settings_.allow_metadata_ = true; diff --git a/test/integration/xfcc_integration_test.cc b/test/integration/xfcc_integration_test.cc index 4e2bed984cfbd..0b62da4439de2 100644 --- a/test/integration/xfcc_integration_test.cc +++ b/test/integration/xfcc_integration_test.cc @@ -92,7 +92,7 @@ Network::TransportSocketFactoryPtr XfccIntegrationTest::createUpstreamSslContext std::move(cfg), *context_manager_, *upstream_stats_store, std::vector{}); } -Network::ClientConnectionPtr XfccIntegrationTest::makeClientConnection() { +Network::ClientConnectionPtr XfccIntegrationTest::makeTcpClientConnection() { Network::Address::InstanceConstSharedPtr address = Network::Utility::resolveUrl("tcp://" + Network::Test::getLoopbackAddressUrlString(version_) + ":" + std::to_string(lookupPort("http"))); @@ -143,7 +143,7 @@ void XfccIntegrationTest::initialize() { void XfccIntegrationTest::testRequestAndResponseWithXfccHeader(std::string previous_xfcc, std::string expected_xfcc) { - Network::ClientConnectionPtr conn = tls_ ? makeMtlsClientConnection() : makeClientConnection(); + Network::ClientConnectionPtr conn = tls_ ? makeMtlsClientConnection() : makeTcpClientConnection(); Http::TestHeaderMapImpl header_map; if (previous_xfcc.empty()) { header_map = Http::TestHeaderMapImpl{{":method", "GET"}, diff --git a/test/integration/xfcc_integration_test.h b/test/integration/xfcc_integration_test.h index 488ff98aad65d..5e1ad2082890d 100644 --- a/test/integration/xfcc_integration_test.h +++ b/test/integration/xfcc_integration_test.h @@ -46,7 +46,7 @@ class XfccIntegrationTest : public testing::TestWithParam Date: Mon, 7 Oct 2019 11:41:31 -0400 Subject: [PATCH 06/76] remove stream libs Signed-off-by: Dan Zhang --- source/extensions/quic_listeners/quiche/BUILD | 22 ------------------- 1 file changed, 22 deletions(-) diff --git a/source/extensions/quic_listeners/quiche/BUILD b/source/extensions/quic_listeners/quiche/BUILD index f59b96ced7708..27a80cda38bf9 100644 --- a/source/extensions/quic_listeners/quiche/BUILD +++ b/source/extensions/quic_listeners/quiche/BUILD @@ -103,21 +103,6 @@ envoy_cc_library( ], ) -envoy_cc_library( - name = "envoy_quic_server_stream_lib", - srcs = ["envoy_quic_server_stream.cc"], - hdrs = ["envoy_quic_server_stream.h"], - tags = ["nofips"], - deps = [ - ":envoy_quic_stream_lib", - ":envoy_quic_utils_lib", - "//source/common/buffer:buffer_lib", - "//source/common/common:assert_lib", - "//source/common/http:header_map_lib", - "@com_googlesource_quiche//:quic_core_http_spdy_session_lib", - ], -) - envoy_cc_library( name = "codec_lib", srcs = ["codec_impl.cc"], @@ -173,13 +158,6 @@ envoy_cc_library( ], ) -envoy_cc_library( - name = "envoy_quic_client_stream_lib", - tags = ["nofips"], - deps = [ - ], -) - envoy_cc_library( name = "envoy_quic_client_session_lib", srcs = [ From c0ede9af09a203febe751154db94e7739a2fb994 Mon Sep 17 00:00:00 2001 From: Dan Zhang Date: Mon, 7 Oct 2019 13:22:08 -0400 Subject: [PATCH 07/76] fix asan Signed-off-by: Dan Zhang --- .../quic_listeners/quiche/envoy_quic_client_stream.cc | 3 +++ .../quic_listeners/quiche/envoy_quic_server_stream.cc | 2 ++ 2 files changed, 5 insertions(+) diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_client_stream.cc b/source/extensions/quic_listeners/quiche/envoy_quic_client_stream.cc index 3b4005de7cfb0..3fc24dea4e25c 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_client_stream.cc +++ b/source/extensions/quic_listeners/quiche/envoy_quic_client_stream.cc @@ -84,6 +84,9 @@ void EnvoyQuicClientStream::encodeMetadata(const Http::MetadataMapVector& /*meta } void EnvoyQuicClientStream::resetStream(Http::StreamResetReason reason) { + // Higher layers expect calling resetStream() to immediately raise reset callbacks. + runResetCallbacks(reason); + Reset(envoyResetReasonToQuicRstError(reason)); } diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_server_stream.cc b/source/extensions/quic_listeners/quiche/envoy_quic_server_stream.cc index 89e4ec51c32b9..9f05182af88af 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_server_stream.cc +++ b/source/extensions/quic_listeners/quiche/envoy_quic_server_stream.cc @@ -86,6 +86,8 @@ void EnvoyQuicServerStream::encodeMetadata(const Http::MetadataMapVector& /*meta } void EnvoyQuicServerStream::resetStream(Http::StreamResetReason reason) { + // Higher layers expect calling resetStream() to immediately raise reset callbacks. + runResetCallbacks(reason); if (local_end_stream_ && !reading_stopped()) { // This is after 200 early response. Reset with QUIC_STREAM_NO_ERROR instead // of propagating original reset reason. From 3e39fbe218595c314dbcf06daafcc4e6ab538322 Mon Sep 17 00:00:00 2001 From: Dan Zhang Date: Mon, 7 Oct 2019 15:36:20 -0400 Subject: [PATCH 08/76] revert non-HCM change Signed-off-by: Dan Zhang --- bazel/external/quiche.BUILD | 1 - source/common/common/logger.h | 1 - source/common/http/BUILD | 2 - source/common/http/codec_client.cc | 15 +- .../network/http_connection_manager/config.cc | 8 +- source/extensions/quic_listeners/quiche/BUILD | 80 ++----- .../quiche/active_quic_listener.cc | 6 +- .../quiche/active_quic_listener.h | 2 - .../quic_listeners/quiche/codec_impl.cc | 57 +---- .../quic_listeners/quiche/codec_impl.h | 37 +-- .../quic_listeners/quiche/envoy_quic_alarm.h | 6 +- .../quiche/envoy_quic_client_connection.cc | 176 -------------- .../quiche/envoy_quic_client_connection.h | 66 ------ .../quiche/envoy_quic_client_session.cc | 87 ------- .../quiche/envoy_quic_client_session.h | 82 ------- .../quiche/envoy_quic_client_stream.cc | 219 ------------------ .../quiche/envoy_quic_client_stream.h | 56 ----- .../quiche/envoy_quic_connection.cc | 5 +- .../quiche/envoy_quic_dispatcher.cc | 3 +- .../quiche/envoy_quic_fake_proof_verifier.h | 3 +- .../quiche/envoy_quic_server_session.cc | 12 +- .../quiche/envoy_quic_server_session.h | 7 +- .../quiche/envoy_quic_server_stream.cc | 152 +++--------- .../quiche/envoy_quic_server_stream.h | 14 +- .../envoy_quic_simulated_watermark_buffer.h | 62 ----- .../quic_listeners/quiche/envoy_quic_stream.h | 55 +---- .../quic_listeners/quiche/envoy_quic_utils.cc | 45 ---- .../quic_listeners/quiche/envoy_quic_utils.h | 11 - .../quic_filter_manager_connection_impl.cc | 57 +---- .../quic_filter_manager_connection_impl.h | 32 +-- test/config/utility.cc | 31 --- test/config/utility.h | 3 +- test/extensions/quic_listeners/quiche/BUILD | 3 +- .../quiche/envoy_quic_dispatcher_test.cc | 10 +- .../quiche/envoy_quic_proof_source_test.cc | 4 +- .../quiche/envoy_quic_server_session_test.cc | 10 +- .../quiche/envoy_quic_server_stream_test.cc | 213 +++-------------- .../quic_listeners/quiche/integration/BUILD | 29 --- .../integration/quic_http_integration_test.cc | 185 --------------- test/integration/http_integration.cc | 12 +- test/integration/http_integration.h | 2 +- test/integration/integration.h | 4 +- test/integration/xfcc_integration_test.cc | 4 +- test/integration/xfcc_integration_test.h | 2 +- 44 files changed, 161 insertions(+), 1710 deletions(-) delete mode 100644 source/extensions/quic_listeners/quiche/envoy_quic_client_connection.cc delete mode 100644 source/extensions/quic_listeners/quiche/envoy_quic_client_connection.h delete mode 100644 source/extensions/quic_listeners/quiche/envoy_quic_client_session.cc delete mode 100644 source/extensions/quic_listeners/quiche/envoy_quic_client_session.h delete mode 100644 source/extensions/quic_listeners/quiche/envoy_quic_client_stream.cc delete mode 100644 source/extensions/quic_listeners/quiche/envoy_quic_client_stream.h delete mode 100644 source/extensions/quic_listeners/quiche/envoy_quic_simulated_watermark_buffer.h delete mode 100644 test/extensions/quic_listeners/quiche/integration/BUILD delete mode 100644 test/extensions/quic_listeners/quiche/integration/quic_http_integration_test.cc diff --git a/bazel/external/quiche.BUILD b/bazel/external/quiche.BUILD index 47505a2d131fb..67d7d4207ce90 100644 --- a/bazel/external/quiche.BUILD +++ b/bazel/external/quiche.BUILD @@ -1993,7 +1993,6 @@ envoy_cc_library( copts = quiche_copt, repository = "@envoy", tags = ["nofips"], - visibility = ["//visibility:public"], deps = [ ":quic_core_alarm_interface_lib", ":quic_core_crypto_encryption_lib", diff --git a/source/common/common/logger.h b/source/common/common/logger.h index 80c9f57e574e3..5efa9accd3404 100644 --- a/source/common/common/logger.h +++ b/source/common/common/logger.h @@ -49,7 +49,6 @@ namespace Logger { FUNCTION(misc) \ FUNCTION(mongo) \ FUNCTION(quic) \ - FUNCTION(quic_stream) \ FUNCTION(pool) \ FUNCTION(rbac) \ FUNCTION(redis) \ diff --git a/source/common/http/BUILD b/source/common/http/BUILD index b7b152311c4a1..05553bd529169 100644 --- a/source/common/http/BUILD +++ b/source/common/http/BUILD @@ -51,7 +51,6 @@ envoy_cc_library( "//source/common/http/http1:codec_lib", "//source/common/http/http2:codec_lib", "//source/common/network:filter_lib", - "//source/extensions/quic_listeners/quiche:codec_lib", ], ) @@ -189,7 +188,6 @@ envoy_cc_library( "//source/common/runtime:uuid_util_lib", "//source/common/stream_info:stream_info_lib", "//source/common/tracing:http_tracer_lib", - "//source/extensions/quic_listeners/quiche:codec_lib", ], ) diff --git a/source/common/http/codec_client.cc b/source/common/http/codec_client.cc index da5d19eb7a16e..d809327ab5b27 100644 --- a/source/common/http/codec_client.cc +++ b/source/common/http/codec_client.cc @@ -9,8 +9,6 @@ #include "common/http/http2/codec_impl.h" #include "common/http/utility.h" -#include "extensions/quic_listeners/quiche/codec_impl.h" - namespace Envoy { namespace Http { @@ -157,17 +155,8 @@ CodecClientProd::CodecClientProd(Type type, Network::ClientConnectionPtr&& conne break; } case Type::HTTP3: { - // TODO(danzh) this enforce dependency from core code to QUICHE. Is there a - // better way to aoivd such dependency in case QUICHE breaks Envoy build. - // Alternatives: - // 1) move codec creation to Network::Connection instance, in - // QUIC's case, EnvoyQuicClientSession. This is not ideal as - // Network::Connection is not necessart to speak HTTP. - // 2) make codec creation in a static registered factory again. It can be - // only necessary for QUIC and for HTTP2 and HTTP1 just use the existing - // logic. - codec_ = std::make_unique( - dynamic_cast(*connection_), *this); + // TODO(danzh) Add QUIC codec; + NOT_IMPLEMENTED_GCOVR_EXCL_LINE; } } } diff --git a/source/extensions/filters/network/http_connection_manager/config.cc b/source/extensions/filters/network/http_connection_manager/config.cc index 6a3d45bdb70ec..8359fe50c02b0 100644 --- a/source/extensions/filters/network/http_connection_manager/config.cc +++ b/source/extensions/filters/network/http_connection_manager/config.cc @@ -25,8 +25,6 @@ #include "common/router/rds_impl.h" #include "common/router/scoped_rds.h" -#include "extensions/quic_listeners/quiche/codec_impl.h" - namespace Envoy { namespace Extensions { namespace NetworkFilters { @@ -410,10 +408,8 @@ HttpConnectionManagerConfig::createCodec(Network::Connection& connection, return std::make_unique( connection, callbacks, context_.scope(), http2_settings_, maxRequestHeadersKb()); case CodecType::HTTP3: - // TODO(danzh) same as client side. This enforce dependency on QUICHE. Is there a - // better way to aoivd such dependency in case QUICHE breaks this extension. - return std::make_unique( - dynamic_cast(connection), callbacks); + // TODO(danzh) create QUIC specific codec. + NOT_IMPLEMENTED_GCOVR_EXCL_LINE; case CodecType::AUTO: return Http::ConnectionManagerUtility::autoCreateCodec(connection, data, callbacks, context_.scope(), http1_settings_, diff --git a/source/extensions/quic_listeners/quiche/BUILD b/source/extensions/quic_listeners/quiche/BUILD index 27a80cda38bf9..d0e569e3d93da 100644 --- a/source/extensions/quic_listeners/quiche/BUILD +++ b/source/extensions/quic_listeners/quiche/BUILD @@ -97,19 +97,32 @@ envoy_cc_library( hdrs = ["envoy_quic_stream.h"], tags = ["nofips"], deps = [ - ":envoy_quic_simulated_watermark_buffer_lib", "//include/envoy/http:codec_interface", "//source/common/http:codec_helper_lib", ], ) +envoy_cc_library( + name = "envoy_quic_server_stream_lib", + srcs = ["envoy_quic_server_stream.cc"], + hdrs = ["envoy_quic_server_stream.h"], + tags = ["nofips"], + deps = [ + ":envoy_quic_stream_lib", + ":envoy_quic_utils_lib", + "//source/common/buffer:buffer_lib", + "//source/common/common:assert_lib", + "//source/common/http:header_map_lib", + "@com_googlesource_quiche//:quic_core_http_spdy_session_lib", + ], +) + envoy_cc_library( name = "codec_lib", srcs = ["codec_impl.cc"], hdrs = ["codec_impl.h"], tags = ["nofips"], deps = [ - ":envoy_quic_client_session_lib", ":envoy_quic_server_session_lib", "//include/envoy/http:codec_interface", "@com_googlesource_quiche//:quic_core_http_spdy_session_lib", @@ -123,13 +136,9 @@ envoy_cc_library( tags = ["nofips"], deps = [ ":envoy_quic_connection_lib", - ":envoy_quic_simulated_watermark_buffer_lib", "//include/envoy/event:dispatcher_interface", "//include/envoy/network:connection_interface", - "//source/common/buffer:buffer_lib", - "//source/common/common:assert_lib", "//source/common/common:empty_string", - "//source/common/http:header_map_lib", "//source/common/network:filter_manager_lib", "//source/common/stream_info:stream_info_lib", ], @@ -137,51 +146,16 @@ envoy_cc_library( envoy_cc_library( name = "envoy_quic_server_session_lib", - srcs = [ - "envoy_quic_server_session.cc", - "envoy_quic_server_stream.cc", - ], - hdrs = [ - "envoy_quic_server_session.h", - "envoy_quic_server_stream.h", - ], + srcs = ["envoy_quic_server_session.cc"], + hdrs = ["envoy_quic_server_session.h"], tags = ["nofips"], deps = [ - ":envoy_quic_stream_lib", - ":envoy_quic_utils_lib", + ":envoy_quic_server_stream_lib", ":quic_filter_manager_connection_lib", - "//source/common/buffer:buffer_lib", - "//source/common/common:assert_lib", - "//source/common/http:header_map_lib", - "//source/extensions/quic_listeners/quiche/platform:quic_platform_mem_slice_storage_impl_lib", "@com_googlesource_quiche//:quic_core_http_spdy_session_lib", ], ) -envoy_cc_library( - name = "envoy_quic_client_session_lib", - srcs = [ - "envoy_quic_client_session.cc", - "envoy_quic_client_stream.cc", - ], - hdrs = [ - "envoy_quic_client_session.h", - "envoy_quic_client_stream.h", - ], - tags = ["nofips"], - deps = [ - ":envoy_quic_client_connection_lib", - ":envoy_quic_stream_lib", - ":envoy_quic_utils_lib", - ":quic_filter_manager_connection_lib", - "//source/common/buffer:buffer_lib", - "//source/common/common:assert_lib", - "//source/common/http:header_map_lib", - "//source/extensions/quic_listeners/quiche/platform:quic_platform_mem_slice_storage_impl_lib", - "@com_googlesource_quiche//:quic_core_http_client_lib", - ], -) - envoy_cc_library( name = "quic_io_handle_wrapper_lib", hdrs = ["quic_io_handle_wrapper.h"], @@ -217,19 +191,6 @@ envoy_cc_library( ], ) -envoy_cc_library( - name = "envoy_quic_client_connection_lib", - srcs = ["envoy_quic_client_connection.cc"], - hdrs = ["envoy_quic_client_connection.h"], - tags = ["nofips"], - deps = [ - ":envoy_quic_connection_lib", - ":envoy_quic_packet_writer_lib", - "//include/envoy/event:dispatcher_interface", - "//source/common/network:socket_option_factory_lib", - ], -) - envoy_cc_library( name = "envoy_quic_dispatcher_lib", srcs = ["envoy_quic_dispatcher.cc"], @@ -246,11 +207,6 @@ envoy_cc_library( ], ) -envoy_cc_library( - name = "envoy_quic_simulated_watermark_buffer_lib", - hdrs = ["envoy_quic_simulated_watermark_buffer.h"], -) - envoy_cc_library( name = "active_quic_listener_lib", srcs = ["active_quic_listener.cc"], diff --git a/source/extensions/quic_listeners/quiche/active_quic_listener.cc b/source/extensions/quic_listeners/quiche/active_quic_listener.cc index af6d92fad2a17..15d1b700e0b44 100644 --- a/source/extensions/quic_listeners/quiche/active_quic_listener.cc +++ b/source/extensions/quic_listeners/quiche/active_quic_listener.cc @@ -43,8 +43,6 @@ ActiveQuicListener::ActiveQuicListener(Event::Dispatcher& dispatcher, quic::QuicRandom::GetInstance(), std::make_unique(), quic::KeyExchangeSource::Default()); auto connection_helper = std::make_unique(dispatcher_); - crypto_config_->AddDefaultConfig(random, connection_helper->GetClock(), - quic::QuicCryptoServerConfig::ConfigOptions()); auto alarm_factory = std::make_unique(dispatcher_, *connection_helper->GetClock()); quic_dispatcher_ = std::make_unique( @@ -54,8 +52,6 @@ ActiveQuicListener::ActiveQuicListener(Event::Dispatcher& dispatcher, quic_dispatcher_->InitializeWithWriter(writer.release()); } -ActiveQuicListener::~ActiveQuicListener() { onListenerShutdown(); } - void ActiveQuicListener::onListenerShutdown() { ENVOY_LOG(info, "Quic listener {} shutdown.", config_.name()); quic_dispatcher_->Shutdown(); @@ -67,7 +63,7 @@ void ActiveQuicListener::onData(Network::UdpRecvData& data) { envoyAddressInstanceToQuicSocketAddress(data.local_address_)); quic::QuicTime timestamp = quic::QuicTime::Zero() + - quic::QuicTime::Delta::FromMicroseconds(std::chrono::duration_cast( + quic::QuicTime::Delta::FromMilliseconds(std::chrono::duration_cast( data.receive_time_.time_since_epoch()) .count()); uint64_t num_slice = data.buffer_->getRawSlices(nullptr, 0); diff --git a/source/extensions/quic_listeners/quiche/active_quic_listener.h b/source/extensions/quic_listeners/quiche/active_quic_listener.h index d724790bd5299..89ef57d83727d 100644 --- a/source/extensions/quic_listeners/quiche/active_quic_listener.h +++ b/source/extensions/quic_listeners/quiche/active_quic_listener.h @@ -32,8 +32,6 @@ class ActiveQuicListener : public Network::UdpListenerCallbacks, Network::UdpListenerPtr&& listener, Network::ListenerConfig& listener_config, const quic::QuicConfig& quic_config); - ~ActiveQuicListener() override; - // TODO(#7465): Make this a callback. void onListenerShutdown(); diff --git a/source/extensions/quic_listeners/quiche/codec_impl.cc b/source/extensions/quic_listeners/quiche/codec_impl.cc index 4d00c6a8d679f..fdb060cb7c159 100644 --- a/source/extensions/quic_listeners/quiche/codec_impl.cc +++ b/source/extensions/quic_listeners/quiche/codec_impl.cc @@ -1,68 +1,23 @@ #include "extensions/quic_listeners/quiche/codec_impl.h" -#include "extensions/quic_listeners/quiche/envoy_quic_client_stream.h" -#include "extensions/quic_listeners/quiche/envoy_quic_server_stream.h" - namespace Envoy { namespace Quic { bool QuicHttpConnectionImplBase::wantsToWrite() { return quic_session_.HasDataToWrite(); } -QuicHttpServerConnectionImpl::QuicHttpServerConnectionImpl( - EnvoyQuicServerSession& quic_session, Http::ServerConnectionCallbacks& callbacks) - : QuicHttpConnectionImplBase(quic_session), quic_server_session_(quic_session) { - quic_session.setHttpConnectionCallbacks(callbacks); -} - -void QuicHttpServerConnectionImpl::onUnderlyingConnectionAboveWriteBufferHighWatermark() { - for (auto& it : quic_server_session_.stream_map()) { - if (!it.second->is_static()) { - dynamic_cast(it.second.get())->runHighWatermarkCallbacks(); - } - } +// TODO(danzh): modify QUIC stack to react based on aggregated bytes across all +// the streams. And call StreamCallbackHelper::runHighWatermarkCallbacks() for each stream. +void QuicHttpConnectionImplBase::onUnderlyingConnectionAboveWriteBufferHighWatermark() { + NOT_IMPLEMENTED_GCOVR_EXCL_LINE; } -void QuicHttpServerConnectionImpl::onUnderlyingConnectionBelowWriteBufferLowWatermark() { - for (const auto& it : quic_server_session_.stream_map()) { - if (!it.second->is_static()) { - dynamic_cast(it.second.get())->runLowWatermarkCallbacks(); - } - } +void QuicHttpConnectionImplBase::onUnderlyingConnectionBelowWriteBufferLowWatermark() { + NOT_IMPLEMENTED_GCOVR_EXCL_LINE; } void QuicHttpServerConnectionImpl::goAway() { quic_server_session_.SendGoAway(quic::QUIC_PEER_GOING_AWAY, "server shutdown imminent"); } -QuicHttpClientConnectionImpl::QuicHttpClientConnectionImpl(EnvoyQuicClientSession& session, - Http::ConnectionCallbacks& callbacks) - : QuicHttpConnectionImplBase(session), quic_client_session_(session) { - session.setHttpConnectionCallbacks(callbacks); -} - -Http::StreamEncoder& -QuicHttpClientConnectionImpl::newStream(Http::StreamDecoder& response_decoder) { - auto stream = dynamic_cast( - quic_client_session_.CreateOutgoingBidirectionalStream()); - stream->setDecoder(response_decoder); - return *stream; -} - -void QuicHttpClientConnectionImpl::onUnderlyingConnectionAboveWriteBufferHighWatermark() { - for (auto& it : quic_client_session_.stream_map()) { - if (!it.second->is_static()) { - dynamic_cast(it.second.get())->runHighWatermarkCallbacks(); - } - } -} - -void QuicHttpClientConnectionImpl::onUnderlyingConnectionBelowWriteBufferLowWatermark() { - for (const auto& it : quic_client_session_.stream_map()) { - if (!it.second->is_static()) { - dynamic_cast(it.second.get())->runLowWatermarkCallbacks(); - } - } -} - } // namespace Quic } // namespace Envoy diff --git a/source/extensions/quic_listeners/quiche/codec_impl.h b/source/extensions/quic_listeners/quiche/codec_impl.h index 141d73220d98a..debff738cb044 100644 --- a/source/extensions/quic_listeners/quiche/codec_impl.h +++ b/source/extensions/quic_listeners/quiche/codec_impl.h @@ -3,7 +3,6 @@ #include "common/common/assert.h" #include "common/common/logger.h" -#include "extensions/quic_listeners/quiche/envoy_quic_client_session.h" #include "extensions/quic_listeners/quiche/envoy_quic_server_session.h" namespace Envoy { @@ -22,11 +21,17 @@ class QuicHttpConnectionImplBase : public virtual Http::Connection, // Bypassed. QUIC connection already hands all data to streams. NOT_REACHED_GCOVR_EXCL_LINE; } - Http::Protocol protocol() override { return Http::Protocol::Http3; } - + Http::Protocol protocol() override { + // From HCM's view, QUIC should behave the same as Http2, only the stats + // should be different. + // TODO(danzh) add Http3 enum value for QUIC. + return Http::Protocol::Http2; + } // Returns true if the session has data to send but queued in connection or // stream send buffer. bool wantsToWrite() override; + void onUnderlyingConnectionAboveWriteBufferHighWatermark() override; + void onUnderlyingConnectionBelowWriteBufferLowWatermark() override; protected: quic::QuicSpdySession& quic_session_; @@ -36,7 +41,10 @@ class QuicHttpServerConnectionImpl : public QuicHttpConnectionImplBase, public Http::ServerConnection { public: QuicHttpServerConnectionImpl(EnvoyQuicServerSession& quic_session, - Http::ServerConnectionCallbacks& callbacks); + Http::ServerConnectionCallbacks& callbacks) + : QuicHttpConnectionImplBase(quic_session), quic_server_session_(quic_session) { + quic_session.setHttpConnectionCallbacks(callbacks); + } // Http::Connection void goAway() override; @@ -44,31 +52,10 @@ class QuicHttpServerConnectionImpl : public QuicHttpConnectionImplBase, // TODO(danzh): Add double-GOAWAY support in QUIC. ENVOY_CONN_LOG(error, "Shutdown notice is not propagated to QUIC.", quic_server_session_); } - void onUnderlyingConnectionAboveWriteBufferHighWatermark() override; - void onUnderlyingConnectionBelowWriteBufferLowWatermark() override; private: EnvoyQuicServerSession& quic_server_session_; }; -class QuicHttpClientConnectionImpl : public QuicHttpConnectionImplBase, - public Http::ClientConnection { -public: - QuicHttpClientConnectionImpl(EnvoyQuicClientSession& session, - Http::ConnectionCallbacks& callbacks); - - // Http::ClientConnection - Http::StreamEncoder& newStream(Http::StreamDecoder& response_decoder) override; - - // Http::Connection - void goAway() override { NOT_REACHED_GCOVR_EXCL_LINE; } - void shutdownNotice() override { NOT_REACHED_GCOVR_EXCL_LINE; } - void onUnderlyingConnectionAboveWriteBufferHighWatermark() override; - void onUnderlyingConnectionBelowWriteBufferLowWatermark() override; - -private: - EnvoyQuicClientSession& quic_client_session_; -}; - } // namespace Quic } // namespace Envoy diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_alarm.h b/source/extensions/quic_listeners/quiche/envoy_quic_alarm.h index 22010987c4def..4152f4c101c3f 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_alarm.h +++ b/source/extensions/quic_listeners/quiche/envoy_quic_alarm.h @@ -20,11 +20,7 @@ class EnvoyQuicAlarm : public quic::QuicAlarm { EnvoyQuicAlarm(Event::Dispatcher& dispatcher, const quic::QuicClock& clock, quic::QuicArenaScopedPtr delegate); - ~EnvoyQuicAlarm() override { - if (IsSet()) { - Cancel(); - } - }; + ~EnvoyQuicAlarm() override { ASSERT(!IsSet()); }; // quic::QuicAlarm void CancelImpl() override; diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_client_connection.cc b/source/extensions/quic_listeners/quiche/envoy_quic_client_connection.cc deleted file mode 100644 index 9a6dfb01028cf..0000000000000 --- a/source/extensions/quic_listeners/quiche/envoy_quic_client_connection.cc +++ /dev/null @@ -1,176 +0,0 @@ -#include "extensions/quic_listeners/quiche/envoy_quic_client_connection.h" - -#include "common/network/listen_socket_impl.h" -#include "common/network/socket_option_factory.h" - -#include "extensions/quic_listeners/quiche/envoy_quic_packet_writer.h" -#include "extensions/quic_listeners/quiche/envoy_quic_utils.h" -#include "extensions/transport_sockets/well_known_names.h" - -namespace Envoy { -namespace Quic { - -EnvoyQuicClientConnection::EnvoyQuicClientConnection( - const quic::QuicConnectionId& server_connection_id, - Network::Address::InstanceConstSharedPtr& initial_peer_address, - quic::QuicConnectionHelperInterface& helper, quic::QuicAlarmFactory& alarm_factory, - const quic::ParsedQuicVersionVector& supported_versions, - Network::Address::InstanceConstSharedPtr local_addr, Event::Dispatcher& dispatcher, - const Network::ConnectionSocket::OptionsSharedPtr& options) - : EnvoyQuicClientConnection(server_connection_id, helper, alarm_factory, supported_versions, - dispatcher, - createConnectionSocket(initial_peer_address, local_addr, options)) { -} - -EnvoyQuicClientConnection::EnvoyQuicClientConnection( - const quic::QuicConnectionId& server_connection_id, quic::QuicConnectionHelperInterface& helper, - quic::QuicAlarmFactory& alarm_factory, const quic::ParsedQuicVersionVector& supported_versions, - Event::Dispatcher& dispatcher, Network::ConnectionSocketPtr&& connection_socket) - : EnvoyQuicClientConnection(server_connection_id, helper, alarm_factory, - new EnvoyQuicPacketWriter(*connection_socket), true, - supported_versions, dispatcher, std::move(connection_socket)) {} - -EnvoyQuicClientConnection::EnvoyQuicClientConnection( - const quic::QuicConnectionId& server_connection_id, quic::QuicConnectionHelperInterface& helper, - quic::QuicAlarmFactory& alarm_factory, quic::QuicPacketWriter* writer, bool owns_writer, - const quic::ParsedQuicVersionVector& supported_versions, Event::Dispatcher& dispatcher, - Network::ConnectionSocketPtr&& connection_socket) - : EnvoyQuicConnection( - server_connection_id, - envoyAddressInstanceToQuicSocketAddress(connection_socket->remoteAddress()), helper, - alarm_factory, writer, owns_writer, quic::Perspective::IS_CLIENT, supported_versions, - std::move(connection_socket)), - dispatcher_(dispatcher) {} - -EnvoyQuicClientConnection::~EnvoyQuicClientConnection() { file_event_->setEnabled(0); } - -void EnvoyQuicClientConnection::processPacket( - Network::Address::InstanceConstSharedPtr local_address, - Network::Address::InstanceConstSharedPtr peer_address, Buffer::InstancePtr buffer, - MonotonicTime receive_time) { - quic::QuicTime timestamp = - quic::QuicTime::Zero() + - quic::QuicTime::Delta::FromMicroseconds( - std::chrono::duration_cast(receive_time.time_since_epoch()) - .count()); - uint64_t num_slice = buffer->getRawSlices(nullptr, 0); - ASSERT(num_slice == 1); - Buffer::RawSlice slice; - buffer->getRawSlices(&slice, 1); - quic::QuicReceivedPacket packet(reinterpret_cast(slice.mem_), slice.len_, timestamp, - /*owns_buffer=*/false, /*ttl=*/0, /*ttl_valid=*/true, - /*packet_headers=*/nullptr, /*headers_length=*/0, - /*owns_header_buffer*/ false); - ProcessUdpPacket(envoyAddressInstanceToQuicSocketAddress(local_address), - envoyAddressInstanceToQuicSocketAddress(peer_address), packet); -} - -uint64_t EnvoyQuicClientConnection::maxPacketSize() const { - // TODO(danzh) make this variable configurable to support jumbo frames. - return Network::MAX_UDP_PACKET_SIZE; -} - -void EnvoyQuicClientConnection::setUpConnectionSocket() { - if (connectionSocket()->ioHandle().isOpen()) { - file_event_ = dispatcher_.createFileEvent( - connectionSocket()->ioHandle().fd(), - [this](uint32_t events) -> void { onFileEvent(events); }, Event::FileTriggerType::Edge, - Event::FileReadyType::Read | Event::FileReadyType::Write); - - if (!Network::Socket::applyOptions(connectionSocket()->options(), *connectionSocket(), - envoy::api::v2::core::SocketOption::STATE_LISTENING)) { - ENVOY_LOG_MISC(error, "Fail to apply listening options"); - connectionSocket()->close(); - } - } - if (!connectionSocket()->ioHandle().isOpen()) { - CloseConnection(quic::QUIC_CONNECTION_CANCELLED, "Fail to setup connection socket.", - quic::ConnectionCloseBehavior::SILENT_CLOSE); - } -} - -void EnvoyQuicClientConnection::onFileEvent(uint32_t events) { - ENVOY_CONN_LOG(trace, "socket event: {}", *this, events); - ASSERT(events & (Event::FileReadyType::Read | Event::FileReadyType::Write)); - - if (events & Event::FileReadyType::Write) { - OnCanWrite(); - } - - // It's possible for a write event callback to close the connection, in such case ignore read - // event processing. - if (connected() && (events & Event::FileReadyType::Read)) { - uint32_t old_packets_dropped = packets_dropped_; - while (connected()) { - // Read till socket is drained. - // TODO(danzh): limit read times here. - MonotonicTime receive_time = dispatcher_.timeSource().monotonicTime(); - Api::IoCallUint64Result result = Network::Utility::readFromSocket( - *connectionSocket(), *this, receive_time, &packets_dropped_); - if (!result.ok()) { - if (result.err_->getErrorCode() != Api::IoError::IoErrorCode::Again) { - ENVOY_CONN_LOG(error, "recvmsg result {}: {}", *this, - static_cast(result.err_->getErrorCode()), - result.err_->getErrorDetails()); - } - // Stop reading. - break; - } - - if (result.rc_ == 0) { - // TODO(conqerAtapple): Is zero length packet interesting? If so add stats - // for it. Otherwise remove the warning log below. - ENVOY_CONN_LOG(trace, "received 0-length packet", *this); - } - - if (packets_dropped_ != old_packets_dropped) { - // The kernel tracks SO_RXQ_OVFL as a uint32 which can overflow to a smaller - // value. So as long as this count differs from previously recorded value, - // more packets are dropped by kernel. - uint32_t delta = (packets_dropped_ > old_packets_dropped) - ? (packets_dropped_ - old_packets_dropped) - : (packets_dropped_ + - (std::numeric_limits::max() - old_packets_dropped) + 1); - // TODO(danzh) add stats for this. - ENVOY_CONN_LOG(debug, - "Kernel dropped {} more packets. Consider increase receive buffer size.", - *this, delta); - } - } - } -} - -Network::ConnectionSocketPtr EnvoyQuicClientConnection::createConnectionSocket( - Network::Address::InstanceConstSharedPtr& peer_addr, - Network::Address::InstanceConstSharedPtr& local_addr, - const Network::ConnectionSocket::OptionsSharedPtr& options) { - Network::IoHandlePtr io_handle = peer_addr->socket(Network::Address::SocketType::Datagram); - auto connection_socket = - std::make_unique(std::move(io_handle), local_addr, peer_addr); - connection_socket->addOptions(Network::SocketOptionFactory::buildIpPacketInfoOptions()); - connection_socket->addOptions(Network::SocketOptionFactory::buildRxQueueOverFlowOptions()); - if (options != nullptr) { - connection_socket->addOptions(options); - } - if (!Network::Socket::applyOptions(connection_socket->options(), *connection_socket, - envoy::api::v2::core::SocketOption::STATE_PREBIND)) { - connection_socket->close(); - ENVOY_LOG_MISC(error, "Fail to apply pre-bind options"); - return connection_socket; - } - local_addr->bind(connection_socket->ioHandle().fd()); - ASSERT(local_addr->ip()); - if (local_addr->ip()->port() == 0) { - // Get ephemeral port number. - local_addr = Network::Address::addressFromFd(connection_socket->ioHandle().fd()); - } - if (!Network::Socket::applyOptions(connection_socket->options(), *connection_socket, - envoy::api::v2::core::SocketOption::STATE_BOUND)) { - ENVOY_LOG_MISC(error, "Fail to apply post-bind options"); - connection_socket->close(); - } - return connection_socket; -} - -} // namespace Quic -} // namespace Envoy diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_client_connection.h b/source/extensions/quic_listeners/quiche/envoy_quic_client_connection.h deleted file mode 100644 index f9d79af6acca8..0000000000000 --- a/source/extensions/quic_listeners/quiche/envoy_quic_client_connection.h +++ /dev/null @@ -1,66 +0,0 @@ -#pragma once - -#include "envoy/event/dispatcher.h" - -#include "common/network/utility.h" - -#include "extensions/quic_listeners/quiche/envoy_quic_connection.h" - -namespace Envoy { -namespace Quic { - -// A client QuicConnection instance manages its own I/O events. -class EnvoyQuicClientConnection : public EnvoyQuicConnection, public Network::UdpPacketProcessor { -public: - // A connection socket will be created with given |local_addr|. If binding - // port not provided in |local_addr|, pick up a random port. - EnvoyQuicClientConnection(const quic::QuicConnectionId& server_connection_id, - Network::Address::InstanceConstSharedPtr& initial_peer_address, - quic::QuicConnectionHelperInterface& helper, - quic::QuicAlarmFactory& alarm_factory, - const quic::ParsedQuicVersionVector& supported_versions, - Network::Address::InstanceConstSharedPtr local_addr, - Event::Dispatcher& dispatcher, - const Network::ConnectionSocket::OptionsSharedPtr& options); - - // Overridden to un-register all file events. - ~EnvoyQuicClientConnection() override; - - void processPacket(Network::Address::InstanceConstSharedPtr local_address, - Network::Address::InstanceConstSharedPtr peer_address, - Buffer::InstancePtr buffer, MonotonicTime receive_time) override; - - uint64_t maxPacketSize() const override; - - // Register file event and apply socket options. - void setUpConnectionSocket(); - -private: - EnvoyQuicClientConnection(const quic::QuicConnectionId& server_connection_id, - quic::QuicConnectionHelperInterface& helper, - quic::QuicAlarmFactory& alarm_factory, - const quic::ParsedQuicVersionVector& supported_versions, - Event::Dispatcher& dispatcher, - Network::ConnectionSocketPtr&& connection_socket); - - EnvoyQuicClientConnection(const quic::QuicConnectionId& server_connection_id, - quic::QuicConnectionHelperInterface& helper, - quic::QuicAlarmFactory& alarm_factory, quic::QuicPacketWriter* writer, - bool owns_writer, - const quic::ParsedQuicVersionVector& supported_versions, - Event::Dispatcher& dispatcher, - Network::ConnectionSocketPtr&& connection_socket); - - Network::ConnectionSocketPtr - createConnectionSocket(Network::Address::InstanceConstSharedPtr& peer_addr, - Network::Address::InstanceConstSharedPtr& local_addr, - const Network::ConnectionSocket::OptionsSharedPtr& options); - - void onFileEvent(uint32_t events); - uint32_t packets_dropped_{0}; - Event::Dispatcher& dispatcher_; - Event::FileEventPtr file_event_; -}; - -} // namespace Quic -} // namespace Envoy diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_client_session.cc b/source/extensions/quic_listeners/quiche/envoy_quic_client_session.cc deleted file mode 100644 index f9aeb81df3230..0000000000000 --- a/source/extensions/quic_listeners/quiche/envoy_quic_client_session.cc +++ /dev/null @@ -1,87 +0,0 @@ -#include "extensions/quic_listeners/quiche/envoy_quic_client_session.h" - -namespace Envoy { -namespace Quic { - -EnvoyQuicClientSession::EnvoyQuicClientSession( - const quic::QuicConfig& config, const quic::ParsedQuicVersionVector& supported_versions, - std::unique_ptr connection, const quic::QuicServerId& server_id, - quic::QuicCryptoClientConfig* crypto_config, - quic::QuicClientPushPromiseIndex* push_promise_index, Event::Dispatcher& dispatcher, - uint32_t send_buffer_limit) - : QuicFilterManagerConnectionImpl(*connection, dispatcher, send_buffer_limit), - quic::QuicSpdyClientSession(config, supported_versions, connection.release(), server_id, - crypto_config, push_promise_index) { - Initialize(); -} - -EnvoyQuicClientSession::~EnvoyQuicClientSession() { - ASSERT(!connection()->connected()); - QuicFilterManagerConnectionImpl::quic_connection_ = nullptr; -} - -absl::string_view EnvoyQuicClientSession::requestedServerName() const { - return {GetCryptoStream()->crypto_negotiated_params().sni}; -} - -void EnvoyQuicClientSession::connect() { - dynamic_cast(quic_connection_)->setUpConnectionSocket(); -} - -void EnvoyQuicClientSession::OnConnectionClosed(const quic::QuicConnectionCloseFrame& frame, - quic::ConnectionCloseSource source) { - quic::QuicSpdyClientSession::OnConnectionClosed(frame, source); - onConnectionCloseEvent(frame, source); -} - -void EnvoyQuicClientSession::Initialize() { - quic::QuicSpdyClientSession::Initialize(); - quic_connection_->setEnvoyConnection(*this); -} - -void EnvoyQuicClientSession::OnGoAway(const quic::QuicGoAwayFrame& frame) { - ENVOY_CONN_LOG(debug, "GOAWAY received with error {}: {}", *this, - quic::QuicErrorCodeToString(frame.error_code), frame.reason_phrase); - quic::QuicSpdyClientSession::OnGoAway(frame); - if (http_connection_callbacks_ != nullptr) { - http_connection_callbacks_->onGoAway(); - } -} - -void EnvoyQuicClientSession::OnCryptoHandshakeEvent(CryptoHandshakeEvent event) { - quic::QuicSpdyClientSession::OnCryptoHandshakeEvent(event); - if (event == HANDSHAKE_CONFIRMED) { - raiseEvent(Network::ConnectionEvent::Connected); - } -} - -void EnvoyQuicClientSession::cryptoConnect() { - CryptoConnect(); - set_max_allowed_push_id(0u); - // Wait for finishing handshake with server. - dispatcher_.run(Event::Dispatcher::RunType::Block); -} - -std::unique_ptr EnvoyQuicClientSession::CreateClientStream() { - auto stream = std::make_unique(GetNextOutgoingBidirectionalStreamId(), - this, quic::BIDIRECTIONAL); - return stream; -} - -quic::QuicSpdyStream* EnvoyQuicClientSession::CreateIncomingStream(quic::QuicStreamId id) { - if (!ShouldCreateIncomingStream(id)) { - return nullptr; - } - auto stream = new EnvoyQuicClientStream(id, this, quic::READ_UNIDIRECTIONAL); - ActivateStream(std::unique_ptr(stream)); - return stream; -} - -quic::QuicSpdyStream* EnvoyQuicClientSession::CreateIncomingStream(quic::PendingStream* pending) { - auto stream = new EnvoyQuicClientStream(pending, this, quic::READ_UNIDIRECTIONAL); - ActivateStream(std::unique_ptr(stream)); - return stream; -} - -} // namespace Quic -} // namespace Envoy diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_client_session.h b/source/extensions/quic_listeners/quiche/envoy_quic_client_session.h deleted file mode 100644 index e5dd9862173db..0000000000000 --- a/source/extensions/quic_listeners/quiche/envoy_quic_client_session.h +++ /dev/null @@ -1,82 +0,0 @@ -#pragma once - -#pragma GCC diagnostic push -// QUICHE allows unused parameters. -#pragma GCC diagnostic ignored "-Wunused-parameter" -// QUICHE uses offsetof(). -#pragma GCC diagnostic ignored "-Winvalid-offsetof" -#pragma GCC diagnostic ignored "-Wtype-limits" - -#include "quiche/quic/core/http/quic_spdy_client_session.h" - -#pragma GCC diagnostic pop - -#include "extensions/quic_listeners/quiche/envoy_quic_client_stream.h" -#include "extensions/quic_listeners/quiche/envoy_quic_client_connection.h" -#include "extensions/quic_listeners/quiche/quic_filter_manager_connection_impl.h" - -namespace Envoy { -namespace Quic { - -// Act as a Network::ClientConnection to ClientCodec. -// TODO(danzh) This class doesn't need to inherit Network::FilterManager -// interface but need all other Network::Connection implementation in -// QuicFilterManagerConnectionImpl. Refactor QuicFilterManagerConnectionImpl to -// move FilterManager interface to EnvoyQuicServerSession. -class EnvoyQuicClientSession : public QuicFilterManagerConnectionImpl, - public quic::QuicSpdyClientSession, - public Network::ClientConnection { -public: - EnvoyQuicClientSession(const quic::QuicConfig& config, - const quic::ParsedQuicVersionVector& supported_versions, - std::unique_ptr connection, - const quic::QuicServerId& server_id, - quic::QuicCryptoClientConfig* crypto_config, - quic::QuicClientPushPromiseIndex* push_promise_index, - Event::Dispatcher& dispatcher, uint32_t send_buffer_limit); - - ~EnvoyQuicClientSession() override; - - // Called by QuicHttpClientConnectionImpl before creating data streams. - void setHttpConnectionCallbacks(Http::ConnectionCallbacks& callbacks) { - http_connection_callbacks_ = &callbacks; - } - - // Network::Connection - absl::string_view requestedServerName() const override; - - // Network::ClientConnection - // Only register socket and set socket options. - void connect() override; - - // quic::QuicSession - void OnConnectionClosed(const quic::QuicConnectionCloseFrame& frame, - quic::ConnectionCloseSource source) override; - void Initialize() override; - void OnGoAway(const quic::QuicGoAwayFrame& frame) override; - // quic::QuicSpdyClientSessionBase - void OnCryptoHandshakeEvent(CryptoHandshakeEvent event) override; - - // Do version negotiation and crypto handshake. Fail the connection if server - // doesn't support the one and only supported version. - // This call will block till the handshake finished with either success to - // failure. - void cryptoConnect(); - - using quic::QuicSpdyClientSession::stream_map; - -protected: - // quic::QuicSpdyClientSession - std::unique_ptr CreateClientStream() override; - // quic::QuicSpdySession - quic::QuicSpdyStream* CreateIncomingStream(quic::QuicStreamId id) override; - quic::QuicSpdyStream* CreateIncomingStream(quic::PendingStream* pending) override; - -private: - // These callbacks are owned by network filters and quic session should out live - // them. - Http::ConnectionCallbacks* http_connection_callbacks_{nullptr}; -}; - -} // namespace Quic -} // namespace Envoy diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_client_stream.cc b/source/extensions/quic_listeners/quiche/envoy_quic_client_stream.cc deleted file mode 100644 index 3fc24dea4e25c..0000000000000 --- a/source/extensions/quic_listeners/quiche/envoy_quic_client_stream.cc +++ /dev/null @@ -1,219 +0,0 @@ -#include "extensions/quic_listeners/quiche/envoy_quic_client_stream.h" - -#pragma GCC diagnostic push -// QUICHE allows unused parameters. -#pragma GCC diagnostic ignored "-Wunused-parameter" -// QUICHE uses offsetof(). -#pragma GCC diagnostic ignored "-Winvalid-offsetof" - -#include "quiche/quic/core/quic_session.h" -#include "quiche/quic/core/http/quic_header_list.h" -#include "quiche/quic/core/quic_session.h" -#include "quiche/spdy/core/spdy_header_block.h" -#include "extensions/quic_listeners/quiche/platform/quic_mem_slice_span_impl.h" - -#pragma GCC diagnostic pop - -#include "extensions/quic_listeners/quiche/envoy_quic_utils.h" -#include "extensions/quic_listeners/quiche/envoy_quic_client_session.h" - -#include "common/buffer/buffer_impl.h" -#include "common/http/header_map_impl.h" -#include "common/common/assert.h" - -namespace Envoy { -namespace Quic { - -EnvoyQuicClientStream::EnvoyQuicClientStream(quic::QuicStreamId id, - quic::QuicSpdyClientSession* client_session, - quic::StreamType type) - : quic::QuicSpdyClientStream(id, client_session, type), - EnvoyQuicStream( - session()->config()->GetInitialStreamFlowControlWindowToSend(), - [this]() { runLowWatermarkCallbacks(); }, [this]() { runHighWatermarkCallbacks(); }) {} - -EnvoyQuicClientStream::EnvoyQuicClientStream(quic::PendingStream* pending, - quic::QuicSpdyClientSession* client_session, - quic::StreamType type) - : quic::QuicSpdyClientStream(pending, client_session, type), - EnvoyQuicStream( - session()->config()->GetInitialStreamFlowControlWindowToSend(), - [this]() { runLowWatermarkCallbacks(); }, [this]() { runHighWatermarkCallbacks(); }) {} - -void EnvoyQuicClientStream::encode100ContinueHeaders(const Http::HeaderMap& headers) { - ASSERT(headers.Status()->value() == "100"); - encodeHeaders(headers, false); -} - -void EnvoyQuicClientStream::encodeHeaders(const Http::HeaderMap& headers, bool end_stream) { - ENVOY_STREAM_LOG(debug, "encodeHeaders: (end_stream={}) {}.", *this, end_stream, headers); - WriteHeaders(envoyHeadersToSpdyHeaderBlock(headers), end_stream, nullptr); - local_end_stream_ = end_stream; -} - -void EnvoyQuicClientStream::encodeData(Buffer::Instance& data, bool end_stream) { - ENVOY_STREAM_LOG(debug, "encodeData (end_stream={}) of {} bytes.", *this, end_stream, - data.length()); - local_end_stream_ = end_stream; - // This is counting not serialized bytes in the send buffer. - uint64_t bytes_to_send_old = BufferedDataBytes(); - // QUIC stream must take all. - WriteBodySlices(quic::QuicMemSliceSpan(quic::QuicMemSliceSpanImpl(data)), end_stream); - ASSERT(data.length() == 0); - - uint64_t bytes_to_send_new = BufferedDataBytes(); - ASSERT(bytes_to_send_old <= bytes_to_send_new); - if (bytes_to_send_new > bytes_to_send_old) { - // If buffered bytes changed, update stream and session's watermark book - // keeping. - sendBufferSimulation().checkHighWatermark(bytes_to_send_new); - dynamic_cast(session())->adjustBytesToSend(bytes_to_send_new - - bytes_to_send_old); - } -} - -void EnvoyQuicClientStream::encodeTrailers(const Http::HeaderMap& trailers) { - ASSERT(!local_end_stream_); - local_end_stream_ = true; - ENVOY_STREAM_LOG(debug, "encodeTrailers: {}.", *this, trailers); - WriteTrailers(envoyHeadersToSpdyHeaderBlock(trailers), nullptr); -} - -void EnvoyQuicClientStream::encodeMetadata(const Http::MetadataMapVector& /*metadata_map_vector*/) { - ASSERT(false, "Metadata Frame is not supported in QUIC"); -} - -void EnvoyQuicClientStream::resetStream(Http::StreamResetReason reason) { - // Higher layers expect calling resetStream() to immediately raise reset callbacks. - runResetCallbacks(reason); - - Reset(envoyResetReasonToQuicRstError(reason)); -} - -void EnvoyQuicClientStream::switchStreamBlockState(bool should_block) { - ASSERT(FinishedReadingHeaders(), - "codec buffer limit is reached before response body is delivered."); - if (should_block) { - sequencer()->SetBlockedUntilFlush(); - } else { - sequencer()->SetUnblocked(); - } -} - -void EnvoyQuicClientStream::OnInitialHeadersComplete(bool fin, size_t frame_len, - const quic::QuicHeaderList& header_list) { - quic::QuicSpdyStream::OnInitialHeadersComplete(fin, frame_len, header_list); - if (rst_sent()) { - return; - } - ASSERT(decoder() != nullptr); - ASSERT(headers_decompressed()); - decoder()->decodeHeaders(quicHeadersToEnvoyHeaders(header_list), /*end_stream=*/fin); - if (fin) { - end_stream_decoded_ = true; - } - ConsumeHeaderList(); -} - -void EnvoyQuicClientStream::OnBodyAvailable() { - ASSERT(FinishedReadingHeaders()); - ASSERT(read_disable_counter_ == 0); - ASSERT(!in_encode_data_callstack_); - in_encode_data_callstack_ = true; - - Buffer::InstancePtr buffer = std::make_unique(); - // TODO(danzh): check Envoy per stream buffer limit. - // Currently read out all the data. - while (HasBytesToRead()) { - struct iovec iov; - int num_regions = GetReadableRegions(&iov, 1); - ASSERT(num_regions > 0); - size_t bytes_read = iov.iov_len; - Buffer::RawSlice slice; - buffer->reserve(bytes_read, &slice, 1); - ASSERT(slice.len_ >= bytes_read); - slice.len_ = bytes_read; - memcpy(slice.mem_, iov.iov_base, iov.iov_len); - buffer->commit(&slice, 1); - MarkConsumed(bytes_read); - } - - // True if no trailer and FIN read. - bool finished_reading = IsDoneReading(); - bool empty_payload_with_fin = buffer->length() == 0 && finished_reading; - if (!empty_payload_with_fin || !end_stream_decoded_) { - ASSERT(decoder() != nullptr); - decoder()->decodeData(*buffer, finished_reading); - if (finished_reading) { - end_stream_decoded_ = true; - } - } - - if (!sequencer()->IsClosed()) { - in_encode_data_callstack_ = false; - if (read_disable_counter_ > 0) { - // If readDisable() was ever called during decodeData() and it meant to disable - // reading from downstream, the call must have been deferred. Call it now. - switchStreamBlockState(true); - } - return; - } - - if (!quic::VersionUsesQpack(transport_version()) && !FinishedReadingTrailers()) { - // For Google QUIC implementation, trailers may arrived earlier and wait to - // be consumed after reading all the body. Consume it here. - // IETF QUIC shouldn't reach here because trailers are sent on same stream. - decoder()->decodeTrailers(spdyHeaderBlockToEnvoyHeaders(received_trailers())); - MarkTrailersConsumed(); - } - OnFinRead(); - in_encode_data_callstack_ = false; -} - -void EnvoyQuicClientStream::OnTrailingHeadersComplete(bool fin, size_t frame_len, - const quic::QuicHeaderList& header_list) { - quic::QuicSpdyStream::OnTrailingHeadersComplete(fin, frame_len, header_list); - if (session()->connection()->connected() && - (quic::VersionUsesQpack(transport_version()) || sequencer()->IsClosed()) && - !FinishedReadingTrailers()) { - // Before QPack trailers can arrive before body. Only decode trailers after finishing decoding - // body. - ASSERT(decoder() != nullptr); - decoder()->decodeTrailers(spdyHeaderBlockToEnvoyHeaders(received_trailers())); - MarkTrailersConsumed(); - } -} - -void EnvoyQuicClientStream::OnStreamReset(const quic::QuicRstStreamFrame& frame) { - quic::QuicSpdyClientStream::OnStreamReset(frame); - runResetCallbacks(quicRstErrorToEnvoyResetReason(frame.error_code)); -} - -void EnvoyQuicClientStream::OnConnectionClosed(quic::QuicErrorCode error, - quic::ConnectionCloseSource source) { - quic::QuicSpdyClientStream::OnConnectionClosed(error, source); - runResetCallbacks(quicErrorCodeToEnvoyResetReason(error)); -} - -void EnvoyQuicClientStream::OnCanWrite() { - uint64_t buffered_data_old = BufferedDataBytes(); - quic::QuicSpdyClientStream::OnCanWrite(); - uint64_t buffered_data_new = BufferedDataBytes(); - // As long as OnCanWriteNewData() is no-op, data to sent in buffer shouldn't - // increase. - ASSERT(buffered_data_new <= buffered_data_old); - if (buffered_data_new < buffered_data_old) { - sendBufferSimulation().checkLowWatermark(buffered_data_new); - dynamic_cast(session())->adjustBytesToSend(buffered_data_new - - buffered_data_old); - } -} - -uint32_t EnvoyQuicClientStream::streamId() { return id(); } - -Network::Connection* EnvoyQuicClientStream::connection() { - return dynamic_cast(session()); -} - -} // namespace Quic -} // namespace Envoy diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_client_stream.h b/source/extensions/quic_listeners/quiche/envoy_quic_client_stream.h deleted file mode 100644 index 6bc88ff8a9e81..0000000000000 --- a/source/extensions/quic_listeners/quiche/envoy_quic_client_stream.h +++ /dev/null @@ -1,56 +0,0 @@ -#pragma once - -#pragma GCC diagnostic push -// QUICHE allows unused parameters. -#pragma GCC diagnostic ignored "-Wunused-parameter" -// QUICHE uses offsetof(). -#pragma GCC diagnostic ignored "-Winvalid-offsetof" -#include "quiche/quic/core/http/quic_spdy_client_stream.h" - -#pragma GCC diagnostic pop - -#include "extensions/quic_listeners/quiche/envoy_quic_stream.h" - -namespace Envoy { -namespace Quic { - -// This class is a quic stream and also a request encoder. -class EnvoyQuicClientStream : public quic::QuicSpdyClientStream, public EnvoyQuicStream { -public: - EnvoyQuicClientStream(quic::QuicStreamId id, quic::QuicSpdyClientSession* client_session, - quic::StreamType type); - EnvoyQuicClientStream(quic::PendingStream* pending, quic::QuicSpdyClientSession* client_session, - quic::StreamType type); - - // Http::StreamEncoder - void encode100ContinueHeaders(const Http::HeaderMap& headers) override; - void encodeHeaders(const Http::HeaderMap& headers, bool end_stream) override; - void encodeData(Buffer::Instance& data, bool end_stream) override; - void encodeTrailers(const Http::HeaderMap& trailers) override; - void encodeMetadata(const Http::MetadataMapVector& metadata_map_vector) override; - - // Http::Stream - void resetStream(Http::StreamResetReason reason) override; - // quic::QuicSpdyStream - void OnBodyAvailable() override; - void OnStreamReset(const quic::QuicRstStreamFrame& frame) override; - void OnCanWrite() override; - // quic::Stream - void OnConnectionClosed(quic::QuicErrorCode error, quic::ConnectionCloseSource source) override; - -protected: - // EnvoyQuicStream - void switchStreamBlockState(bool should_block) override; - uint32_t streamId() override; - Network::Connection* connection() override; - - // quic::QuicSpdyStream - // Overridden to pass headers to decoder. - void OnInitialHeadersComplete(bool fin, size_t frame_len, - const quic::QuicHeaderList& header_list) override; - void OnTrailingHeadersComplete(bool fin, size_t frame_len, - const quic::QuicHeaderList& header_list) override; -}; - -} // namespace Quic -} // namespace Envoy diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_connection.cc b/source/extensions/quic_listeners/quiche/envoy_quic_connection.cc index f2459bf79a190..dcc311a6eaac6 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_connection.cc +++ b/source/extensions/quic_listeners/quiche/envoy_quic_connection.cc @@ -19,10 +19,7 @@ EnvoyQuicConnection::EnvoyQuicConnection(const quic::QuicConnectionId& server_co EnvoyQuicConnection::~EnvoyQuicConnection() { connection_socket_->close(); } -uint64_t EnvoyQuicConnection::id() const { - ASSERT(envoy_connection_ != nullptr); - return envoy_connection_->id(); -} +uint64_t EnvoyQuicConnection::id() const { return envoy_connection_->id(); } } // namespace Quic } // namespace Envoy diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_dispatcher.cc b/source/extensions/quic_listeners/quiche/envoy_quic_dispatcher.cc index b4f103469d818..07126a2908588 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_dispatcher.cc +++ b/source/extensions/quic_listeners/quiche/envoy_quic_dispatcher.cc @@ -44,8 +44,7 @@ quic::QuicSession* EnvoyQuicDispatcher::CreateQuicSession( listener_stats_); auto quic_session = new EnvoyQuicServerSession( config(), quic::ParsedQuicVersionVector{version}, std::move(quic_connection), this, - session_helper(), crypto_config(), compressed_certs_cache(), dispatcher_, - listener_config_.perConnectionBufferLimitBytes()); + session_helper(), crypto_config(), compressed_certs_cache(), dispatcher_); quic_session->Initialize(); // Filter chain can't be retrieved here as self address is unknown at this // point. diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_fake_proof_verifier.h b/source/extensions/quic_listeners/quiche/envoy_quic_fake_proof_verifier.h index c8355717bccb8..0861e09fb4d9b 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_fake_proof_verifier.h +++ b/source/extensions/quic_listeners/quiche/envoy_quic_fake_proof_verifier.h @@ -48,8 +48,7 @@ class EnvoyQuicFakeProofVerifier : public quic::ProofVerifier { const quic::ProofVerifyContext* /*context*/, std::string* /*error_details*/, std::unique_ptr* /*details*/, std::unique_ptr /*callback*/) override { - // Cert SCT support is not enabled for fake ProofSource. - if (cert_sct == "" && certs.size() == 1 && certs[0] == "Fake cert") { + if (cert_sct == "Fake timestamp" && certs.size() == 1 && certs[0] == "Fake cert") { return quic::QUIC_SUCCESS; } return quic::QUIC_FAILURE; diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_server_session.cc b/source/extensions/quic_listeners/quiche/envoy_quic_server_session.cc index 456cc77da297e..021c390e474d5 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_server_session.cc +++ b/source/extensions/quic_listeners/quiche/envoy_quic_server_session.cc @@ -9,7 +9,6 @@ #include "quiche/quic/core/quic_crypto_server_stream.h" #pragma GCC diagnostic pop -#include "common/common/assert.h" #include "extensions/quic_listeners/quiche/envoy_quic_server_stream.h" namespace Envoy { @@ -19,17 +18,10 @@ EnvoyQuicServerSession::EnvoyQuicServerSession( const quic::QuicConfig& config, const quic::ParsedQuicVersionVector& supported_versions, std::unique_ptr connection, quic::QuicSession::Visitor* visitor, quic::QuicCryptoServerStream::Helper* helper, const quic::QuicCryptoServerConfig* crypto_config, - quic::QuicCompressedCertsCache* compressed_certs_cache, Event::Dispatcher& dispatcher, - uint32_t send_buffer_limit) + quic::QuicCompressedCertsCache* compressed_certs_cache, Event::Dispatcher& dispatcher) : quic::QuicServerSessionBase(config, supported_versions, connection.get(), visitor, helper, crypto_config, compressed_certs_cache), - QuicFilterManagerConnectionImpl(*connection, dispatcher, send_buffer_limit), - quic_connection_(std::move(connection)) {} - -EnvoyQuicServerSession::~EnvoyQuicServerSession() { - ASSERT(!quic_connection_->connected()); - QuicFilterManagerConnectionImpl::quic_connection_ = nullptr; -} + QuicFilterManagerConnectionImpl(std::move(connection), dispatcher) {} absl::string_view EnvoyQuicServerSession::requestedServerName() const { return {GetCryptoStream()->crypto_negotiated_params().sni}; diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_server_session.h b/source/extensions/quic_listeners/quiche/envoy_quic_server_session.h index 5c5561cbb7f52..d849aa0161cac 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_server_session.h +++ b/source/extensions/quic_listeners/quiche/envoy_quic_server_session.h @@ -30,9 +30,7 @@ class EnvoyQuicServerSession : public quic::QuicServerSessionBase, quic::QuicCryptoServerStream::Helper* helper, const quic::QuicCryptoServerConfig* crypto_config, quic::QuicCompressedCertsCache* compressed_certs_cache, - Event::Dispatcher& dispatcher, uint32_t send_buffer_limit); - - ~EnvoyQuicServerSession() override; + Event::Dispatcher& dispatcher); // Network::Connection absl::string_view requestedServerName() const override; @@ -50,8 +48,6 @@ class EnvoyQuicServerSession : public quic::QuicServerSessionBase, // quic::QuicSpdySession void OnCryptoHandshakeEvent(CryptoHandshakeEvent event) override; - using quic::QuicSession::stream_map; - protected: // quic::QuicServerSessionBase quic::QuicCryptoServerStreamBase* @@ -68,7 +64,6 @@ class EnvoyQuicServerSession : public quic::QuicServerSessionBase, private: void setUpRequestDecoder(EnvoyQuicStream& stream); - std::unique_ptr quic_connection_; // These callbacks are owned by network filters and quic session should out live // them. Http::ServerConnectionCallbacks* http_connection_callbacks_{nullptr}; diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_server_stream.cc b/source/extensions/quic_listeners/quiche/envoy_quic_server_stream.cc index 9f05182af88af..35bd63f811355 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_server_stream.cc +++ b/source/extensions/quic_listeners/quiche/envoy_quic_server_stream.cc @@ -3,7 +3,7 @@ #include #include -#include +#include #pragma GCC diagnostic push // QUICHE allows unused parameters. @@ -14,12 +14,10 @@ #include "quiche/quic/core/http/quic_header_list.h" #include "quiche/quic/core/quic_session.h" #include "quiche/spdy/core/spdy_header_block.h" -#include "extensions/quic_listeners/quiche/platform/quic_mem_slice_span_impl.h" + #pragma GCC diagnostic pop #include "extensions/quic_listeners/quiche/envoy_quic_utils.h" -#include "extensions/quic_listeners/quiche/envoy_quic_server_session.h" - #include "common/buffer/buffer_impl.h" #include "common/http/header_map_impl.h" #include "common/common/assert.h" @@ -27,86 +25,28 @@ namespace Envoy { namespace Quic { -EnvoyQuicServerStream::EnvoyQuicServerStream(quic::QuicStreamId id, quic::QuicSpdySession* session, - quic::StreamType type) - : quic::QuicSpdyServerStreamBase(id, session, type), - EnvoyQuicStream( - session->config()->GetInitialStreamFlowControlWindowToSend(), - [this]() { runLowWatermarkCallbacks(); }, [this]() { runHighWatermarkCallbacks(); }) {} - -EnvoyQuicServerStream::EnvoyQuicServerStream(quic::PendingStream* pending, - quic::QuicSpdySession* session, quic::StreamType type) - : quic::QuicSpdyServerStreamBase(pending, session, type), - EnvoyQuicStream( - session->config()->GetInitialStreamFlowControlWindowToSend(), - [this]() { runLowWatermarkCallbacks(); }, [this]() { runHighWatermarkCallbacks(); }) {} - void EnvoyQuicServerStream::encode100ContinueHeaders(const Http::HeaderMap& headers) { ASSERT(headers.Status()->value() == "100"); encodeHeaders(headers, false); } - -void EnvoyQuicServerStream::encodeHeaders(const Http::HeaderMap& headers, bool end_stream) { - ENVOY_STREAM_LOG(debug, "encodeHeaders (end_stream={}) {}.", *this, end_stream, headers); - WriteHeaders(envoyHeadersToSpdyHeaderBlock(headers), end_stream, nullptr); - local_end_stream_ = end_stream; +void EnvoyQuicServerStream::encodeHeaders(const Http::HeaderMap& /*headers*/, bool /*end_stream*/) { + NOT_IMPLEMENTED_GCOVR_EXCL_LINE; } - -void EnvoyQuicServerStream::encodeData(Buffer::Instance& data, bool end_stream) { - ENVOY_STREAM_LOG(debug, "encodeData (end_stream={}) of {} bytes.", *this, end_stream, - data.length()); - local_end_stream_ = end_stream; - // This is counting not serialized bytes in the send buffer. - uint64_t bytes_to_send_old = BufferedDataBytes(); - // QUIC stream must take all. - WriteBodySlices(quic::QuicMemSliceSpan(quic::QuicMemSliceSpanImpl(data)), end_stream); - ASSERT(data.length() == 0); - - uint64_t bytes_to_send_new = BufferedDataBytes(); - ASSERT(bytes_to_send_old <= bytes_to_send_new); - if (bytes_to_send_new > bytes_to_send_old) { - // If buffered bytes changed, update stream and session's watermark book - // keeping. - sendBufferSimulation().checkHighWatermark(bytes_to_send_new); - dynamic_cast(session())->adjustBytesToSend(bytes_to_send_new - - bytes_to_send_old); - } +void EnvoyQuicServerStream::encodeData(Buffer::Instance& /*data*/, bool /*end_stream*/) { + NOT_IMPLEMENTED_GCOVR_EXCL_LINE; } - -void EnvoyQuicServerStream::encodeTrailers(const Http::HeaderMap& trailers) { - ASSERT(!local_end_stream_); - local_end_stream_ = true; - ENVOY_STREAM_LOG(debug, "encodeTrailers: {}.", *this, trailers); - WriteTrailers(envoyHeadersToSpdyHeaderBlock(trailers), nullptr); +void EnvoyQuicServerStream::encodeTrailers(const Http::HeaderMap& /*trailers*/) { + NOT_IMPLEMENTED_GCOVR_EXCL_LINE; } - void EnvoyQuicServerStream::encodeMetadata(const Http::MetadataMapVector& /*metadata_map_vector*/) { - // Metadata Frame is not supported in QUIC. NOT_IMPLEMENTED_GCOVR_EXCL_LINE; } -void EnvoyQuicServerStream::resetStream(Http::StreamResetReason reason) { - // Higher layers expect calling resetStream() to immediately raise reset callbacks. - runResetCallbacks(reason); - if (local_end_stream_ && !reading_stopped()) { - // This is after 200 early response. Reset with QUIC_STREAM_NO_ERROR instead - // of propagating original reset reason. - StopReading(); - } else { - Reset(envoyResetReasonToQuicRstError(reason)); - } +void EnvoyQuicServerStream::resetStream(Http::StreamResetReason /*reason*/) { + NOT_IMPLEMENTED_GCOVR_EXCL_LINE; } -void EnvoyQuicServerStream::switchStreamBlockState(bool should_block) { - ASSERT(FinishedReadingHeaders(), - "Upperstream buffer limit is reached before request body is delivered."); - if (should_block) { - sequencer()->SetBlockedUntilFlush(); - } else { - ASSERT(read_disable_counter_ == 0, "readDisable called in btw."); - sequencer()->SetUnblocked(); - } -} +void EnvoyQuicServerStream::readDisable(bool /*disable*/) { NOT_IMPLEMENTED_GCOVR_EXCL_LINE; } void EnvoyQuicServerStream::OnInitialHeadersComplete(bool fin, size_t frame_len, const quic::QuicHeaderList& header_list) { @@ -114,18 +54,10 @@ void EnvoyQuicServerStream::OnInitialHeadersComplete(bool fin, size_t frame_len, ASSERT(decoder() != nullptr); ASSERT(headers_decompressed()); decoder()->decodeHeaders(quicHeadersToEnvoyHeaders(header_list), /*end_stream=*/fin); - if (fin) { - end_stream_decoded_ = true; - } ConsumeHeaderList(); } void EnvoyQuicServerStream::OnBodyAvailable() { - ASSERT(FinishedReadingHeaders()); - ASSERT(read_disable_counter_ == 0); - ASSERT(!in_encode_data_callstack_); - in_encode_data_callstack_ = true; - Buffer::InstancePtr buffer = std::make_unique(); // TODO(danzh): check Envoy per stream buffer limit. // Currently read out all the data. @@ -145,34 +77,18 @@ void EnvoyQuicServerStream::OnBodyAvailable() { // True if no trailer and FIN read. bool finished_reading = IsDoneReading(); - bool empty_payload_with_fin = buffer->length() == 0 && finished_reading; - if (!empty_payload_with_fin || !end_stream_decoded_) { - ASSERT(decoder() != nullptr); - decoder()->decodeData(*buffer, finished_reading); - if (finished_reading) { - end_stream_decoded_ = true; - } - } - - if (!sequencer()->IsClosed()) { - in_encode_data_callstack_ = false; - if (read_disable_counter_ > 0) { - // If readDisable() was ever called during decodeData() and it meant to disable - // reading from downstream, the call must have been deferred. Call it now. - switchStreamBlockState(true); - } - return; - } - - if (!quic::VersionUsesQpack(transport_version()) && !FinishedReadingTrailers()) { + // If this is the last stream data, set end_stream if there is no + // trailers. + ASSERT(decoder() != nullptr); + decoder()->decodeData(*buffer, finished_reading); + if (!quic::VersionUsesQpack(transport_version()) && sequencer()->IsClosed() && + !FinishedReadingTrailers()) { // For Google QUIC implementation, trailers may arrived earlier and wait to // be consumed after reading all the body. Consume it here. // IETF QUIC shouldn't reach here because trailers are sent on same stream. decoder()->decodeTrailers(spdyHeaderBlockToEnvoyHeaders(received_trailers())); MarkTrailersConsumed(); } - OnFinRead(); - in_encode_data_callstack_ = false; } void EnvoyQuicServerStream::OnTrailingHeadersComplete(bool fin, size_t frame_len, @@ -191,33 +107,25 @@ void EnvoyQuicServerStream::OnTrailingHeadersComplete(bool fin, size_t frame_len void EnvoyQuicServerStream::OnStreamReset(const quic::QuicRstStreamFrame& frame) { quic::QuicSpdyServerStreamBase::OnStreamReset(frame); - runResetCallbacks(quicRstErrorToEnvoyResetReason(frame.error_code)); + Http::StreamResetReason reason; + if (frame.error_code == quic::QUIC_REFUSED_STREAM) { + reason = Http::StreamResetReason::RemoteRefusedStreamReset; + } else { + reason = Http::StreamResetReason::RemoteReset; + } + runResetCallbacks(reason); } void EnvoyQuicServerStream::OnConnectionClosed(quic::QuicErrorCode error, quic::ConnectionCloseSource source) { quic::QuicSpdyServerStreamBase::OnConnectionClosed(error, source); - runResetCallbacks(quicErrorCodeToEnvoyResetReason(error)); -} - -void EnvoyQuicServerStream::OnCanWrite() { - uint64_t buffered_data_old = BufferedDataBytes(); - quic::QuicSpdyServerStreamBase::OnCanWrite(); - uint64_t buffered_data_new = BufferedDataBytes(); - // As long as OnCanWriteNewData() is no-op, data to sent in buffer shouldn't - // increase. - ASSERT(buffered_data_new <= buffered_data_old); - if (buffered_data_new < buffered_data_old) { - sendBufferSimulation().checkLowWatermark(buffered_data_new); - dynamic_cast(session())->adjustBytesToSend(buffered_data_new - - buffered_data_old); + Http::StreamResetReason reason; + if (error == quic::QUIC_NO_ERROR) { + reason = Http::StreamResetReason::ConnectionTermination; + } else { + reason = Http::StreamResetReason::ConnectionFailure; } -} - -uint32_t EnvoyQuicServerStream::streamId() { return id(); } - -Network::Connection* EnvoyQuicServerStream::connection() { - return dynamic_cast(session()); + runResetCallbacks(reason); } } // namespace Quic diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_server_stream.h b/source/extensions/quic_listeners/quiche/envoy_quic_server_stream.h index e38317907d8ec..047970f4fdbed 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_server_stream.h +++ b/source/extensions/quic_listeners/quiche/envoy_quic_server_stream.h @@ -18,10 +18,11 @@ namespace Quic { class EnvoyQuicServerStream : public quic::QuicSpdyServerStreamBase, public EnvoyQuicStream { public: EnvoyQuicServerStream(quic::QuicStreamId id, quic::QuicSpdySession* session, - quic::StreamType type); - + quic::StreamType type) + : quic::QuicSpdyServerStreamBase(id, session, type) {} EnvoyQuicServerStream(quic::PendingStream* pending, quic::QuicSpdySession* session, - quic::StreamType type); + quic::StreamType type) + : quic::QuicSpdyServerStreamBase(pending, session, type) {} // Http::StreamEncoder void encode100ContinueHeaders(const Http::HeaderMap& headers) override; @@ -32,19 +33,14 @@ class EnvoyQuicServerStream : public quic::QuicSpdyServerStreamBase, public Envo // Http::Stream void resetStream(Http::StreamResetReason reason) override; + void readDisable(bool disable) override; // quic::QuicSpdyStream void OnBodyAvailable() override; void OnStreamReset(const quic::QuicRstStreamFrame& frame) override; - void OnCanWrite() override; // quic::QuicServerSessionBase void OnConnectionClosed(quic::QuicErrorCode error, quic::ConnectionCloseSource source) override; protected: - // EnvoyQuicStream - void switchStreamBlockState(bool should_block) override; - uint32_t streamId() override; - Network::Connection* connection() override; - // quic::QuicSpdyStream void OnInitialHeadersComplete(bool fin, size_t frame_len, const quic::QuicHeaderList& header_list) override; diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_simulated_watermark_buffer.h b/source/extensions/quic_listeners/quiche/envoy_quic_simulated_watermark_buffer.h deleted file mode 100644 index 0602bd39477f5..0000000000000 --- a/source/extensions/quic_listeners/quiche/envoy_quic_simulated_watermark_buffer.h +++ /dev/null @@ -1,62 +0,0 @@ -#pragma once - -#include - -#include "spdlog/spdlog.h" - -namespace Envoy { -namespace Quic { - -// A class, together with a stand alone buffer, used to achieve the purpose of WatermarkBuffer. -// Itself doesn't have buffer or do bookeeping of buffered bytes. But provided with buffered_bytes, -// it re-acts upon crossing high/low watermarks. -class EnvoyQuicSimulatedWatermarkBuffer { -public: - EnvoyQuicSimulatedWatermarkBuffer(uint32_t low_watermark, uint32_t high_watermark, - std::function below_low_watermark, - std::function above_high_watermark, - spdlog::logger& logger) - : low_watermark_(low_watermark), high_watermark_(high_watermark), - below_low_watermark_(std::move(below_low_watermark)), - above_high_watermark_(std::move(above_high_watermark)), logger_(logger) { - ASSERT((high_watermark == 0 && low_watermark == 0) || (high_watermark_ > low_watermark_)); - } - - void checkHighWatermark(uint32_t bytes_buffered) { - if (high_watermark_ > 0 && !is_above_high_watermark_ && bytes_buffered > high_watermark_) { - // Just exceeds high watermark. - ENVOY_LOG_TO_LOGGER(logger_, debug, "Buffered {} bytes, cross high watermark {}", - bytes_buffered, high_watermark_); - is_above_high_watermark_ = true; - is_below_low_watermark_ = false; - above_high_watermark_(); - } - } - - void checkLowWatermark(uint32_t bytes_buffered) { - if (low_watermark_ > 0 && !is_below_low_watermark_ && bytes_buffered < low_watermark_) { - // Just cross low watermark. - ENVOY_LOG_TO_LOGGER(logger_, debug, "Buffered {} bytes, cross low watermark {}", - bytes_buffered, low_watermark_); - is_below_low_watermark_ = true; - is_above_high_watermark_ = false; - below_low_watermark_(); - } - } - - bool isAboveHighWatermark() const { return is_above_high_watermark_; } - - bool isBelowLowWatermark() const { return is_below_low_watermark_; } - -private: - uint32_t low_watermark_{0}; - bool is_below_low_watermark_{true}; - uint32_t high_watermark_{0}; - bool is_above_high_watermark_{false}; - std::function below_low_watermark_; - std::function above_high_watermark_; - spdlog::logger& logger_; -}; - -} // namespace Quic -} // namespace Envoy diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_stream.h b/source/extensions/quic_listeners/quiche/envoy_quic_stream.h index 0ff2768386251..a6126224cc688 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_stream.h +++ b/source/extensions/quic_listeners/quiche/envoy_quic_stream.h @@ -1,92 +1,41 @@ #pragma once -#include "envoy/event/dispatcher.h" #include "envoy/http/codec.h" #include "common/http/codec_helper.h" -#include "extensions/quic_listeners/quiche/envoy_quic_simulated_watermark_buffer.h" - namespace Envoy { namespace Quic { // Base class for EnvoyQuicServer|ClientStream. class EnvoyQuicStream : public Http::StreamEncoder, public Http::Stream, - public Http::StreamCallbackHelper, - protected Logger::Loggable { + public Http::StreamCallbackHelper { public: - EnvoyQuicStream(uint32_t buffer_limit, std::function below_low_watermark, - std::function above_high_watermark) - : send_buffer_simulation_(buffer_limit / 2, buffer_limit, std::move(below_low_watermark), - std::move(above_high_watermark), ENVOY_LOGGER()) {} - // Http::StreamEncoder Stream& getStream() override { return *this; } // Http::Stream - void readDisable(bool disable) override { - bool status_changed{false}; - if (disable) { - ++read_disable_counter_; - ASSERT(read_disable_counter_ == 1); - if (read_disable_counter_ == 1) { - status_changed = true; - } - } else { - ASSERT(read_disable_counter_ > 0); - --read_disable_counter_; - if (read_disable_counter_ == 0) { - status_changed = true; - } - } - - if (status_changed && !in_encode_data_callstack_) { - switchStreamBlockState(disable); - } - } - void addCallbacks(Http::StreamCallbacks& callbacks) override { ASSERT(!local_end_stream_); addCallbacks_(callbacks); } void removeCallbacks(Http::StreamCallbacks& callbacks) override { removeCallbacks_(callbacks); } - uint32_t bufferLimit() override { return quic::kStreamReceiveWindowLimit; } + uint32_t bufferLimit() override { NOT_IMPLEMENTED_GCOVR_EXCL_LINE; } // Needs to be called during quic stream creation before the stream receives // any headers and data. void setDecoder(Http::StreamDecoder& decoder) { decoder_ = &decoder; } protected: - virtual void switchStreamBlockState(bool should_block) PURE; - - // Needed for ENVOY_STREAM_LOG. - virtual uint32_t streamId() PURE; - virtual Network::Connection* connection() PURE; - Http::StreamDecoder* decoder() { ASSERT(decoder_ != nullptr); return decoder_; } - EnvoyQuicSimulatedWatermarkBuffer& sendBufferSimulation() { return send_buffer_simulation_; } - // True once end of stream is propergated to Envoy. Envoy doesn't expect to be - // notified more than once about end of stream. So once this is true, no need - // to set it in the callback to Envoy stream any more. - bool end_stream_decoded_{false}; - int32_t read_disable_counter_{0}; - // If true, switchStreamBlockState() should be deferred till this variable - // becomes false. - bool in_encode_data_callstack_{false}; - private: // Not owned. Http::StreamDecoder* decoder_{nullptr}; - // Keeps the buffer state of the connection, and react upon the changes of how many bytes are - // buffered cross all streams' send buffer. The state is evaluated and may be changed upon each - // stream write. QUICHE doesn't buffer data in connection, all the data is buffered in stream's - // send buffer. - EnvoyQuicSimulatedWatermarkBuffer send_buffer_simulation_; }; } // namespace Quic diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_utils.cc b/source/extensions/quic_listeners/quiche/envoy_quic_utils.cc index 6be73faef9546..33e0c43fc035b 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_utils.cc +++ b/source/extensions/quic_listeners/quiche/envoy_quic_utils.cc @@ -62,50 +62,5 @@ Http::HeaderMapImplPtr spdyHeaderBlockToEnvoyHeaders(const spdy::SpdyHeaderBlock return headers; } -spdy::SpdyHeaderBlock envoyHeadersToSpdyHeaderBlock(const Http::HeaderMap& headers) { - spdy::SpdyHeaderBlock header_block; - headers.iterate( - [](const Http::HeaderEntry& header, void* context) -> Http::HeaderMap::Iterate { - auto spdy_headers = static_cast(context); - // The key-value pairs are copied. - spdy_headers->insert({header.key().getStringView(), header.value().getStringView()}); - return Http::HeaderMap::Iterate::Continue; - }, - &header_block); - return header_block; -} - -quic::QuicRstStreamErrorCode envoyResetReasonToQuicRstError(Http::StreamResetReason reason) { - switch (reason) { - case Http::StreamResetReason::LocalRefusedStreamReset: - return quic::QUIC_REFUSED_STREAM; - case Http::StreamResetReason::ConnectionFailure: - return quic::QUIC_STREAM_CONNECTION_ERROR; - case Http::StreamResetReason::LocalReset: - return quic::QUIC_STREAM_CANCELLED; - case Http::StreamResetReason::ConnectionTermination: - return quic::QUIC_STREAM_NO_ERROR; - default: - return quic::QUIC_BAD_APPLICATION_PAYLOAD; - } -} - -Http::StreamResetReason quicRstErrorToEnvoyResetReason(quic::QuicRstStreamErrorCode rst_err) { - switch (rst_err) { - case quic::QUIC_REFUSED_STREAM: - return Http::StreamResetReason::RemoteRefusedStreamReset; - default: - return Http::StreamResetReason::RemoteReset; - } -} - -Http::StreamResetReason quicErrorCodeToEnvoyResetReason(quic::QuicErrorCode error) { - if (error == quic::QUIC_NO_ERROR) { - return Http::StreamResetReason::ConnectionTermination; - } else { - return Http::StreamResetReason::ConnectionFailure; - } -} - } // namespace Quic } // namespace Envoy diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_utils.h b/source/extensions/quic_listeners/quiche/envoy_quic_utils.h index 6505b22a66e28..54b1bf07f6036 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_utils.h +++ b/source/extensions/quic_listeners/quiche/envoy_quic_utils.h @@ -25,16 +25,5 @@ Http::HeaderMapImplPtr quicHeadersToEnvoyHeaders(const quic::QuicHeaderList& hea Http::HeaderMapImplPtr spdyHeaderBlockToEnvoyHeaders(const spdy::SpdyHeaderBlock& header_block); -spdy::SpdyHeaderBlock envoyHeadersToSpdyHeaderBlock(const Http::HeaderMap& headers); - -// Called when Envoy wants to reset the underlying QUIC stream. -quic::QuicRstStreamErrorCode envoyResetReasonToQuicRstError(Http::StreamResetReason reason); - -// Called when a RST_STREAM frame is received. -Http::StreamResetReason quicRstErrorToEnvoyResetReason(quic::QuicRstStreamErrorCode rst_err); - -// Called when underlying QUIC connection is closed either locally or by peer. -Http::StreamResetReason quicErrorCodeToEnvoyResetReason(quic::QuicErrorCode error); - } // namespace Quic } // namespace Envoy diff --git a/source/extensions/quic_listeners/quiche/quic_filter_manager_connection_impl.cc b/source/extensions/quic_listeners/quiche/quic_filter_manager_connection_impl.cc index 4baaed3a2c8dd..edfe62051d3cf 100644 --- a/source/extensions/quic_listeners/quiche/quic_filter_manager_connection_impl.cc +++ b/source/extensions/quic_listeners/quiche/quic_filter_manager_connection_impl.cc @@ -5,17 +5,14 @@ namespace Envoy { namespace Quic { -QuicFilterManagerConnectionImpl::QuicFilterManagerConnectionImpl(EnvoyQuicConnection& connection, - Event::Dispatcher& dispatcher, - uint32_t send_buffer_limit) - : quic_connection_(&connection), dispatcher_(dispatcher), filter_manager_(*this), +QuicFilterManagerConnectionImpl::QuicFilterManagerConnectionImpl( + std::unique_ptr connection, Event::Dispatcher& dispatcher) + : quic_connection_(std::move(connection)), filter_manager_(*this), dispatcher_(dispatcher), // QUIC connection id can be 18 bytes. It's easier to use hash value instead // of trying to map it into a 64-bit space. - stream_info_(dispatcher.timeSource()), id_(quic_connection_->connection_id().Hash()), - write_buffer_watermark_simulation_( - send_buffer_limit / 2, send_buffer_limit, [this]() { onSendBufferLowWatermark(); }, - [this]() { onSendBufferHighWatermark(); }, ENVOY_LOGGER()) { - stream_info_.protocol(Http::Protocol::Http3); + stream_info_(dispatcher.timeSource()), id_(quic_connection_->connection_id().Hash()) { + // TODO(danzh): Use QUIC specific enum value. + stream_info_.protocol(Http::Protocol::Http2); } void QuicFilterManagerConnectionImpl::addWriteFilter(Network::WriteFilterSharedPtr filter) { @@ -45,26 +42,16 @@ void QuicFilterManagerConnectionImpl::enableHalfClose(bool enabled) { void QuicFilterManagerConnectionImpl::setBufferLimits(uint32_t /*limit*/) { // TODO(danzh): add interface to quic for connection level buffer throttling. // Currently read buffer is capped by connection level flow control. And - // write buffer limit is set during construction. Change buffer limit during - // the life time of connection is not supported. + // write buffer is not capped. NOT_REACHED_GCOVR_EXCL_LINE; } -bool QuicFilterManagerConnectionImpl::aboveHighWatermark() const { - return write_buffer_watermark_simulation_.isAboveHighWatermark(); -} - void QuicFilterManagerConnectionImpl::close(Network::ConnectionCloseType type) { if (type != Network::ConnectionCloseType::NoFlush) { // TODO(danzh): Implement FlushWrite and FlushWriteAndDelay mode. } - if (quic_connection_ == nullptr) { - // Already detached from quic connection. - return; - } quic_connection_->CloseConnection(quic::QUIC_NO_ERROR, "Closed by application", quic::ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET); - quic_connection_ = nullptr; } void QuicFilterManagerConnectionImpl::setDelayedCloseTimeout(std::chrono::milliseconds timeout) { @@ -109,22 +96,14 @@ void QuicFilterManagerConnectionImpl::rawWrite(Buffer::Instance& /*data*/, bool NOT_REACHED_GCOVR_EXCL_LINE; } -void QuicFilterManagerConnectionImpl::adjustBytesToSend(int64_t delta) { - bytes_to_send_ += delta; - write_buffer_watermark_simulation_.checkHighWatermark(bytes_to_send_); - write_buffer_watermark_simulation_.checkLowWatermark(bytes_to_send_); -} - void QuicFilterManagerConnectionImpl::onConnectionCloseEvent( const quic::QuicConnectionCloseFrame& frame, quic::ConnectionCloseSource source) { + // Tell network callbacks about connection close. + raiseEvent(source == quic::ConnectionCloseSource::FROM_PEER + ? Network::ConnectionEvent::RemoteClose + : Network::ConnectionEvent::LocalClose); transport_failure_reason_ = absl::StrCat(quic::QuicErrorCodeToString(frame.quic_error_code), " with details: ", frame.error_details); - if (quic_connection_ != nullptr) { - // Tell network callbacks about connection close if not detached yet. - raiseEvent(source == quic::ConnectionCloseSource::FROM_PEER - ? Network::ConnectionEvent::RemoteClose - : Network::ConnectionEvent::LocalClose); - } } void QuicFilterManagerConnectionImpl::raiseEvent(Network::ConnectionEvent event) { @@ -133,19 +112,5 @@ void QuicFilterManagerConnectionImpl::raiseEvent(Network::ConnectionEvent event) } } -void QuicFilterManagerConnectionImpl::onSendBufferHighWatermark() { - ENVOY_CONN_LOG(trace, "onSendBufferHighWatermark", *this); - for (auto callback : network_connection_callbacks_) { - callback->onAboveWriteBufferHighWatermark(); - } -} - -void QuicFilterManagerConnectionImpl::onSendBufferLowWatermark() { - ENVOY_CONN_LOG(trace, "onSendBufferLowWatermark", *this); - for (auto callback : network_connection_callbacks_) { - callback->onBelowWriteBufferLowWatermark(); - } -} - } // namespace Quic } // namespace Envoy diff --git a/source/extensions/quic_listeners/quiche/quic_filter_manager_connection_impl.h b/source/extensions/quic_listeners/quiche/quic_filter_manager_connection_impl.h index a72364d334e53..fc8f57df6a039 100644 --- a/source/extensions/quic_listeners/quiche/quic_filter_manager_connection_impl.h +++ b/source/extensions/quic_listeners/quiche/quic_filter_manager_connection_impl.h @@ -9,7 +9,6 @@ #include "common/stream_info/stream_info_impl.h" #include "extensions/quic_listeners/quiche/envoy_quic_connection.h" -#include "extensions/quic_listeners/quiche/envoy_quic_simulated_watermark_buffer.h" namespace Envoy { namespace Quic { @@ -18,8 +17,8 @@ namespace Quic { class QuicFilterManagerConnectionImpl : public Network::FilterManagerConnection, protected Logger::Loggable { public: - QuicFilterManagerConnectionImpl(EnvoyQuicConnection& connection, Event::Dispatcher& dispatcher, - uint32_t send_buffer_limit); + QuicFilterManagerConnectionImpl(std::unique_ptr connection, + Event::Dispatcher& dispatcher); // Network::FilterManager // Overridden to delegate calls to filter_manager_. @@ -61,10 +60,8 @@ class QuicFilterManagerConnectionImpl : public Network::FilterManagerConnection, } Ssl::ConnectionInfoConstSharedPtr ssl() const override; Network::Connection::State state() const override { - if (quic_connection_ != nullptr && quic_connection_->connected()) { - return Network::Connection::State::Open; - } - return Network::Connection::State::Closed; + return quic_connection_->connected() ? Network::Connection::State::Open + : Network::Connection::State::Closed; } void write(Buffer::Instance& /*data*/, bool /*end_stream*/) override { // All writes should be handled by Quic internally. @@ -79,8 +76,11 @@ class QuicFilterManagerConnectionImpl : public Network::FilterManagerConnection, // SO_ORIGINAL_DST not supported by QUIC. NOT_REACHED_GCOVR_EXCL_LINE; } - bool aboveHighWatermark() const override; - + bool aboveHighWatermark() const override { + // TODO(danzh) Aggregate the write buffer usage cross all the streams and + // add an upper limit for this connection. + NOT_IMPLEMENTED_GCOVR_EXCL_LINE; + } const Network::ConnectionSocket::OptionsSharedPtr& socketOptions() const override; StreamInfo::StreamInfo& streamInfo() override { return stream_info_; } const StreamInfo::StreamInfo& streamInfo() const override { return stream_info_; } @@ -97,8 +97,6 @@ class QuicFilterManagerConnectionImpl : public Network::FilterManagerConnection, // Network::WriteBufferSource Network::StreamBuffer getWriteBuffer() override { NOT_REACHED_GCOVR_EXCL_LINE; } - void adjustBytesToSend(int64_t delta); - protected: // Propagate connection close to network_connection_callbacks_. void onConnectionCloseEvent(const quic::QuicConnectionCloseFrame& frame, @@ -106,31 +104,23 @@ class QuicFilterManagerConnectionImpl : public Network::FilterManagerConnection, void raiseEvent(Network::ConnectionEvent event); - EnvoyQuicConnection* quic_connection_{nullptr}; + std::unique_ptr quic_connection_; // TODO(danzh): populate stats. std::unique_ptr stats_; - Event::Dispatcher& dispatcher_; private: - // Called when aggregated buffered bytes across all the streams exceeds high watermark. - void onSendBufferHighWatermark(); - // Called when aggregated buffered bytes across all the streams declines to low watermark. - void onSendBufferLowWatermark(); - // Currently ConnectionManagerImpl is the one and only filter. If more network // filters are added, ConnectionManagerImpl should always be the last one. // Its onRead() is only called once to trigger ReadFilter::onNewConnection() // and the rest incoming data bypasses these filters. Network::FilterManagerImpl filter_manager_; - + Event::Dispatcher& dispatcher_; StreamInfo::StreamInfoImpl stream_info_; // These callbacks are owned by network filters and quic session should out live // them. std::list network_connection_callbacks_; std::string transport_failure_reason_; const uint64_t id_; - uint32_t bytes_to_send_{0}; - EnvoyQuicSimulatedWatermarkBuffer write_buffer_watermark_simulation_; }; } // namespace Quic diff --git a/test/config/utility.cc b/test/config/utility.cc index d98be4ec59524..2cd563e33e62b 100644 --- a/test/config/utility.cc +++ b/test/config/utility.cc @@ -113,37 +113,6 @@ const std::string ConfigHelper::HTTP_PROXY_CONFIG = BASE_CONFIG + R"EOF( name: route_config_0 )EOF"; -const std::string ConfigHelper::QUIC_HTTP_PROXY_CONFIG = BASE_UDP_LISTENER_CONFIG + R"EOF( - filter_chains: - transport_socket: - name: quic - filters: - name: envoy.http_connection_manager - config: - stat_prefix: config_test - http_filters: - name: envoy.router - codec_type: HTTP3 - access_log: - name: envoy.file_access_log - filter: - not_health_check_filter: {} - config: - path: /dev/null - route_config: - virtual_hosts: - name: integration - routes: - route: - cluster: cluster_0 - match: - prefix: "/" - domains: "*" - name: route_config_0 - udp_listener_config: - udp_listener_name: "quiche_quic_listener" -)EOF"; - const std::string ConfigHelper::DEFAULT_BUFFER_FILTER = R"EOF( name: envoy.buffer diff --git a/test/config/utility.h b/test/config/utility.h index ada7f71906f51..aa03d8583a6f3 100644 --- a/test/config/utility.h +++ b/test/config/utility.h @@ -78,8 +78,7 @@ class ConfigHelper { static const std::string TCP_PROXY_CONFIG; // A basic configuration for L7 proxying. static const std::string HTTP_PROXY_CONFIG; - // A basic configuration for L7 proxying with QUIC transport. - static const std::string QUIC_HTTP_PROXY_CONFIG; + // A string for a basic buffer filter, which can be used with addFilter() static const std::string DEFAULT_BUFFER_FILTER; // A string for a small buffer filter, which can be used with addFilter() diff --git a/test/extensions/quic_listeners/quiche/BUILD b/test/extensions/quic_listeners/quiche/BUILD index 1af4834e518ec..74659b5829587 100644 --- a/test/extensions/quic_listeners/quiche/BUILD +++ b/test/extensions/quic_listeners/quiche/BUILD @@ -62,8 +62,7 @@ envoy_cc_test( "//source/extensions/quic_listeners/quiche:envoy_quic_alarm_factory_lib", "//source/extensions/quic_listeners/quiche:envoy_quic_connection_helper_lib", "//source/extensions/quic_listeners/quiche:envoy_quic_server_connection_lib", - "//source/extensions/quic_listeners/quiche:envoy_quic_server_session_lib", - "//test/mocks/http:http_mocks", + "//source/extensions/quic_listeners/quiche:envoy_quic_server_stream_lib", "//test/mocks/http:stream_decoder_mock", "//test/mocks/network:network_mocks", "//test/test_common:utility_lib", diff --git a/test/extensions/quic_listeners/quiche/envoy_quic_dispatcher_test.cc b/test/extensions/quic_listeners/quiche/envoy_quic_dispatcher_test.cc index d8d0146f0e1ff..2d1c44a4ab4f7 100644 --- a/test/extensions/quic_listeners/quiche/envoy_quic_dispatcher_test.cc +++ b/test/extensions/quic_listeners/quiche/envoy_quic_dispatcher_test.cc @@ -84,8 +84,6 @@ class EnvoyQuicDispatcherTest : public testing::TestWithParam read_filter(new Network::MockReadFilter()); Network::MockConnectionCallbacks network_connection_callbacks; - testing::StrictMock read_total; - testing::StrictMock read_current; - testing::StrictMock write_total; - testing::StrictMock write_current; - std::vector filter_factory( {[&](Network::FilterManager& filter_manager) { filter_manager.addReadFilter(read_filter); read_filter->callbacks_->connection().addConnectionCallbacks(network_connection_callbacks); - read_filter->callbacks_->connection().setConnectionStats( - {read_total, read_current, write_total, write_current, nullptr, nullptr}); }}); EXPECT_CALL(filter_chain, networkFilterFactories()).WillOnce(ReturnRef(filter_factory)); EXPECT_CALL(listener_config_, filterChainFactory()); diff --git a/test/extensions/quic_listeners/quiche/envoy_quic_proof_source_test.cc b/test/extensions/quic_listeners/quiche/envoy_quic_proof_source_test.cc index 57bdf94e9e1f0..4737a532f558b 100644 --- a/test/extensions/quic_listeners/quiche/envoy_quic_proof_source_test.cc +++ b/test/extensions/quic_listeners/quiche/envoy_quic_proof_source_test.cc @@ -62,8 +62,8 @@ TEST_F(EnvoyQuicFakeProofSourceTest, TestGetProof) { TEST_F(EnvoyQuicFakeProofSourceTest, TestVerifyProof) { EXPECT_EQ(quic::QUIC_SUCCESS, proof_verifier_.VerifyProof(hostname_, /*port=*/0, server_config_, version_, chlo_hash_, - expected_certs_, "", expected_signature_, nullptr, nullptr, - nullptr, nullptr)); + expected_certs_, "Fake timestamp", expected_signature_, + nullptr, nullptr, nullptr, nullptr)); std::vector wrong_certs{"wrong cert"}; EXPECT_EQ(quic::QUIC_FAILURE, proof_verifier_.VerifyProof(hostname_, /*port=*/0, server_config_, version_, chlo_hash_, diff --git a/test/extensions/quic_listeners/quiche/envoy_quic_server_session_test.cc b/test/extensions/quic_listeners/quiche/envoy_quic_server_session_test.cc index 419953ca523f8..013c98851be4d 100644 --- a/test/extensions/quic_listeners/quiche/envoy_quic_server_session_test.cc +++ b/test/extensions/quic_listeners/quiche/envoy_quic_server_session_test.cc @@ -87,8 +87,7 @@ class EnvoyQuicServerSessionTest : public testing::TestWithParam { envoy_quic_session_(quic_config_, quic_version_, std::unique_ptr(quic_connection_), /*visitor=*/nullptr, &crypto_stream_helper_, &crypto_config_, - &compressed_certs_cache_, *dispatcher_, - /*send_buffer_limit*/ 1024 * 1024), + &compressed_certs_cache_, *dispatcher_), read_filter_(new Network::MockReadFilter()) { EXPECT_EQ(time_system_.systemTime(), envoy_quic_session_.streamInfo().startTime()); EXPECT_EQ(EMPTY_STRING, envoy_quic_session_.nextProtocol()); @@ -103,7 +102,7 @@ class EnvoyQuicServerSessionTest : public testing::TestWithParam { bool installReadFilter() { // Setup read filter. envoy_quic_session_.addReadFilter(read_filter_); - EXPECT_EQ(Http::Protocol::Http3, + EXPECT_EQ(Http::Protocol::Http2, read_filter_->callbacks_->connection().streamInfo().protocol().value()); EXPECT_EQ(envoy_quic_session_.id(), read_filter_->callbacks_->connection().id()); EXPECT_EQ(&envoy_quic_session_, &read_filter_->callbacks_->connection()); @@ -115,7 +114,7 @@ class EnvoyQuicServerSessionTest : public testing::TestWithParam { // Create ServerConnection instance and setup callbacks for it. http_connection_ = std::make_unique(envoy_quic_session_, http_connection_callbacks_); - EXPECT_EQ(Http::Protocol::Http3, http_connection_->protocol()); + EXPECT_EQ(Http::Protocol::Http2, http_connection_->protocol()); // Stop iteration to avoid calling getRead/WriteBuffer(). return Network::FilterStatus::StopIteration; })); @@ -315,6 +314,7 @@ TEST_P(EnvoyQuicServerSessionTest, ShutdownNotice) { // Not verifying dummy implementation, just to have coverage. EXPECT_DEATH(envoy_quic_session_.enableHalfClose(true), ""); EXPECT_EQ(nullptr, envoy_quic_session_.ssl()); + EXPECT_DEATH(envoy_quic_session_.aboveHighWatermark(), ""); EXPECT_DEATH(envoy_quic_session_.setDelayedCloseTimeout(std::chrono::milliseconds(1)), ""); http_connection_->shutdownNotice(); } @@ -362,8 +362,6 @@ TEST_P(EnvoyQuicServerSessionTest, InitializeFilterChain) { Network::FilterManager& filter_manager) { filter_manager.addReadFilter(read_filter_); read_filter_->callbacks_->connection().addConnectionCallbacks(network_connection_callbacks_); - read_filter_->callbacks_->connection().setConnectionStats( - {read_total_, read_current_, write_total_, write_current_, nullptr, nullptr}); }}; EXPECT_CALL(filter_chain, networkFilterFactories()).WillOnce(ReturnRef(filter_factory)); EXPECT_CALL(*read_filter_, onNewConnection()) diff --git a/test/extensions/quic_listeners/quiche/envoy_quic_server_stream_test.cc b/test/extensions/quic_listeners/quiche/envoy_quic_server_stream_test.cc index 290096b9c8408..b77714985cc17 100644 --- a/test/extensions/quic_listeners/quiche/envoy_quic_server_stream_test.cc +++ b/test/extensions/quic_listeners/quiche/envoy_quic_server_stream_test.cc @@ -9,7 +9,6 @@ #include "quiche/quic/core/quic_versions.h" #include "quiche/quic/core/http/quic_server_session_base.h" #include "quiche/quic/test_tools/quic_test_utils.h" -#include "quiche/quic/core/quic_utils.h" #pragma GCC diagnostic pop @@ -19,36 +18,29 @@ #include "common/http/headers.h" #include "test/test_common/utility.h" #include "extensions/quic_listeners/quiche/envoy_quic_alarm_factory.h" -#include "extensions/quic_listeners/quiche/envoy_quic_server_session.h" #include "extensions/quic_listeners/quiche/envoy_quic_server_connection.h" #include "extensions/quic_listeners/quiche/envoy_quic_connection_helper.h" -#include "test/test_common/utility.h" #include "test/mocks/http/stream_decoder.h" -#include "test/mocks/http/mocks.h" #include "test/mocks/network/mocks.h" #include "gmock/gmock.h" #include "gtest/gtest.h" using testing::_; using testing::Invoke; -using testing::Return; namespace Envoy { namespace Quic { -class MockQuicServerSession : public EnvoyQuicServerSession { +class MockQuicServerSession : public quic::QuicServerSessionBase { public: MockQuicServerSession(const quic::QuicConfig& config, const quic::ParsedQuicVersionVector& supported_versions, - std::unique_ptr connection, - quic::QuicSession::Visitor* visitor, + quic::QuicConnection* connection, quic::QuicSession::Visitor* visitor, quic::QuicCryptoServerStream::Helper* helper, const quic::QuicCryptoServerConfig* crypto_config, - quic::QuicCompressedCertsCache* compressed_certs_cache, - Event::Dispatcher& dispatcher, uint32_t send_buffer_limit) - : EnvoyQuicServerSession(config, supported_versions, std::move(connection), visitor, helper, - crypto_config, compressed_certs_cache, dispatcher, - send_buffer_limit) {} + quic::QuicCompressedCertsCache* compressed_certs_cache) + : quic::QuicServerSessionBase(config, supported_versions, connection, visitor, helper, + crypto_config, compressed_certs_cache) {} MOCK_METHOD1(CreateIncomingStream, quic::QuicSpdyStream*(quic::QuicStreamId id)); MOCK_METHOD1(CreateIncomingStream, quic::QuicSpdyStream*(quic::PendingStream* pending)); @@ -57,20 +49,9 @@ class MockQuicServerSession : public EnvoyQuicServerSession { MOCK_METHOD1(ShouldCreateIncomingStream, bool(quic::QuicStreamId id)); MOCK_METHOD0(ShouldCreateOutgoingBidirectionalStream, bool()); MOCK_METHOD0(ShouldCreateOutgoingUnidirectionalStream, bool()); - MOCK_METHOD1(ShouldYield, bool(quic::QuicStreamId stream_id)); - MOCK_METHOD5(WritevData, - quic::QuicConsumedData(quic::QuicStream* stream, quic::QuicStreamId id, - size_t write_length, quic::QuicStreamOffset offset, - quic::StreamSendingState state)); - - quic::QuicCryptoServerStream* - CreateQuicCryptoServerStream(const quic::QuicCryptoServerConfig* crypto_config, - quic::QuicCompressedCertsCache* compressed_certs_cache) override { - return new quic::QuicCryptoServerStream(crypto_config, compressed_certs_cache, this, - stream_helper()); - } - - using quic::QuicServerSessionBase::ActivateStream; + MOCK_METHOD2(CreateQuicCryptoServerStream, + quic::QuicCryptoServerStream*(const quic::QuicCryptoServerConfig*, + quic::QuicCompressedCertsCache*)); }; class EnvoyQuicServerStreamTest : public testing::TestWithParam { @@ -85,45 +66,26 @@ class EnvoyQuicServerStreamTest : public testing::TestWithParam { listener_stats_({ALL_LISTENER_STATS(POOL_COUNTER(listener_config_.listenerScope()), POOL_GAUGE(listener_config_.listenerScope()), POOL_HISTOGRAM(listener_config_.listenerScope()))}), - quic_connection_(new EnvoyQuicServerConnection( - quic::test::TestConnectionId(), - quic::QuicSocketAddress(quic::QuicIpAddress::Any6(), 12345), connection_helper_, - alarm_factory_, &writer_, - /*owns_writer=*/false, {quic_version_}, listener_config_, listener_stats_)), - quic_session_(quic_config_, {quic_version_}, - std::unique_ptr(quic_connection_), - /*visitor=*/nullptr, + quic_connection_(quic::test::TestConnectionId(), + quic::QuicSocketAddress(quic::QuicIpAddress::Any6(), 12345), + connection_helper_, alarm_factory_, &writer_, + /*owns_writer=*/false, {quic_version_}, listener_config_, listener_stats_), + quic_session_(quic_config_, {quic_version_}, &quic_connection_, /*visitor=*/nullptr, /*helper=*/nullptr, /*crypto_config=*/nullptr, - /*compressed_certs_cache=*/nullptr, *dispatcher_, - quic_config_.GetInitialStreamFlowControlWindowToSend()), + /*compressed_certs_cache=*/nullptr), stream_id_(quic_version_.transport_version == quic::QUIC_VERSION_99 ? 4u : 5u), - quic_stream_(new EnvoyQuicServerStream(stream_id_, &quic_session_, quic::BIDIRECTIONAL)), - response_headers_{{":status", "200"}} { + quic_stream_(stream_id_, &quic_session_, quic::BIDIRECTIONAL) { quic::SetVerbosityLogThreshold(3); - quic_stream_->setDecoder(stream_decoder_); - quic_stream_->addCallbacks(stream_callbacks_); - quic_session_.ActivateStream(std::unique_ptr(quic_stream_)); - EXPECT_CALL(quic_session_, ShouldYield(_)).WillRepeatedly(Return(false)); - EXPECT_CALL(quic_session_, WritevData(_, _, _, _, _)) - .WillRepeatedly(Invoke([](quic::QuicStream*, quic::QuicStreamId, size_t write_length, - quic::QuicStreamOffset, quic::StreamSendingState) { - return quic::QuicConsumedData{write_length, true}; - })); - EXPECT_CALL(writer_, WritePacket(_, _, _, _, _)) - .WillRepeatedly(Invoke([](const char*, size_t buf_len, const quic::QuicIpAddress&, - const quic::QuicSocketAddress&, quic::PerPacketOptions*) { - return quic::WriteResult{quic::WRITE_STATUS_OK, static_cast(buf_len)}; - })); + + quic_stream_.setDecoder(stream_decoder_); } void SetUp() override { - quic_session_.Initialize(); - request_headers_.OnHeaderBlockStart(); - request_headers_.OnHeader(":authority", host_); - request_headers_.OnHeader(":method", "GET"); - request_headers_.OnHeader(":path", "/"); - request_headers_.OnHeaderBlockEnd(/*uncompressed_header_bytes=*/0, - /*compressed_header_bytes=*/0); + headers_.OnHeaderBlockStart(); + headers_.OnHeader(":authority", host_); + headers_.OnHeader(":method", "GET"); + headers_.OnHeader(":path", "/"); + headers_.OnHeaderBlockEnd(/*uncompressed_header_bytes=*/0, /*compressed_header_bytes=*/0); trailers_.OnHeaderBlockStart(); trailers_.OnHeader("key1", "value1"); @@ -134,12 +96,6 @@ class EnvoyQuicServerStreamTest : public testing::TestWithParam { trailers_.OnHeaderBlockEnd(/*uncompressed_header_bytes=*/0, /*compressed_header_bytes=*/0); } - void TearDown() override { - if (quic_connection_->connected()) { - quic_session_.close(Network::ConnectionCloseType::NoFlush); - } - } - protected: Api::ApiPtr api_; Event::DispatcherPtr dispatcher_; @@ -150,14 +106,12 @@ class EnvoyQuicServerStreamTest : public testing::TestWithParam { quic::QuicConfig quic_config_; testing::NiceMock listener_config_; Server::ListenerStats listener_stats_; - EnvoyQuicServerConnection* quic_connection_; + EnvoyQuicServerConnection quic_connection_; MockQuicServerSession quic_session_; quic::QuicStreamId stream_id_; - EnvoyQuicServerStream* quic_stream_; + EnvoyQuicServerStream quic_stream_; Http::MockStreamDecoder stream_decoder_; - Http::MockStreamCallbacks stream_callbacks_; - quic::QuicHeaderList request_headers_; - Http::TestHeaderMapImpl response_headers_; + quic::QuicHeaderList headers_; quic::QuicHeaderList trailers_; std::string host_{"www.abc.com"}; std::string request_body_{"Hello world"}; @@ -166,7 +120,7 @@ class EnvoyQuicServerStreamTest : public testing::TestWithParam { INSTANTIATE_TEST_SUITE_P(EnvoyQuicServerStreamTests, EnvoyQuicServerStreamTest, testing::ValuesIn({true, false})); -TEST_P(EnvoyQuicServerStreamTest, PostRequestAndResponse) { +TEST_P(EnvoyQuicServerStreamTest, DecodeHeadersAndBody) { EXPECT_CALL(stream_decoder_, decodeHeaders_(_, /*end_stream=*/false)) .WillOnce(Invoke([this](const Http::HeaderMapPtr& headers, bool) { EXPECT_EQ(host_, headers->Host()->value().getStringView()); @@ -175,12 +129,11 @@ TEST_P(EnvoyQuicServerStreamTest, PostRequestAndResponse) { headers->Method()->value().getStringView()); })); if (quic_version_.transport_version == quic::QUIC_VERSION_99) { - quic_stream_->OnHeadersDecoded(request_headers_); + quic_stream_.OnHeadersDecoded(headers_); } else { - quic_stream_->OnStreamHeaderList(/*fin=*/false, request_headers_.uncompressed_header_bytes(), - request_headers_); + quic_stream_.OnStreamHeaderList(/*fin=*/false, headers_.uncompressed_header_bytes(), headers_); } - EXPECT_TRUE(quic_stream_->FinishedReadingHeaders()); + EXPECT_TRUE(quic_stream_.FinishedReadingHeaders()); EXPECT_CALL(stream_decoder_, decodeData(_, _)) .WillOnce(Invoke([this](Buffer::Instance& buffer, bool finished_reading) { @@ -197,9 +150,7 @@ TEST_P(EnvoyQuicServerStreamTest, PostRequestAndResponse) { data = absl::StrCat(data_frame_header, request_body_); } quic::QuicStreamFrame frame(stream_id_, true, 0, data); - quic_stream_->OnStreamFrame(frame); - - quic_stream_->encodeHeaders(response_headers_, /*end_stream=*/true); + quic_stream_.OnStreamFrame(frame); } TEST_P(EnvoyQuicServerStreamTest, DecodeHeadersBodyAndTrailers) { @@ -210,9 +161,8 @@ TEST_P(EnvoyQuicServerStreamTest, DecodeHeadersBodyAndTrailers) { EXPECT_EQ(Http::Headers::get().MethodValues.Get, headers->Method()->value().getStringView()); })); - quic_stream_->OnStreamHeaderList(/*fin=*/false, request_headers_.uncompressed_header_bytes(), - request_headers_); - EXPECT_TRUE(quic_stream_->FinishedReadingHeaders()); + quic_stream_.OnStreamHeaderList(/*fin=*/false, headers_.uncompressed_header_bytes(), headers_); + EXPECT_TRUE(quic_stream_.FinishedReadingHeaders()); std::string data = request_body_; if (quic_version_.transport_version == quic::QUIC_VERSION_99) { @@ -236,7 +186,7 @@ TEST_P(EnvoyQuicServerStreamTest, DecodeHeadersBodyAndTrailers) { EXPECT_FALSE(finished_reading); EXPECT_EQ(0, buffer.length()); })); - quic_stream_->OnStreamFrame(frame); + quic_stream_.OnStreamFrame(frame); EXPECT_CALL(stream_decoder_, decodeTrailers_(_)) .WillOnce(Invoke([](const Http::HeaderMapPtr& headers) { @@ -245,12 +195,10 @@ TEST_P(EnvoyQuicServerStreamTest, DecodeHeadersBodyAndTrailers) { EXPECT_EQ("value1", headers->get(key1)->value().getStringView()); EXPECT_EQ(nullptr, headers->get(key2)); })); - quic_stream_->OnStreamHeaderList(/*fin=*/true, trailers_.uncompressed_header_bytes(), trailers_); - EXPECT_CALL(stream_callbacks_, onResetStream(_, _)); + quic_stream_.OnStreamHeaderList(/*fin=*/true, trailers_.uncompressed_header_bytes(), trailers_); } TEST_P(EnvoyQuicServerStreamTest, OutOfOrderTrailers) { - EXPECT_CALL(stream_callbacks_, onResetStream(_, _)); if (quic::VersionUsesQpack(quic_version_.transport_version)) { return; } @@ -261,12 +209,11 @@ TEST_P(EnvoyQuicServerStreamTest, OutOfOrderTrailers) { EXPECT_EQ(Http::Headers::get().MethodValues.Get, headers->Method()->value().getStringView()); })); - quic_stream_->OnStreamHeaderList(/*fin=*/false, request_headers_.uncompressed_header_bytes(), - request_headers_); - EXPECT_TRUE(quic_stream_->FinishedReadingHeaders()); + quic_stream_.OnStreamHeaderList(/*fin=*/false, headers_.uncompressed_header_bytes(), headers_); + EXPECT_TRUE(quic_stream_.FinishedReadingHeaders()); // Trailer should be delivered to HCM later after body arrives. - quic_stream_->OnStreamHeaderList(/*fin=*/true, trailers_.uncompressed_header_bytes(), trailers_); + quic_stream_.OnStreamHeaderList(/*fin=*/true, trailers_.uncompressed_header_bytes(), trailers_); std::string data = request_body_; if (quic_version_.transport_version == quic::QUIC_VERSION_99) { @@ -298,91 +245,7 @@ TEST_P(EnvoyQuicServerStreamTest, OutOfOrderTrailers) { EXPECT_EQ("value1", headers->get(key1)->value().getStringView()); EXPECT_EQ(nullptr, headers->get(key2)); })); - quic_stream_->OnStreamFrame(frame); -} - -TEST_P(EnvoyQuicServerStreamTest, WatermarkSendBuffer) { - EXPECT_CALL(stream_decoder_, decodeHeaders_(_, /*end_stream=*/false)) - .WillOnce(Invoke([this](const Http::HeaderMapPtr& headers, bool) { - EXPECT_EQ(host_, headers->Host()->value().getStringView()); - EXPECT_EQ("/", headers->Path()->value().getStringView()); - EXPECT_EQ(Http::Headers::get().MethodValues.Get, - headers->Method()->value().getStringView()); - })); - if (quic_version_.transport_version == quic::QUIC_VERSION_99) { - quic_stream_->OnHeadersDecoded(request_headers_); - } else { - quic_stream_->OnStreamHeaderList(/*fin=*/false, request_headers_.uncompressed_header_bytes(), - request_headers_); - } - EXPECT_TRUE(quic_stream_->FinishedReadingHeaders()); - - EXPECT_CALL(stream_decoder_, decodeData(_, _)) - .WillOnce(Invoke([this](Buffer::Instance& buffer, bool finished_reading) { - EXPECT_EQ(request_body_, buffer.toString()); - EXPECT_TRUE(finished_reading); - })); - std::string data = request_body_; - if (quic_version_.transport_version == quic::QUIC_VERSION_99) { - std::unique_ptr data_buffer; - quic::HttpEncoder encoder; - quic::QuicByteCount data_frame_header_length = - encoder.SerializeDataFrameHeader(request_body_.length(), &data_buffer); - quic::QuicStringPiece data_frame_header(data_buffer.get(), data_frame_header_length); - data = absl::StrCat(data_frame_header, request_body_); - } - quic::QuicStreamFrame frame(stream_id_, true, 0, data); - quic_stream_->OnStreamFrame(frame); - - response_headers_.addCopy(":content-length", "32770"); // 32KB + 2 byte - quic_stream_->encodeHeaders(response_headers_, /*end_stream=*/false); - // encode 32kB response body. first 16KB shoudl be written out right away. The - // rest should be buffered. The high watermark is 16KB, so this call should - // make the send buffer reach its high watermark. - std::string response(32 * 1024 + 1, 'a'); - Buffer::OwnedImpl buffer(response); - EXPECT_CALL(stream_callbacks_, onAboveWriteBufferHighWatermark()); - quic_stream_->encodeData(buffer, false); - - EXPECT_EQ(0u, buffer.length()); - EXPECT_TRUE(quic_stream_->flow_controller()->IsBlocked()); - // Bump connection flow control window large enough not to cause connection - // level flow control blocked. - quic::QuicWindowUpdateFrame window_update( - quic::kInvalidControlFrameId, - quic::QuicUtils::GetInvalidStreamId(quic_version_.transport_version), 1024 * 1024); - quic_session_.OnWindowUpdateFrame(window_update); - - // Receive a WINDOW_UPDATE frame not large enough to drain half of the send - // buffer. - quic::QuicWindowUpdateFrame window_update1(quic::kInvalidControlFrameId, quic_stream_->id(), - 16 * 1024 + 8 * 1024); - quic_stream_->OnWindowUpdateFrame(window_update1); - EXPECT_FALSE(quic_stream_->flow_controller()->IsBlocked()); - quic_session_.OnCanWrite(); - EXPECT_TRUE(quic_stream_->flow_controller()->IsBlocked()); - - // Receive another WINDOW_UPDATE frame to drain the send buffer till below low - // watermark. - quic::QuicWindowUpdateFrame window_update2(quic::kInvalidControlFrameId, quic_stream_->id(), - 16 * 1024 + 8 * 1024 + 1024); - quic_stream_->OnWindowUpdateFrame(window_update2); - EXPECT_FALSE(quic_stream_->flow_controller()->IsBlocked()); - EXPECT_CALL(stream_callbacks_, onBelowWriteBufferLowWatermark()).WillOnce(Invoke([this]() { - std::string rest_response(1, 'a'); - Buffer::OwnedImpl buffer(rest_response); - quic_stream_->encodeData(buffer, true); - })); - quic_session_.OnCanWrite(); - EXPECT_TRUE(quic_stream_->flow_controller()->IsBlocked()); - - quic::QuicWindowUpdateFrame window_update3(quic::kInvalidControlFrameId, quic_stream_->id(), - 32 * 1024 + 1024); - quic_stream_->OnWindowUpdateFrame(window_update3); - quic_session_.OnCanWrite(); - - EXPECT_TRUE(quic_stream_->local_end_stream_); - EXPECT_TRUE(quic_stream_->write_side_closed()); + quic_stream_.OnStreamFrame(frame); } } // namespace Quic diff --git a/test/extensions/quic_listeners/quiche/integration/BUILD b/test/extensions/quic_listeners/quiche/integration/BUILD deleted file mode 100644 index 4e2cde2d9bd8d..0000000000000 --- a/test/extensions/quic_listeners/quiche/integration/BUILD +++ /dev/null @@ -1,29 +0,0 @@ -licenses(["notice"]) # Apache 2 - -load( - "//bazel:envoy_build_system.bzl", - "envoy_cc_fuzz_test", - "envoy_cc_test", - "envoy_cc_test_binary", - "envoy_cc_test_library", - "envoy_package", - "envoy_proto_library", -) - -envoy_package() - -envoy_cc_test( - name = "quic_http_integration_test", - srcs = ["quic_http_integration_test.cc"], - data = ["//test/config/integration/certs"], - tags = ["nofips"], - deps = [ - "//source/extensions/quic_listeners/quiche:active_quic_listener_config_lib", - "//source/extensions/quic_listeners/quiche:envoy_quic_client_connection_lib", - "//source/extensions/quic_listeners/quiche:envoy_quic_client_session_lib", - "//source/extensions/quic_listeners/quiche:envoy_quic_connection_helper_lib", - "//source/extensions/quic_listeners/quiche:envoy_quic_proof_verifier_lib", - "//source/extensions/quic_listeners/quiche:quic_transport_socket_factory_lib", - "//test/integration:http_integration_lib", - ], -) diff --git a/test/extensions/quic_listeners/quiche/integration/quic_http_integration_test.cc b/test/extensions/quic_listeners/quiche/integration/quic_http_integration_test.cc deleted file mode 100644 index 86d5f08b48f58..0000000000000 --- a/test/extensions/quic_listeners/quiche/integration/quic_http_integration_test.cc +++ /dev/null @@ -1,185 +0,0 @@ -#include "test/config/utility.h" -#include "test/integration/http_integration.h" -#include "test/test_common/utility.h" - -#pragma GCC diagnostic push -// QUICHE allows unused parameters. -#pragma GCC diagnostic ignored "-Wunused-parameter" -// QUICHE uses offsetof(). -#pragma GCC diagnostic ignored "-Winvalid-offsetof" - -#include "quiche/quic/core/http/quic_client_push_promise_index.h" -#include "quiche/quic/core/quic_utils.h" - -#pragma GCC diagnostic pop - -#include "extensions/quic_listeners/quiche/envoy_quic_client_session.h" -#include "extensions/quic_listeners/quiche/envoy_quic_client_connection.h" -#include "extensions/quic_listeners/quiche/envoy_quic_fake_proof_verifier.h" -#include "extensions/quic_listeners/quiche/envoy_quic_connection_helper.h" -#include "extensions/quic_listeners/quiche/envoy_quic_alarm_factory.h" -#include "extensions/quic_listeners/quiche/envoy_quic_packet_writer.h" - -namespace Envoy { -namespace Quic { - -class CodecClientCallbacksForTest : public Http::CodecClientCallbacks { -public: - void onStreamDestroy() override {} - - void onStreamReset(Http::StreamResetReason reason) override { - last_stream_reset_reason_ = reason; - } - - Http::StreamResetReason last_stream_reset_reason_{Http::StreamResetReason::LocalReset}; -}; - -class QuicHttpIntegrationTest : public testing::TestWithParam, - public HttpIntegrationTest { -public: - QuicHttpIntegrationTest() - : HttpIntegrationTest(Http::CodecClient::Type::HTTP3, GetParam(), - ConfigHelper::QUIC_HTTP_PROXY_CONFIG), - supported_versions_(quic::CurrentSupportedVersions()), - crypto_config_(std::make_unique()), conn_helper_(*dispatcher_), - alarm_factory_(*dispatcher_, *conn_helper_.GetClock()) { - quic::SetVerbosityLogThreshold(1); - } - - Network::ClientConnectionPtr makeClientConnection(uint32_t port) override { - Network::Address::InstanceConstSharedPtr server_addr = Network::Utility::resolveUrl( - fmt::format("udp://{}:{}", Network::Test::getLoopbackAddressUrlString(version_), port)); - Network::Address::InstanceConstSharedPtr local_addr = - Network::Test::getCanonicalLoopbackAddress(version_); - // Initiate a QUIC connection with the highest supported version. If not - // supported by server, this connection will fail. - // TODO(danzh) Implement retry upon version mismatch and modify test frame work to specify a - // different version set on server side to test that. - auto connection = std::make_unique( - getNextServerDesignatedConnectionId(), server_addr, conn_helper_, alarm_factory_, - quic::ParsedQuicVersionVector{supported_versions_[0]}, local_addr, *dispatcher_, nullptr); - auto session = std::make_unique( - quic_config_, supported_versions_, std::move(connection), server_id_, &crypto_config_, - &push_promise_index_, *dispatcher_, 0); - return session; - } - - // This call may fail because of INVALID_VERSION, because QUIC connection doesn't support - // in-connection version negotiation. - // TODO(#8479) Popagate INVALID_VERSION error to caller and let caller to use server advertised - // version list to create a new connection with mutually supported version and make client codec - // again. - IntegrationCodecClientPtr makeRawHttpConnection(Network::ClientConnectionPtr&& conn) override { - IntegrationCodecClientPtr codec = HttpIntegrationTest::makeRawHttpConnection(std::move(conn)); - ASSERT(!codec->connected()); - dynamic_cast(codec->connection())->cryptoConnect(); - if (codec->disconnected()) { - // Connection may get closed during version negotiation or handshake. - ENVOY_LOG(error, "Fail to connect to server with error: {}", - codec->connection()->transportFailureReason()); - } else { - codec->setCodecClientCallbacks(client_codec_callback_); - } - return codec; - } - - quic::QuicConnectionId getNextServerDesignatedConnectionId() { - quic::QuicCryptoClientConfig::CachedState* cached = crypto_config_.LookupOrCreate(server_id_); - // If the cached state indicates that we should use a server-designated - // connection ID, then return that connection ID. - quic::QuicConnectionId conn_id = cached->has_server_designated_connection_id() - ? cached->GetNextServerDesignatedConnectionId() - : quic::EmptyQuicConnectionId(); - return conn_id.IsEmpty() ? quic::QuicUtils::CreateRandomConnectionId() : conn_id; - } - - void initialize() override { - config_helper_.addConfigModifier([](envoy::config::bootstrap::v2::Bootstrap& bootstrap) { - envoy::api::v2::auth::DownstreamTlsContext tls_context; - ConfigHelper::initializeTls({}, *tls_context.mutable_common_tls_context()); - auto* filter_chain = - bootstrap.mutable_static_resources()->mutable_listeners(0)->mutable_filter_chains(0); - auto* transport_socket = filter_chain->mutable_transport_socket(); - TestUtility::jsonConvert(tls_context, *transport_socket->mutable_config()); - }); - config_helper_.addConfigModifier( - [](envoy::config::filter::network::http_connection_manager::v2::HttpConnectionManager& - hcm) { - hcm.mutable_delayed_close_timeout()->set_nanos(0); - EXPECT_EQ(hcm.codec_type(), envoy::config::filter::network::http_connection_manager::v2:: - HttpConnectionManager::HTTP3); - }); - - HttpIntegrationTest::initialize(); - registerTestServerPorts({"http"}); - } - -protected: - quic::QuicConfig quic_config_; - quic::QuicServerId server_id_; - quic::QuicClientPushPromiseIndex push_promise_index_; - quic::ParsedQuicVersionVector supported_versions_; - quic::QuicCryptoClientConfig crypto_config_; - EnvoyQuicConnectionHelper conn_helper_; - EnvoyQuicAlarmFactory alarm_factory_; - CodecClientCallbacksForTest client_codec_callback_; -}; - -INSTANTIATE_TEST_SUITE_P(IpVersions, QuicHttpIntegrationTest, - testing::ValuesIn(TestEnvironment::getIpVersionsForTest()), - TestUtility::ipTestParamsToString); - -TEST_P(QuicHttpIntegrationTest, GetRequestAndEmptyResponse) { - testRouterHeaderOnlyRequestAndResponse(); -} - -TEST_P(QuicHttpIntegrationTest, GetRequestAndResponseWithBody) { - initialize(); - sendRequestAndVerifyResponse(default_request_headers_, /*request_size=*/0, - default_response_headers_, /*response_size=*/1024, - /*backend_index*/ 0); -} - -TEST_P(QuicHttpIntegrationTest, PostRequestAndResponseWithBody) { - testRouterRequestAndResponseWithBody(1024, 512, false); -} - -TEST_P(QuicHttpIntegrationTest, PostRequestWithBigHeadersAndResponseWithBody) { - testRouterRequestAndResponseWithBody(1024, 512, true); -} - -TEST_P(QuicHttpIntegrationTest, RouterUpstreamDisconnectBeforeRequestcomplete) { - testRouterUpstreamDisconnectBeforeRequestComplete(); -} - -TEST_P(QuicHttpIntegrationTest, RouterUpstreamDisconnectBeforeResponseComplete) { - testRouterUpstreamDisconnectBeforeResponseComplete(); - EXPECT_EQ(Http::StreamResetReason::RemoteReset, client_codec_callback_.last_stream_reset_reason_); -} - -TEST_P(QuicHttpIntegrationTest, RouterDownstreamDisconnectBeforeRequestComplete) { - testRouterDownstreamDisconnectBeforeRequestComplete(); -} - -TEST_P(QuicHttpIntegrationTest, RouterDownstreamDisconnectBeforeResponseComplete) { - testRouterDownstreamDisconnectBeforeResponseComplete(); -} - -TEST_P(QuicHttpIntegrationTest, RouterUpstreamResponseBeforeRequestComplete) { - testRouterUpstreamResponseBeforeRequestComplete(); -} - -TEST_P(QuicHttpIntegrationTest, Retry) { testRetry(); } - -TEST_P(QuicHttpIntegrationTest, UpstreamThrottlingOnGiantResponseBody) { - config_helper_.setBufferLimits(/*upstream_buffer_limit=*/1024, /*downstream_buffer_limit=*/1024); - testRouterRequestAndResponseWithBody(/*request_size=*/512, /*response_size=*/1024 * 1024, false); -} - -TEST_P(QuicHttpIntegrationTest, DownstreamThrottlingOnGiantPost) { - config_helper_.setBufferLimits(/*upstream_buffer_limit=*/1024, /*downstream_buffer_limit=*/1024); - testRouterRequestAndResponseWithBody(/*request_size=*/1024 * 1024, /*response_size=*/1024, false); -} - -} // namespace Quic -} // namespace Envoy diff --git a/test/integration/http_integration.cc b/test/integration/http_integration.cc index 99ff9d5f91b5d..be622d93f3b81 100644 --- a/test/integration/http_integration.cc +++ b/test/integration/http_integration.cc @@ -46,9 +46,6 @@ typeToCodecType(Http::CodecClient::Type type) { case Http::CodecClient::Type::HTTP2: return envoy::config::filter::network::http_connection_manager::v2::HttpConnectionManager:: HTTP2; - case Http::CodecClient::Type::HTTP3: - return envoy::config::filter::network::http_connection_manager::v2::HttpConnectionManager:: - HTTP3; default: RELEASE_ASSERT(0, ""); } @@ -63,12 +60,7 @@ IntegrationCodecClient::IntegrationCodecClient( callbacks_(*this), codec_callbacks_(*this) { connection_->addConnectionCallbacks(callbacks_); setCodecConnectionCallbacks(codec_callbacks_); - if (type != CodecClient::Type::HTTP3) { - // Only expect to have IO event if it's not QUIC. Because call to connect() in CodecClientProd - // for QUIC doesn't send anything to server, but just register file event. QUIC connection won't - // have any IO event till cryptoConnect() is called later. - dispatcher.run(Event::Dispatcher::RunType::Block); - } + dispatcher.run(Event::Dispatcher::RunType::Block); } void IntegrationCodecClient::flushWrite() { @@ -577,9 +569,7 @@ void HttpIntegrationTest::testRouterUpstreamResponseBeforeRequestComplete() { ASSERT_TRUE(fake_upstreams_[0]->waitForHttpConnection(*dispatcher_, fake_upstream_connection_)); ASSERT_TRUE(fake_upstream_connection_->waitForNewStream(*dispatcher_, upstream_request_)); ASSERT_TRUE(upstream_request_->waitForHeadersComplete()); - upstream_request_->encodeHeaders(default_response_headers_, false); - upstream_request_->encodeData(512, true); response->waitForEndStream(); diff --git a/test/integration/http_integration.h b/test/integration/http_integration.h index 35871f0e47073..1a2193556e654 100644 --- a/test/integration/http_integration.h +++ b/test/integration/http_integration.h @@ -108,7 +108,7 @@ class HttpIntegrationTest : public BaseIntegrationTest { IntegrationCodecClientPtr makeHttpConnection(uint32_t port); // Makes a http connection object without checking its connected state. - virtual IntegrationCodecClientPtr makeRawHttpConnection(Network::ClientConnectionPtr&& conn); + IntegrationCodecClientPtr makeRawHttpConnection(Network::ClientConnectionPtr&& conn); // Makes a http connection object with asserting a connected state. IntegrationCodecClientPtr makeHttpConnection(Network::ClientConnectionPtr&& conn); diff --git a/test/integration/integration.h b/test/integration/integration.h index 0e2b7d48c6fe1..7cd2109cc8593 100644 --- a/test/integration/integration.h +++ b/test/integration/integration.h @@ -138,7 +138,7 @@ struct ApiFilesystemConfig { /** * Test fixture for all integration tests. */ -class BaseIntegrationTest : protected Logger::Loggable { +class BaseIntegrationTest : Logger::Loggable { public: using TestTimeSystemPtr = std::unique_ptr; using InstanceConstSharedPtrFn = std::function; @@ -191,7 +191,7 @@ class BaseIntegrationTest : protected Logger::Loggable { void setUpstreamAddress(uint32_t upstream_index, envoy::api::v2::endpoint::LbEndpoint& endpoint) const; - virtual Network::ClientConnectionPtr makeClientConnection(uint32_t port); + Network::ClientConnectionPtr makeClientConnection(uint32_t port); void registerTestServerPorts(const std::vector& port_names); void createTestServer(const std::string& json_path, const std::vector& port_names); diff --git a/test/integration/xfcc_integration_test.cc b/test/integration/xfcc_integration_test.cc index 0b62da4439de2..4e2bed984cfbd 100644 --- a/test/integration/xfcc_integration_test.cc +++ b/test/integration/xfcc_integration_test.cc @@ -92,7 +92,7 @@ Network::TransportSocketFactoryPtr XfccIntegrationTest::createUpstreamSslContext std::move(cfg), *context_manager_, *upstream_stats_store, std::vector{}); } -Network::ClientConnectionPtr XfccIntegrationTest::makeTcpClientConnection() { +Network::ClientConnectionPtr XfccIntegrationTest::makeClientConnection() { Network::Address::InstanceConstSharedPtr address = Network::Utility::resolveUrl("tcp://" + Network::Test::getLoopbackAddressUrlString(version_) + ":" + std::to_string(lookupPort("http"))); @@ -143,7 +143,7 @@ void XfccIntegrationTest::initialize() { void XfccIntegrationTest::testRequestAndResponseWithXfccHeader(std::string previous_xfcc, std::string expected_xfcc) { - Network::ClientConnectionPtr conn = tls_ ? makeMtlsClientConnection() : makeTcpClientConnection(); + Network::ClientConnectionPtr conn = tls_ ? makeMtlsClientConnection() : makeClientConnection(); Http::TestHeaderMapImpl header_map; if (previous_xfcc.empty()) { header_map = Http::TestHeaderMapImpl{{":method", "GET"}, diff --git a/test/integration/xfcc_integration_test.h b/test/integration/xfcc_integration_test.h index 5e1ad2082890d..488ff98aad65d 100644 --- a/test/integration/xfcc_integration_test.h +++ b/test/integration/xfcc_integration_test.h @@ -46,7 +46,7 @@ class XfccIntegrationTest : public testing::TestWithParam Date: Mon, 7 Oct 2019 15:38:12 -0400 Subject: [PATCH 09/76] Revert "revert non-HCM change" This reverts commit 3e39fbe218595c314dbcf06daafcc4e6ab538322. Signed-off-by: Dan Zhang --- bazel/external/quiche.BUILD | 1 + source/common/common/logger.h | 1 + source/common/http/BUILD | 2 + source/common/http/codec_client.cc | 15 +- .../network/http_connection_manager/config.cc | 8 +- source/extensions/quic_listeners/quiche/BUILD | 80 +++++-- .../quiche/active_quic_listener.cc | 6 +- .../quiche/active_quic_listener.h | 2 + .../quic_listeners/quiche/codec_impl.cc | 57 ++++- .../quic_listeners/quiche/codec_impl.h | 37 ++- .../quic_listeners/quiche/envoy_quic_alarm.h | 6 +- .../quiche/envoy_quic_client_connection.cc | 176 ++++++++++++++ .../quiche/envoy_quic_client_connection.h | 66 ++++++ .../quiche/envoy_quic_client_session.cc | 87 +++++++ .../quiche/envoy_quic_client_session.h | 82 +++++++ .../quiche/envoy_quic_client_stream.cc | 219 ++++++++++++++++++ .../quiche/envoy_quic_client_stream.h | 56 +++++ .../quiche/envoy_quic_connection.cc | 5 +- .../quiche/envoy_quic_dispatcher.cc | 3 +- .../quiche/envoy_quic_fake_proof_verifier.h | 3 +- .../quiche/envoy_quic_server_session.cc | 12 +- .../quiche/envoy_quic_server_session.h | 7 +- .../quiche/envoy_quic_server_stream.cc | 152 +++++++++--- .../quiche/envoy_quic_server_stream.h | 14 +- .../envoy_quic_simulated_watermark_buffer.h | 62 +++++ .../quic_listeners/quiche/envoy_quic_stream.h | 55 ++++- .../quic_listeners/quiche/envoy_quic_utils.cc | 45 ++++ .../quic_listeners/quiche/envoy_quic_utils.h | 11 + .../quic_filter_manager_connection_impl.cc | 57 ++++- .../quic_filter_manager_connection_impl.h | 32 ++- test/config/utility.cc | 31 +++ test/config/utility.h | 3 +- test/extensions/quic_listeners/quiche/BUILD | 3 +- .../quiche/envoy_quic_dispatcher_test.cc | 10 +- .../quiche/envoy_quic_proof_source_test.cc | 4 +- .../quiche/envoy_quic_server_session_test.cc | 10 +- .../quiche/envoy_quic_server_stream_test.cc | 213 ++++++++++++++--- .../quic_listeners/quiche/integration/BUILD | 29 +++ .../integration/quic_http_integration_test.cc | 185 +++++++++++++++ test/integration/http_integration.cc | 12 +- test/integration/http_integration.h | 2 +- test/integration/integration.h | 4 +- test/integration/xfcc_integration_test.cc | 4 +- test/integration/xfcc_integration_test.h | 2 +- 44 files changed, 1710 insertions(+), 161 deletions(-) create mode 100644 source/extensions/quic_listeners/quiche/envoy_quic_client_connection.cc create mode 100644 source/extensions/quic_listeners/quiche/envoy_quic_client_connection.h create mode 100644 source/extensions/quic_listeners/quiche/envoy_quic_client_session.cc create mode 100644 source/extensions/quic_listeners/quiche/envoy_quic_client_session.h create mode 100644 source/extensions/quic_listeners/quiche/envoy_quic_client_stream.cc create mode 100644 source/extensions/quic_listeners/quiche/envoy_quic_client_stream.h create mode 100644 source/extensions/quic_listeners/quiche/envoy_quic_simulated_watermark_buffer.h create mode 100644 test/extensions/quic_listeners/quiche/integration/BUILD create mode 100644 test/extensions/quic_listeners/quiche/integration/quic_http_integration_test.cc diff --git a/bazel/external/quiche.BUILD b/bazel/external/quiche.BUILD index 67d7d4207ce90..47505a2d131fb 100644 --- a/bazel/external/quiche.BUILD +++ b/bazel/external/quiche.BUILD @@ -1993,6 +1993,7 @@ envoy_cc_library( copts = quiche_copt, repository = "@envoy", tags = ["nofips"], + visibility = ["//visibility:public"], deps = [ ":quic_core_alarm_interface_lib", ":quic_core_crypto_encryption_lib", diff --git a/source/common/common/logger.h b/source/common/common/logger.h index 5efa9accd3404..80c9f57e574e3 100644 --- a/source/common/common/logger.h +++ b/source/common/common/logger.h @@ -49,6 +49,7 @@ namespace Logger { FUNCTION(misc) \ FUNCTION(mongo) \ FUNCTION(quic) \ + FUNCTION(quic_stream) \ FUNCTION(pool) \ FUNCTION(rbac) \ FUNCTION(redis) \ diff --git a/source/common/http/BUILD b/source/common/http/BUILD index 05553bd529169..b7b152311c4a1 100644 --- a/source/common/http/BUILD +++ b/source/common/http/BUILD @@ -51,6 +51,7 @@ envoy_cc_library( "//source/common/http/http1:codec_lib", "//source/common/http/http2:codec_lib", "//source/common/network:filter_lib", + "//source/extensions/quic_listeners/quiche:codec_lib", ], ) @@ -188,6 +189,7 @@ envoy_cc_library( "//source/common/runtime:uuid_util_lib", "//source/common/stream_info:stream_info_lib", "//source/common/tracing:http_tracer_lib", + "//source/extensions/quic_listeners/quiche:codec_lib", ], ) diff --git a/source/common/http/codec_client.cc b/source/common/http/codec_client.cc index d809327ab5b27..da5d19eb7a16e 100644 --- a/source/common/http/codec_client.cc +++ b/source/common/http/codec_client.cc @@ -9,6 +9,8 @@ #include "common/http/http2/codec_impl.h" #include "common/http/utility.h" +#include "extensions/quic_listeners/quiche/codec_impl.h" + namespace Envoy { namespace Http { @@ -155,8 +157,17 @@ CodecClientProd::CodecClientProd(Type type, Network::ClientConnectionPtr&& conne break; } case Type::HTTP3: { - // TODO(danzh) Add QUIC codec; - NOT_IMPLEMENTED_GCOVR_EXCL_LINE; + // TODO(danzh) this enforce dependency from core code to QUICHE. Is there a + // better way to aoivd such dependency in case QUICHE breaks Envoy build. + // Alternatives: + // 1) move codec creation to Network::Connection instance, in + // QUIC's case, EnvoyQuicClientSession. This is not ideal as + // Network::Connection is not necessart to speak HTTP. + // 2) make codec creation in a static registered factory again. It can be + // only necessary for QUIC and for HTTP2 and HTTP1 just use the existing + // logic. + codec_ = std::make_unique( + dynamic_cast(*connection_), *this); } } } diff --git a/source/extensions/filters/network/http_connection_manager/config.cc b/source/extensions/filters/network/http_connection_manager/config.cc index 8359fe50c02b0..6a3d45bdb70ec 100644 --- a/source/extensions/filters/network/http_connection_manager/config.cc +++ b/source/extensions/filters/network/http_connection_manager/config.cc @@ -25,6 +25,8 @@ #include "common/router/rds_impl.h" #include "common/router/scoped_rds.h" +#include "extensions/quic_listeners/quiche/codec_impl.h" + namespace Envoy { namespace Extensions { namespace NetworkFilters { @@ -408,8 +410,10 @@ HttpConnectionManagerConfig::createCodec(Network::Connection& connection, return std::make_unique( connection, callbacks, context_.scope(), http2_settings_, maxRequestHeadersKb()); case CodecType::HTTP3: - // TODO(danzh) create QUIC specific codec. - NOT_IMPLEMENTED_GCOVR_EXCL_LINE; + // TODO(danzh) same as client side. This enforce dependency on QUICHE. Is there a + // better way to aoivd such dependency in case QUICHE breaks this extension. + return std::make_unique( + dynamic_cast(connection), callbacks); case CodecType::AUTO: return Http::ConnectionManagerUtility::autoCreateCodec(connection, data, callbacks, context_.scope(), http1_settings_, diff --git a/source/extensions/quic_listeners/quiche/BUILD b/source/extensions/quic_listeners/quiche/BUILD index d0e569e3d93da..27a80cda38bf9 100644 --- a/source/extensions/quic_listeners/quiche/BUILD +++ b/source/extensions/quic_listeners/quiche/BUILD @@ -97,32 +97,19 @@ envoy_cc_library( hdrs = ["envoy_quic_stream.h"], tags = ["nofips"], deps = [ + ":envoy_quic_simulated_watermark_buffer_lib", "//include/envoy/http:codec_interface", "//source/common/http:codec_helper_lib", ], ) -envoy_cc_library( - name = "envoy_quic_server_stream_lib", - srcs = ["envoy_quic_server_stream.cc"], - hdrs = ["envoy_quic_server_stream.h"], - tags = ["nofips"], - deps = [ - ":envoy_quic_stream_lib", - ":envoy_quic_utils_lib", - "//source/common/buffer:buffer_lib", - "//source/common/common:assert_lib", - "//source/common/http:header_map_lib", - "@com_googlesource_quiche//:quic_core_http_spdy_session_lib", - ], -) - envoy_cc_library( name = "codec_lib", srcs = ["codec_impl.cc"], hdrs = ["codec_impl.h"], tags = ["nofips"], deps = [ + ":envoy_quic_client_session_lib", ":envoy_quic_server_session_lib", "//include/envoy/http:codec_interface", "@com_googlesource_quiche//:quic_core_http_spdy_session_lib", @@ -136,9 +123,13 @@ envoy_cc_library( tags = ["nofips"], deps = [ ":envoy_quic_connection_lib", + ":envoy_quic_simulated_watermark_buffer_lib", "//include/envoy/event:dispatcher_interface", "//include/envoy/network:connection_interface", + "//source/common/buffer:buffer_lib", + "//source/common/common:assert_lib", "//source/common/common:empty_string", + "//source/common/http:header_map_lib", "//source/common/network:filter_manager_lib", "//source/common/stream_info:stream_info_lib", ], @@ -146,16 +137,51 @@ envoy_cc_library( envoy_cc_library( name = "envoy_quic_server_session_lib", - srcs = ["envoy_quic_server_session.cc"], - hdrs = ["envoy_quic_server_session.h"], + srcs = [ + "envoy_quic_server_session.cc", + "envoy_quic_server_stream.cc", + ], + hdrs = [ + "envoy_quic_server_session.h", + "envoy_quic_server_stream.h", + ], tags = ["nofips"], deps = [ - ":envoy_quic_server_stream_lib", + ":envoy_quic_stream_lib", + ":envoy_quic_utils_lib", ":quic_filter_manager_connection_lib", + "//source/common/buffer:buffer_lib", + "//source/common/common:assert_lib", + "//source/common/http:header_map_lib", + "//source/extensions/quic_listeners/quiche/platform:quic_platform_mem_slice_storage_impl_lib", "@com_googlesource_quiche//:quic_core_http_spdy_session_lib", ], ) +envoy_cc_library( + name = "envoy_quic_client_session_lib", + srcs = [ + "envoy_quic_client_session.cc", + "envoy_quic_client_stream.cc", + ], + hdrs = [ + "envoy_quic_client_session.h", + "envoy_quic_client_stream.h", + ], + tags = ["nofips"], + deps = [ + ":envoy_quic_client_connection_lib", + ":envoy_quic_stream_lib", + ":envoy_quic_utils_lib", + ":quic_filter_manager_connection_lib", + "//source/common/buffer:buffer_lib", + "//source/common/common:assert_lib", + "//source/common/http:header_map_lib", + "//source/extensions/quic_listeners/quiche/platform:quic_platform_mem_slice_storage_impl_lib", + "@com_googlesource_quiche//:quic_core_http_client_lib", + ], +) + envoy_cc_library( name = "quic_io_handle_wrapper_lib", hdrs = ["quic_io_handle_wrapper.h"], @@ -191,6 +217,19 @@ envoy_cc_library( ], ) +envoy_cc_library( + name = "envoy_quic_client_connection_lib", + srcs = ["envoy_quic_client_connection.cc"], + hdrs = ["envoy_quic_client_connection.h"], + tags = ["nofips"], + deps = [ + ":envoy_quic_connection_lib", + ":envoy_quic_packet_writer_lib", + "//include/envoy/event:dispatcher_interface", + "//source/common/network:socket_option_factory_lib", + ], +) + envoy_cc_library( name = "envoy_quic_dispatcher_lib", srcs = ["envoy_quic_dispatcher.cc"], @@ -207,6 +246,11 @@ envoy_cc_library( ], ) +envoy_cc_library( + name = "envoy_quic_simulated_watermark_buffer_lib", + hdrs = ["envoy_quic_simulated_watermark_buffer.h"], +) + envoy_cc_library( name = "active_quic_listener_lib", srcs = ["active_quic_listener.cc"], diff --git a/source/extensions/quic_listeners/quiche/active_quic_listener.cc b/source/extensions/quic_listeners/quiche/active_quic_listener.cc index 15d1b700e0b44..af6d92fad2a17 100644 --- a/source/extensions/quic_listeners/quiche/active_quic_listener.cc +++ b/source/extensions/quic_listeners/quiche/active_quic_listener.cc @@ -43,6 +43,8 @@ ActiveQuicListener::ActiveQuicListener(Event::Dispatcher& dispatcher, quic::QuicRandom::GetInstance(), std::make_unique(), quic::KeyExchangeSource::Default()); auto connection_helper = std::make_unique(dispatcher_); + crypto_config_->AddDefaultConfig(random, connection_helper->GetClock(), + quic::QuicCryptoServerConfig::ConfigOptions()); auto alarm_factory = std::make_unique(dispatcher_, *connection_helper->GetClock()); quic_dispatcher_ = std::make_unique( @@ -52,6 +54,8 @@ ActiveQuicListener::ActiveQuicListener(Event::Dispatcher& dispatcher, quic_dispatcher_->InitializeWithWriter(writer.release()); } +ActiveQuicListener::~ActiveQuicListener() { onListenerShutdown(); } + void ActiveQuicListener::onListenerShutdown() { ENVOY_LOG(info, "Quic listener {} shutdown.", config_.name()); quic_dispatcher_->Shutdown(); @@ -63,7 +67,7 @@ void ActiveQuicListener::onData(Network::UdpRecvData& data) { envoyAddressInstanceToQuicSocketAddress(data.local_address_)); quic::QuicTime timestamp = quic::QuicTime::Zero() + - quic::QuicTime::Delta::FromMilliseconds(std::chrono::duration_cast( + quic::QuicTime::Delta::FromMicroseconds(std::chrono::duration_cast( data.receive_time_.time_since_epoch()) .count()); uint64_t num_slice = data.buffer_->getRawSlices(nullptr, 0); diff --git a/source/extensions/quic_listeners/quiche/active_quic_listener.h b/source/extensions/quic_listeners/quiche/active_quic_listener.h index 89ef57d83727d..d724790bd5299 100644 --- a/source/extensions/quic_listeners/quiche/active_quic_listener.h +++ b/source/extensions/quic_listeners/quiche/active_quic_listener.h @@ -32,6 +32,8 @@ class ActiveQuicListener : public Network::UdpListenerCallbacks, Network::UdpListenerPtr&& listener, Network::ListenerConfig& listener_config, const quic::QuicConfig& quic_config); + ~ActiveQuicListener() override; + // TODO(#7465): Make this a callback. void onListenerShutdown(); diff --git a/source/extensions/quic_listeners/quiche/codec_impl.cc b/source/extensions/quic_listeners/quiche/codec_impl.cc index fdb060cb7c159..4d00c6a8d679f 100644 --- a/source/extensions/quic_listeners/quiche/codec_impl.cc +++ b/source/extensions/quic_listeners/quiche/codec_impl.cc @@ -1,23 +1,68 @@ #include "extensions/quic_listeners/quiche/codec_impl.h" +#include "extensions/quic_listeners/quiche/envoy_quic_client_stream.h" +#include "extensions/quic_listeners/quiche/envoy_quic_server_stream.h" + namespace Envoy { namespace Quic { bool QuicHttpConnectionImplBase::wantsToWrite() { return quic_session_.HasDataToWrite(); } -// TODO(danzh): modify QUIC stack to react based on aggregated bytes across all -// the streams. And call StreamCallbackHelper::runHighWatermarkCallbacks() for each stream. -void QuicHttpConnectionImplBase::onUnderlyingConnectionAboveWriteBufferHighWatermark() { - NOT_IMPLEMENTED_GCOVR_EXCL_LINE; +QuicHttpServerConnectionImpl::QuicHttpServerConnectionImpl( + EnvoyQuicServerSession& quic_session, Http::ServerConnectionCallbacks& callbacks) + : QuicHttpConnectionImplBase(quic_session), quic_server_session_(quic_session) { + quic_session.setHttpConnectionCallbacks(callbacks); +} + +void QuicHttpServerConnectionImpl::onUnderlyingConnectionAboveWriteBufferHighWatermark() { + for (auto& it : quic_server_session_.stream_map()) { + if (!it.second->is_static()) { + dynamic_cast(it.second.get())->runHighWatermarkCallbacks(); + } + } } -void QuicHttpConnectionImplBase::onUnderlyingConnectionBelowWriteBufferLowWatermark() { - NOT_IMPLEMENTED_GCOVR_EXCL_LINE; +void QuicHttpServerConnectionImpl::onUnderlyingConnectionBelowWriteBufferLowWatermark() { + for (const auto& it : quic_server_session_.stream_map()) { + if (!it.second->is_static()) { + dynamic_cast(it.second.get())->runLowWatermarkCallbacks(); + } + } } void QuicHttpServerConnectionImpl::goAway() { quic_server_session_.SendGoAway(quic::QUIC_PEER_GOING_AWAY, "server shutdown imminent"); } +QuicHttpClientConnectionImpl::QuicHttpClientConnectionImpl(EnvoyQuicClientSession& session, + Http::ConnectionCallbacks& callbacks) + : QuicHttpConnectionImplBase(session), quic_client_session_(session) { + session.setHttpConnectionCallbacks(callbacks); +} + +Http::StreamEncoder& +QuicHttpClientConnectionImpl::newStream(Http::StreamDecoder& response_decoder) { + auto stream = dynamic_cast( + quic_client_session_.CreateOutgoingBidirectionalStream()); + stream->setDecoder(response_decoder); + return *stream; +} + +void QuicHttpClientConnectionImpl::onUnderlyingConnectionAboveWriteBufferHighWatermark() { + for (auto& it : quic_client_session_.stream_map()) { + if (!it.second->is_static()) { + dynamic_cast(it.second.get())->runHighWatermarkCallbacks(); + } + } +} + +void QuicHttpClientConnectionImpl::onUnderlyingConnectionBelowWriteBufferLowWatermark() { + for (const auto& it : quic_client_session_.stream_map()) { + if (!it.second->is_static()) { + dynamic_cast(it.second.get())->runLowWatermarkCallbacks(); + } + } +} + } // namespace Quic } // namespace Envoy diff --git a/source/extensions/quic_listeners/quiche/codec_impl.h b/source/extensions/quic_listeners/quiche/codec_impl.h index debff738cb044..141d73220d98a 100644 --- a/source/extensions/quic_listeners/quiche/codec_impl.h +++ b/source/extensions/quic_listeners/quiche/codec_impl.h @@ -3,6 +3,7 @@ #include "common/common/assert.h" #include "common/common/logger.h" +#include "extensions/quic_listeners/quiche/envoy_quic_client_session.h" #include "extensions/quic_listeners/quiche/envoy_quic_server_session.h" namespace Envoy { @@ -21,17 +22,11 @@ class QuicHttpConnectionImplBase : public virtual Http::Connection, // Bypassed. QUIC connection already hands all data to streams. NOT_REACHED_GCOVR_EXCL_LINE; } - Http::Protocol protocol() override { - // From HCM's view, QUIC should behave the same as Http2, only the stats - // should be different. - // TODO(danzh) add Http3 enum value for QUIC. - return Http::Protocol::Http2; - } + Http::Protocol protocol() override { return Http::Protocol::Http3; } + // Returns true if the session has data to send but queued in connection or // stream send buffer. bool wantsToWrite() override; - void onUnderlyingConnectionAboveWriteBufferHighWatermark() override; - void onUnderlyingConnectionBelowWriteBufferLowWatermark() override; protected: quic::QuicSpdySession& quic_session_; @@ -41,10 +36,7 @@ class QuicHttpServerConnectionImpl : public QuicHttpConnectionImplBase, public Http::ServerConnection { public: QuicHttpServerConnectionImpl(EnvoyQuicServerSession& quic_session, - Http::ServerConnectionCallbacks& callbacks) - : QuicHttpConnectionImplBase(quic_session), quic_server_session_(quic_session) { - quic_session.setHttpConnectionCallbacks(callbacks); - } + Http::ServerConnectionCallbacks& callbacks); // Http::Connection void goAway() override; @@ -52,10 +44,31 @@ class QuicHttpServerConnectionImpl : public QuicHttpConnectionImplBase, // TODO(danzh): Add double-GOAWAY support in QUIC. ENVOY_CONN_LOG(error, "Shutdown notice is not propagated to QUIC.", quic_server_session_); } + void onUnderlyingConnectionAboveWriteBufferHighWatermark() override; + void onUnderlyingConnectionBelowWriteBufferLowWatermark() override; private: EnvoyQuicServerSession& quic_server_session_; }; +class QuicHttpClientConnectionImpl : public QuicHttpConnectionImplBase, + public Http::ClientConnection { +public: + QuicHttpClientConnectionImpl(EnvoyQuicClientSession& session, + Http::ConnectionCallbacks& callbacks); + + // Http::ClientConnection + Http::StreamEncoder& newStream(Http::StreamDecoder& response_decoder) override; + + // Http::Connection + void goAway() override { NOT_REACHED_GCOVR_EXCL_LINE; } + void shutdownNotice() override { NOT_REACHED_GCOVR_EXCL_LINE; } + void onUnderlyingConnectionAboveWriteBufferHighWatermark() override; + void onUnderlyingConnectionBelowWriteBufferLowWatermark() override; + +private: + EnvoyQuicClientSession& quic_client_session_; +}; + } // namespace Quic } // namespace Envoy diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_alarm.h b/source/extensions/quic_listeners/quiche/envoy_quic_alarm.h index 4152f4c101c3f..22010987c4def 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_alarm.h +++ b/source/extensions/quic_listeners/quiche/envoy_quic_alarm.h @@ -20,7 +20,11 @@ class EnvoyQuicAlarm : public quic::QuicAlarm { EnvoyQuicAlarm(Event::Dispatcher& dispatcher, const quic::QuicClock& clock, quic::QuicArenaScopedPtr delegate); - ~EnvoyQuicAlarm() override { ASSERT(!IsSet()); }; + ~EnvoyQuicAlarm() override { + if (IsSet()) { + Cancel(); + } + }; // quic::QuicAlarm void CancelImpl() override; diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_client_connection.cc b/source/extensions/quic_listeners/quiche/envoy_quic_client_connection.cc new file mode 100644 index 0000000000000..9a6dfb01028cf --- /dev/null +++ b/source/extensions/quic_listeners/quiche/envoy_quic_client_connection.cc @@ -0,0 +1,176 @@ +#include "extensions/quic_listeners/quiche/envoy_quic_client_connection.h" + +#include "common/network/listen_socket_impl.h" +#include "common/network/socket_option_factory.h" + +#include "extensions/quic_listeners/quiche/envoy_quic_packet_writer.h" +#include "extensions/quic_listeners/quiche/envoy_quic_utils.h" +#include "extensions/transport_sockets/well_known_names.h" + +namespace Envoy { +namespace Quic { + +EnvoyQuicClientConnection::EnvoyQuicClientConnection( + const quic::QuicConnectionId& server_connection_id, + Network::Address::InstanceConstSharedPtr& initial_peer_address, + quic::QuicConnectionHelperInterface& helper, quic::QuicAlarmFactory& alarm_factory, + const quic::ParsedQuicVersionVector& supported_versions, + Network::Address::InstanceConstSharedPtr local_addr, Event::Dispatcher& dispatcher, + const Network::ConnectionSocket::OptionsSharedPtr& options) + : EnvoyQuicClientConnection(server_connection_id, helper, alarm_factory, supported_versions, + dispatcher, + createConnectionSocket(initial_peer_address, local_addr, options)) { +} + +EnvoyQuicClientConnection::EnvoyQuicClientConnection( + const quic::QuicConnectionId& server_connection_id, quic::QuicConnectionHelperInterface& helper, + quic::QuicAlarmFactory& alarm_factory, const quic::ParsedQuicVersionVector& supported_versions, + Event::Dispatcher& dispatcher, Network::ConnectionSocketPtr&& connection_socket) + : EnvoyQuicClientConnection(server_connection_id, helper, alarm_factory, + new EnvoyQuicPacketWriter(*connection_socket), true, + supported_versions, dispatcher, std::move(connection_socket)) {} + +EnvoyQuicClientConnection::EnvoyQuicClientConnection( + const quic::QuicConnectionId& server_connection_id, quic::QuicConnectionHelperInterface& helper, + quic::QuicAlarmFactory& alarm_factory, quic::QuicPacketWriter* writer, bool owns_writer, + const quic::ParsedQuicVersionVector& supported_versions, Event::Dispatcher& dispatcher, + Network::ConnectionSocketPtr&& connection_socket) + : EnvoyQuicConnection( + server_connection_id, + envoyAddressInstanceToQuicSocketAddress(connection_socket->remoteAddress()), helper, + alarm_factory, writer, owns_writer, quic::Perspective::IS_CLIENT, supported_versions, + std::move(connection_socket)), + dispatcher_(dispatcher) {} + +EnvoyQuicClientConnection::~EnvoyQuicClientConnection() { file_event_->setEnabled(0); } + +void EnvoyQuicClientConnection::processPacket( + Network::Address::InstanceConstSharedPtr local_address, + Network::Address::InstanceConstSharedPtr peer_address, Buffer::InstancePtr buffer, + MonotonicTime receive_time) { + quic::QuicTime timestamp = + quic::QuicTime::Zero() + + quic::QuicTime::Delta::FromMicroseconds( + std::chrono::duration_cast(receive_time.time_since_epoch()) + .count()); + uint64_t num_slice = buffer->getRawSlices(nullptr, 0); + ASSERT(num_slice == 1); + Buffer::RawSlice slice; + buffer->getRawSlices(&slice, 1); + quic::QuicReceivedPacket packet(reinterpret_cast(slice.mem_), slice.len_, timestamp, + /*owns_buffer=*/false, /*ttl=*/0, /*ttl_valid=*/true, + /*packet_headers=*/nullptr, /*headers_length=*/0, + /*owns_header_buffer*/ false); + ProcessUdpPacket(envoyAddressInstanceToQuicSocketAddress(local_address), + envoyAddressInstanceToQuicSocketAddress(peer_address), packet); +} + +uint64_t EnvoyQuicClientConnection::maxPacketSize() const { + // TODO(danzh) make this variable configurable to support jumbo frames. + return Network::MAX_UDP_PACKET_SIZE; +} + +void EnvoyQuicClientConnection::setUpConnectionSocket() { + if (connectionSocket()->ioHandle().isOpen()) { + file_event_ = dispatcher_.createFileEvent( + connectionSocket()->ioHandle().fd(), + [this](uint32_t events) -> void { onFileEvent(events); }, Event::FileTriggerType::Edge, + Event::FileReadyType::Read | Event::FileReadyType::Write); + + if (!Network::Socket::applyOptions(connectionSocket()->options(), *connectionSocket(), + envoy::api::v2::core::SocketOption::STATE_LISTENING)) { + ENVOY_LOG_MISC(error, "Fail to apply listening options"); + connectionSocket()->close(); + } + } + if (!connectionSocket()->ioHandle().isOpen()) { + CloseConnection(quic::QUIC_CONNECTION_CANCELLED, "Fail to setup connection socket.", + quic::ConnectionCloseBehavior::SILENT_CLOSE); + } +} + +void EnvoyQuicClientConnection::onFileEvent(uint32_t events) { + ENVOY_CONN_LOG(trace, "socket event: {}", *this, events); + ASSERT(events & (Event::FileReadyType::Read | Event::FileReadyType::Write)); + + if (events & Event::FileReadyType::Write) { + OnCanWrite(); + } + + // It's possible for a write event callback to close the connection, in such case ignore read + // event processing. + if (connected() && (events & Event::FileReadyType::Read)) { + uint32_t old_packets_dropped = packets_dropped_; + while (connected()) { + // Read till socket is drained. + // TODO(danzh): limit read times here. + MonotonicTime receive_time = dispatcher_.timeSource().monotonicTime(); + Api::IoCallUint64Result result = Network::Utility::readFromSocket( + *connectionSocket(), *this, receive_time, &packets_dropped_); + if (!result.ok()) { + if (result.err_->getErrorCode() != Api::IoError::IoErrorCode::Again) { + ENVOY_CONN_LOG(error, "recvmsg result {}: {}", *this, + static_cast(result.err_->getErrorCode()), + result.err_->getErrorDetails()); + } + // Stop reading. + break; + } + + if (result.rc_ == 0) { + // TODO(conqerAtapple): Is zero length packet interesting? If so add stats + // for it. Otherwise remove the warning log below. + ENVOY_CONN_LOG(trace, "received 0-length packet", *this); + } + + if (packets_dropped_ != old_packets_dropped) { + // The kernel tracks SO_RXQ_OVFL as a uint32 which can overflow to a smaller + // value. So as long as this count differs from previously recorded value, + // more packets are dropped by kernel. + uint32_t delta = (packets_dropped_ > old_packets_dropped) + ? (packets_dropped_ - old_packets_dropped) + : (packets_dropped_ + + (std::numeric_limits::max() - old_packets_dropped) + 1); + // TODO(danzh) add stats for this. + ENVOY_CONN_LOG(debug, + "Kernel dropped {} more packets. Consider increase receive buffer size.", + *this, delta); + } + } + } +} + +Network::ConnectionSocketPtr EnvoyQuicClientConnection::createConnectionSocket( + Network::Address::InstanceConstSharedPtr& peer_addr, + Network::Address::InstanceConstSharedPtr& local_addr, + const Network::ConnectionSocket::OptionsSharedPtr& options) { + Network::IoHandlePtr io_handle = peer_addr->socket(Network::Address::SocketType::Datagram); + auto connection_socket = + std::make_unique(std::move(io_handle), local_addr, peer_addr); + connection_socket->addOptions(Network::SocketOptionFactory::buildIpPacketInfoOptions()); + connection_socket->addOptions(Network::SocketOptionFactory::buildRxQueueOverFlowOptions()); + if (options != nullptr) { + connection_socket->addOptions(options); + } + if (!Network::Socket::applyOptions(connection_socket->options(), *connection_socket, + envoy::api::v2::core::SocketOption::STATE_PREBIND)) { + connection_socket->close(); + ENVOY_LOG_MISC(error, "Fail to apply pre-bind options"); + return connection_socket; + } + local_addr->bind(connection_socket->ioHandle().fd()); + ASSERT(local_addr->ip()); + if (local_addr->ip()->port() == 0) { + // Get ephemeral port number. + local_addr = Network::Address::addressFromFd(connection_socket->ioHandle().fd()); + } + if (!Network::Socket::applyOptions(connection_socket->options(), *connection_socket, + envoy::api::v2::core::SocketOption::STATE_BOUND)) { + ENVOY_LOG_MISC(error, "Fail to apply post-bind options"); + connection_socket->close(); + } + return connection_socket; +} + +} // namespace Quic +} // namespace Envoy diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_client_connection.h b/source/extensions/quic_listeners/quiche/envoy_quic_client_connection.h new file mode 100644 index 0000000000000..f9d79af6acca8 --- /dev/null +++ b/source/extensions/quic_listeners/quiche/envoy_quic_client_connection.h @@ -0,0 +1,66 @@ +#pragma once + +#include "envoy/event/dispatcher.h" + +#include "common/network/utility.h" + +#include "extensions/quic_listeners/quiche/envoy_quic_connection.h" + +namespace Envoy { +namespace Quic { + +// A client QuicConnection instance manages its own I/O events. +class EnvoyQuicClientConnection : public EnvoyQuicConnection, public Network::UdpPacketProcessor { +public: + // A connection socket will be created with given |local_addr|. If binding + // port not provided in |local_addr|, pick up a random port. + EnvoyQuicClientConnection(const quic::QuicConnectionId& server_connection_id, + Network::Address::InstanceConstSharedPtr& initial_peer_address, + quic::QuicConnectionHelperInterface& helper, + quic::QuicAlarmFactory& alarm_factory, + const quic::ParsedQuicVersionVector& supported_versions, + Network::Address::InstanceConstSharedPtr local_addr, + Event::Dispatcher& dispatcher, + const Network::ConnectionSocket::OptionsSharedPtr& options); + + // Overridden to un-register all file events. + ~EnvoyQuicClientConnection() override; + + void processPacket(Network::Address::InstanceConstSharedPtr local_address, + Network::Address::InstanceConstSharedPtr peer_address, + Buffer::InstancePtr buffer, MonotonicTime receive_time) override; + + uint64_t maxPacketSize() const override; + + // Register file event and apply socket options. + void setUpConnectionSocket(); + +private: + EnvoyQuicClientConnection(const quic::QuicConnectionId& server_connection_id, + quic::QuicConnectionHelperInterface& helper, + quic::QuicAlarmFactory& alarm_factory, + const quic::ParsedQuicVersionVector& supported_versions, + Event::Dispatcher& dispatcher, + Network::ConnectionSocketPtr&& connection_socket); + + EnvoyQuicClientConnection(const quic::QuicConnectionId& server_connection_id, + quic::QuicConnectionHelperInterface& helper, + quic::QuicAlarmFactory& alarm_factory, quic::QuicPacketWriter* writer, + bool owns_writer, + const quic::ParsedQuicVersionVector& supported_versions, + Event::Dispatcher& dispatcher, + Network::ConnectionSocketPtr&& connection_socket); + + Network::ConnectionSocketPtr + createConnectionSocket(Network::Address::InstanceConstSharedPtr& peer_addr, + Network::Address::InstanceConstSharedPtr& local_addr, + const Network::ConnectionSocket::OptionsSharedPtr& options); + + void onFileEvent(uint32_t events); + uint32_t packets_dropped_{0}; + Event::Dispatcher& dispatcher_; + Event::FileEventPtr file_event_; +}; + +} // namespace Quic +} // namespace Envoy diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_client_session.cc b/source/extensions/quic_listeners/quiche/envoy_quic_client_session.cc new file mode 100644 index 0000000000000..f9aeb81df3230 --- /dev/null +++ b/source/extensions/quic_listeners/quiche/envoy_quic_client_session.cc @@ -0,0 +1,87 @@ +#include "extensions/quic_listeners/quiche/envoy_quic_client_session.h" + +namespace Envoy { +namespace Quic { + +EnvoyQuicClientSession::EnvoyQuicClientSession( + const quic::QuicConfig& config, const quic::ParsedQuicVersionVector& supported_versions, + std::unique_ptr connection, const quic::QuicServerId& server_id, + quic::QuicCryptoClientConfig* crypto_config, + quic::QuicClientPushPromiseIndex* push_promise_index, Event::Dispatcher& dispatcher, + uint32_t send_buffer_limit) + : QuicFilterManagerConnectionImpl(*connection, dispatcher, send_buffer_limit), + quic::QuicSpdyClientSession(config, supported_versions, connection.release(), server_id, + crypto_config, push_promise_index) { + Initialize(); +} + +EnvoyQuicClientSession::~EnvoyQuicClientSession() { + ASSERT(!connection()->connected()); + QuicFilterManagerConnectionImpl::quic_connection_ = nullptr; +} + +absl::string_view EnvoyQuicClientSession::requestedServerName() const { + return {GetCryptoStream()->crypto_negotiated_params().sni}; +} + +void EnvoyQuicClientSession::connect() { + dynamic_cast(quic_connection_)->setUpConnectionSocket(); +} + +void EnvoyQuicClientSession::OnConnectionClosed(const quic::QuicConnectionCloseFrame& frame, + quic::ConnectionCloseSource source) { + quic::QuicSpdyClientSession::OnConnectionClosed(frame, source); + onConnectionCloseEvent(frame, source); +} + +void EnvoyQuicClientSession::Initialize() { + quic::QuicSpdyClientSession::Initialize(); + quic_connection_->setEnvoyConnection(*this); +} + +void EnvoyQuicClientSession::OnGoAway(const quic::QuicGoAwayFrame& frame) { + ENVOY_CONN_LOG(debug, "GOAWAY received with error {}: {}", *this, + quic::QuicErrorCodeToString(frame.error_code), frame.reason_phrase); + quic::QuicSpdyClientSession::OnGoAway(frame); + if (http_connection_callbacks_ != nullptr) { + http_connection_callbacks_->onGoAway(); + } +} + +void EnvoyQuicClientSession::OnCryptoHandshakeEvent(CryptoHandshakeEvent event) { + quic::QuicSpdyClientSession::OnCryptoHandshakeEvent(event); + if (event == HANDSHAKE_CONFIRMED) { + raiseEvent(Network::ConnectionEvent::Connected); + } +} + +void EnvoyQuicClientSession::cryptoConnect() { + CryptoConnect(); + set_max_allowed_push_id(0u); + // Wait for finishing handshake with server. + dispatcher_.run(Event::Dispatcher::RunType::Block); +} + +std::unique_ptr EnvoyQuicClientSession::CreateClientStream() { + auto stream = std::make_unique(GetNextOutgoingBidirectionalStreamId(), + this, quic::BIDIRECTIONAL); + return stream; +} + +quic::QuicSpdyStream* EnvoyQuicClientSession::CreateIncomingStream(quic::QuicStreamId id) { + if (!ShouldCreateIncomingStream(id)) { + return nullptr; + } + auto stream = new EnvoyQuicClientStream(id, this, quic::READ_UNIDIRECTIONAL); + ActivateStream(std::unique_ptr(stream)); + return stream; +} + +quic::QuicSpdyStream* EnvoyQuicClientSession::CreateIncomingStream(quic::PendingStream* pending) { + auto stream = new EnvoyQuicClientStream(pending, this, quic::READ_UNIDIRECTIONAL); + ActivateStream(std::unique_ptr(stream)); + return stream; +} + +} // namespace Quic +} // namespace Envoy diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_client_session.h b/source/extensions/quic_listeners/quiche/envoy_quic_client_session.h new file mode 100644 index 0000000000000..e5dd9862173db --- /dev/null +++ b/source/extensions/quic_listeners/quiche/envoy_quic_client_session.h @@ -0,0 +1,82 @@ +#pragma once + +#pragma GCC diagnostic push +// QUICHE allows unused parameters. +#pragma GCC diagnostic ignored "-Wunused-parameter" +// QUICHE uses offsetof(). +#pragma GCC diagnostic ignored "-Winvalid-offsetof" +#pragma GCC diagnostic ignored "-Wtype-limits" + +#include "quiche/quic/core/http/quic_spdy_client_session.h" + +#pragma GCC diagnostic pop + +#include "extensions/quic_listeners/quiche/envoy_quic_client_stream.h" +#include "extensions/quic_listeners/quiche/envoy_quic_client_connection.h" +#include "extensions/quic_listeners/quiche/quic_filter_manager_connection_impl.h" + +namespace Envoy { +namespace Quic { + +// Act as a Network::ClientConnection to ClientCodec. +// TODO(danzh) This class doesn't need to inherit Network::FilterManager +// interface but need all other Network::Connection implementation in +// QuicFilterManagerConnectionImpl. Refactor QuicFilterManagerConnectionImpl to +// move FilterManager interface to EnvoyQuicServerSession. +class EnvoyQuicClientSession : public QuicFilterManagerConnectionImpl, + public quic::QuicSpdyClientSession, + public Network::ClientConnection { +public: + EnvoyQuicClientSession(const quic::QuicConfig& config, + const quic::ParsedQuicVersionVector& supported_versions, + std::unique_ptr connection, + const quic::QuicServerId& server_id, + quic::QuicCryptoClientConfig* crypto_config, + quic::QuicClientPushPromiseIndex* push_promise_index, + Event::Dispatcher& dispatcher, uint32_t send_buffer_limit); + + ~EnvoyQuicClientSession() override; + + // Called by QuicHttpClientConnectionImpl before creating data streams. + void setHttpConnectionCallbacks(Http::ConnectionCallbacks& callbacks) { + http_connection_callbacks_ = &callbacks; + } + + // Network::Connection + absl::string_view requestedServerName() const override; + + // Network::ClientConnection + // Only register socket and set socket options. + void connect() override; + + // quic::QuicSession + void OnConnectionClosed(const quic::QuicConnectionCloseFrame& frame, + quic::ConnectionCloseSource source) override; + void Initialize() override; + void OnGoAway(const quic::QuicGoAwayFrame& frame) override; + // quic::QuicSpdyClientSessionBase + void OnCryptoHandshakeEvent(CryptoHandshakeEvent event) override; + + // Do version negotiation and crypto handshake. Fail the connection if server + // doesn't support the one and only supported version. + // This call will block till the handshake finished with either success to + // failure. + void cryptoConnect(); + + using quic::QuicSpdyClientSession::stream_map; + +protected: + // quic::QuicSpdyClientSession + std::unique_ptr CreateClientStream() override; + // quic::QuicSpdySession + quic::QuicSpdyStream* CreateIncomingStream(quic::QuicStreamId id) override; + quic::QuicSpdyStream* CreateIncomingStream(quic::PendingStream* pending) override; + +private: + // These callbacks are owned by network filters and quic session should out live + // them. + Http::ConnectionCallbacks* http_connection_callbacks_{nullptr}; +}; + +} // namespace Quic +} // namespace Envoy diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_client_stream.cc b/source/extensions/quic_listeners/quiche/envoy_quic_client_stream.cc new file mode 100644 index 0000000000000..3fc24dea4e25c --- /dev/null +++ b/source/extensions/quic_listeners/quiche/envoy_quic_client_stream.cc @@ -0,0 +1,219 @@ +#include "extensions/quic_listeners/quiche/envoy_quic_client_stream.h" + +#pragma GCC diagnostic push +// QUICHE allows unused parameters. +#pragma GCC diagnostic ignored "-Wunused-parameter" +// QUICHE uses offsetof(). +#pragma GCC diagnostic ignored "-Winvalid-offsetof" + +#include "quiche/quic/core/quic_session.h" +#include "quiche/quic/core/http/quic_header_list.h" +#include "quiche/quic/core/quic_session.h" +#include "quiche/spdy/core/spdy_header_block.h" +#include "extensions/quic_listeners/quiche/platform/quic_mem_slice_span_impl.h" + +#pragma GCC diagnostic pop + +#include "extensions/quic_listeners/quiche/envoy_quic_utils.h" +#include "extensions/quic_listeners/quiche/envoy_quic_client_session.h" + +#include "common/buffer/buffer_impl.h" +#include "common/http/header_map_impl.h" +#include "common/common/assert.h" + +namespace Envoy { +namespace Quic { + +EnvoyQuicClientStream::EnvoyQuicClientStream(quic::QuicStreamId id, + quic::QuicSpdyClientSession* client_session, + quic::StreamType type) + : quic::QuicSpdyClientStream(id, client_session, type), + EnvoyQuicStream( + session()->config()->GetInitialStreamFlowControlWindowToSend(), + [this]() { runLowWatermarkCallbacks(); }, [this]() { runHighWatermarkCallbacks(); }) {} + +EnvoyQuicClientStream::EnvoyQuicClientStream(quic::PendingStream* pending, + quic::QuicSpdyClientSession* client_session, + quic::StreamType type) + : quic::QuicSpdyClientStream(pending, client_session, type), + EnvoyQuicStream( + session()->config()->GetInitialStreamFlowControlWindowToSend(), + [this]() { runLowWatermarkCallbacks(); }, [this]() { runHighWatermarkCallbacks(); }) {} + +void EnvoyQuicClientStream::encode100ContinueHeaders(const Http::HeaderMap& headers) { + ASSERT(headers.Status()->value() == "100"); + encodeHeaders(headers, false); +} + +void EnvoyQuicClientStream::encodeHeaders(const Http::HeaderMap& headers, bool end_stream) { + ENVOY_STREAM_LOG(debug, "encodeHeaders: (end_stream={}) {}.", *this, end_stream, headers); + WriteHeaders(envoyHeadersToSpdyHeaderBlock(headers), end_stream, nullptr); + local_end_stream_ = end_stream; +} + +void EnvoyQuicClientStream::encodeData(Buffer::Instance& data, bool end_stream) { + ENVOY_STREAM_LOG(debug, "encodeData (end_stream={}) of {} bytes.", *this, end_stream, + data.length()); + local_end_stream_ = end_stream; + // This is counting not serialized bytes in the send buffer. + uint64_t bytes_to_send_old = BufferedDataBytes(); + // QUIC stream must take all. + WriteBodySlices(quic::QuicMemSliceSpan(quic::QuicMemSliceSpanImpl(data)), end_stream); + ASSERT(data.length() == 0); + + uint64_t bytes_to_send_new = BufferedDataBytes(); + ASSERT(bytes_to_send_old <= bytes_to_send_new); + if (bytes_to_send_new > bytes_to_send_old) { + // If buffered bytes changed, update stream and session's watermark book + // keeping. + sendBufferSimulation().checkHighWatermark(bytes_to_send_new); + dynamic_cast(session())->adjustBytesToSend(bytes_to_send_new - + bytes_to_send_old); + } +} + +void EnvoyQuicClientStream::encodeTrailers(const Http::HeaderMap& trailers) { + ASSERT(!local_end_stream_); + local_end_stream_ = true; + ENVOY_STREAM_LOG(debug, "encodeTrailers: {}.", *this, trailers); + WriteTrailers(envoyHeadersToSpdyHeaderBlock(trailers), nullptr); +} + +void EnvoyQuicClientStream::encodeMetadata(const Http::MetadataMapVector& /*metadata_map_vector*/) { + ASSERT(false, "Metadata Frame is not supported in QUIC"); +} + +void EnvoyQuicClientStream::resetStream(Http::StreamResetReason reason) { + // Higher layers expect calling resetStream() to immediately raise reset callbacks. + runResetCallbacks(reason); + + Reset(envoyResetReasonToQuicRstError(reason)); +} + +void EnvoyQuicClientStream::switchStreamBlockState(bool should_block) { + ASSERT(FinishedReadingHeaders(), + "codec buffer limit is reached before response body is delivered."); + if (should_block) { + sequencer()->SetBlockedUntilFlush(); + } else { + sequencer()->SetUnblocked(); + } +} + +void EnvoyQuicClientStream::OnInitialHeadersComplete(bool fin, size_t frame_len, + const quic::QuicHeaderList& header_list) { + quic::QuicSpdyStream::OnInitialHeadersComplete(fin, frame_len, header_list); + if (rst_sent()) { + return; + } + ASSERT(decoder() != nullptr); + ASSERT(headers_decompressed()); + decoder()->decodeHeaders(quicHeadersToEnvoyHeaders(header_list), /*end_stream=*/fin); + if (fin) { + end_stream_decoded_ = true; + } + ConsumeHeaderList(); +} + +void EnvoyQuicClientStream::OnBodyAvailable() { + ASSERT(FinishedReadingHeaders()); + ASSERT(read_disable_counter_ == 0); + ASSERT(!in_encode_data_callstack_); + in_encode_data_callstack_ = true; + + Buffer::InstancePtr buffer = std::make_unique(); + // TODO(danzh): check Envoy per stream buffer limit. + // Currently read out all the data. + while (HasBytesToRead()) { + struct iovec iov; + int num_regions = GetReadableRegions(&iov, 1); + ASSERT(num_regions > 0); + size_t bytes_read = iov.iov_len; + Buffer::RawSlice slice; + buffer->reserve(bytes_read, &slice, 1); + ASSERT(slice.len_ >= bytes_read); + slice.len_ = bytes_read; + memcpy(slice.mem_, iov.iov_base, iov.iov_len); + buffer->commit(&slice, 1); + MarkConsumed(bytes_read); + } + + // True if no trailer and FIN read. + bool finished_reading = IsDoneReading(); + bool empty_payload_with_fin = buffer->length() == 0 && finished_reading; + if (!empty_payload_with_fin || !end_stream_decoded_) { + ASSERT(decoder() != nullptr); + decoder()->decodeData(*buffer, finished_reading); + if (finished_reading) { + end_stream_decoded_ = true; + } + } + + if (!sequencer()->IsClosed()) { + in_encode_data_callstack_ = false; + if (read_disable_counter_ > 0) { + // If readDisable() was ever called during decodeData() and it meant to disable + // reading from downstream, the call must have been deferred. Call it now. + switchStreamBlockState(true); + } + return; + } + + if (!quic::VersionUsesQpack(transport_version()) && !FinishedReadingTrailers()) { + // For Google QUIC implementation, trailers may arrived earlier and wait to + // be consumed after reading all the body. Consume it here. + // IETF QUIC shouldn't reach here because trailers are sent on same stream. + decoder()->decodeTrailers(spdyHeaderBlockToEnvoyHeaders(received_trailers())); + MarkTrailersConsumed(); + } + OnFinRead(); + in_encode_data_callstack_ = false; +} + +void EnvoyQuicClientStream::OnTrailingHeadersComplete(bool fin, size_t frame_len, + const quic::QuicHeaderList& header_list) { + quic::QuicSpdyStream::OnTrailingHeadersComplete(fin, frame_len, header_list); + if (session()->connection()->connected() && + (quic::VersionUsesQpack(transport_version()) || sequencer()->IsClosed()) && + !FinishedReadingTrailers()) { + // Before QPack trailers can arrive before body. Only decode trailers after finishing decoding + // body. + ASSERT(decoder() != nullptr); + decoder()->decodeTrailers(spdyHeaderBlockToEnvoyHeaders(received_trailers())); + MarkTrailersConsumed(); + } +} + +void EnvoyQuicClientStream::OnStreamReset(const quic::QuicRstStreamFrame& frame) { + quic::QuicSpdyClientStream::OnStreamReset(frame); + runResetCallbacks(quicRstErrorToEnvoyResetReason(frame.error_code)); +} + +void EnvoyQuicClientStream::OnConnectionClosed(quic::QuicErrorCode error, + quic::ConnectionCloseSource source) { + quic::QuicSpdyClientStream::OnConnectionClosed(error, source); + runResetCallbacks(quicErrorCodeToEnvoyResetReason(error)); +} + +void EnvoyQuicClientStream::OnCanWrite() { + uint64_t buffered_data_old = BufferedDataBytes(); + quic::QuicSpdyClientStream::OnCanWrite(); + uint64_t buffered_data_new = BufferedDataBytes(); + // As long as OnCanWriteNewData() is no-op, data to sent in buffer shouldn't + // increase. + ASSERT(buffered_data_new <= buffered_data_old); + if (buffered_data_new < buffered_data_old) { + sendBufferSimulation().checkLowWatermark(buffered_data_new); + dynamic_cast(session())->adjustBytesToSend(buffered_data_new - + buffered_data_old); + } +} + +uint32_t EnvoyQuicClientStream::streamId() { return id(); } + +Network::Connection* EnvoyQuicClientStream::connection() { + return dynamic_cast(session()); +} + +} // namespace Quic +} // namespace Envoy diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_client_stream.h b/source/extensions/quic_listeners/quiche/envoy_quic_client_stream.h new file mode 100644 index 0000000000000..6bc88ff8a9e81 --- /dev/null +++ b/source/extensions/quic_listeners/quiche/envoy_quic_client_stream.h @@ -0,0 +1,56 @@ +#pragma once + +#pragma GCC diagnostic push +// QUICHE allows unused parameters. +#pragma GCC diagnostic ignored "-Wunused-parameter" +// QUICHE uses offsetof(). +#pragma GCC diagnostic ignored "-Winvalid-offsetof" +#include "quiche/quic/core/http/quic_spdy_client_stream.h" + +#pragma GCC diagnostic pop + +#include "extensions/quic_listeners/quiche/envoy_quic_stream.h" + +namespace Envoy { +namespace Quic { + +// This class is a quic stream and also a request encoder. +class EnvoyQuicClientStream : public quic::QuicSpdyClientStream, public EnvoyQuicStream { +public: + EnvoyQuicClientStream(quic::QuicStreamId id, quic::QuicSpdyClientSession* client_session, + quic::StreamType type); + EnvoyQuicClientStream(quic::PendingStream* pending, quic::QuicSpdyClientSession* client_session, + quic::StreamType type); + + // Http::StreamEncoder + void encode100ContinueHeaders(const Http::HeaderMap& headers) override; + void encodeHeaders(const Http::HeaderMap& headers, bool end_stream) override; + void encodeData(Buffer::Instance& data, bool end_stream) override; + void encodeTrailers(const Http::HeaderMap& trailers) override; + void encodeMetadata(const Http::MetadataMapVector& metadata_map_vector) override; + + // Http::Stream + void resetStream(Http::StreamResetReason reason) override; + // quic::QuicSpdyStream + void OnBodyAvailable() override; + void OnStreamReset(const quic::QuicRstStreamFrame& frame) override; + void OnCanWrite() override; + // quic::Stream + void OnConnectionClosed(quic::QuicErrorCode error, quic::ConnectionCloseSource source) override; + +protected: + // EnvoyQuicStream + void switchStreamBlockState(bool should_block) override; + uint32_t streamId() override; + Network::Connection* connection() override; + + // quic::QuicSpdyStream + // Overridden to pass headers to decoder. + void OnInitialHeadersComplete(bool fin, size_t frame_len, + const quic::QuicHeaderList& header_list) override; + void OnTrailingHeadersComplete(bool fin, size_t frame_len, + const quic::QuicHeaderList& header_list) override; +}; + +} // namespace Quic +} // namespace Envoy diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_connection.cc b/source/extensions/quic_listeners/quiche/envoy_quic_connection.cc index dcc311a6eaac6..f2459bf79a190 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_connection.cc +++ b/source/extensions/quic_listeners/quiche/envoy_quic_connection.cc @@ -19,7 +19,10 @@ EnvoyQuicConnection::EnvoyQuicConnection(const quic::QuicConnectionId& server_co EnvoyQuicConnection::~EnvoyQuicConnection() { connection_socket_->close(); } -uint64_t EnvoyQuicConnection::id() const { return envoy_connection_->id(); } +uint64_t EnvoyQuicConnection::id() const { + ASSERT(envoy_connection_ != nullptr); + return envoy_connection_->id(); +} } // namespace Quic } // namespace Envoy diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_dispatcher.cc b/source/extensions/quic_listeners/quiche/envoy_quic_dispatcher.cc index 07126a2908588..b4f103469d818 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_dispatcher.cc +++ b/source/extensions/quic_listeners/quiche/envoy_quic_dispatcher.cc @@ -44,7 +44,8 @@ quic::QuicSession* EnvoyQuicDispatcher::CreateQuicSession( listener_stats_); auto quic_session = new EnvoyQuicServerSession( config(), quic::ParsedQuicVersionVector{version}, std::move(quic_connection), this, - session_helper(), crypto_config(), compressed_certs_cache(), dispatcher_); + session_helper(), crypto_config(), compressed_certs_cache(), dispatcher_, + listener_config_.perConnectionBufferLimitBytes()); quic_session->Initialize(); // Filter chain can't be retrieved here as self address is unknown at this // point. diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_fake_proof_verifier.h b/source/extensions/quic_listeners/quiche/envoy_quic_fake_proof_verifier.h index 0861e09fb4d9b..c8355717bccb8 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_fake_proof_verifier.h +++ b/source/extensions/quic_listeners/quiche/envoy_quic_fake_proof_verifier.h @@ -48,7 +48,8 @@ class EnvoyQuicFakeProofVerifier : public quic::ProofVerifier { const quic::ProofVerifyContext* /*context*/, std::string* /*error_details*/, std::unique_ptr* /*details*/, std::unique_ptr /*callback*/) override { - if (cert_sct == "Fake timestamp" && certs.size() == 1 && certs[0] == "Fake cert") { + // Cert SCT support is not enabled for fake ProofSource. + if (cert_sct == "" && certs.size() == 1 && certs[0] == "Fake cert") { return quic::QUIC_SUCCESS; } return quic::QUIC_FAILURE; diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_server_session.cc b/source/extensions/quic_listeners/quiche/envoy_quic_server_session.cc index 021c390e474d5..456cc77da297e 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_server_session.cc +++ b/source/extensions/quic_listeners/quiche/envoy_quic_server_session.cc @@ -9,6 +9,7 @@ #include "quiche/quic/core/quic_crypto_server_stream.h" #pragma GCC diagnostic pop +#include "common/common/assert.h" #include "extensions/quic_listeners/quiche/envoy_quic_server_stream.h" namespace Envoy { @@ -18,10 +19,17 @@ EnvoyQuicServerSession::EnvoyQuicServerSession( const quic::QuicConfig& config, const quic::ParsedQuicVersionVector& supported_versions, std::unique_ptr connection, quic::QuicSession::Visitor* visitor, quic::QuicCryptoServerStream::Helper* helper, const quic::QuicCryptoServerConfig* crypto_config, - quic::QuicCompressedCertsCache* compressed_certs_cache, Event::Dispatcher& dispatcher) + quic::QuicCompressedCertsCache* compressed_certs_cache, Event::Dispatcher& dispatcher, + uint32_t send_buffer_limit) : quic::QuicServerSessionBase(config, supported_versions, connection.get(), visitor, helper, crypto_config, compressed_certs_cache), - QuicFilterManagerConnectionImpl(std::move(connection), dispatcher) {} + QuicFilterManagerConnectionImpl(*connection, dispatcher, send_buffer_limit), + quic_connection_(std::move(connection)) {} + +EnvoyQuicServerSession::~EnvoyQuicServerSession() { + ASSERT(!quic_connection_->connected()); + QuicFilterManagerConnectionImpl::quic_connection_ = nullptr; +} absl::string_view EnvoyQuicServerSession::requestedServerName() const { return {GetCryptoStream()->crypto_negotiated_params().sni}; diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_server_session.h b/source/extensions/quic_listeners/quiche/envoy_quic_server_session.h index d849aa0161cac..5c5561cbb7f52 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_server_session.h +++ b/source/extensions/quic_listeners/quiche/envoy_quic_server_session.h @@ -30,7 +30,9 @@ class EnvoyQuicServerSession : public quic::QuicServerSessionBase, quic::QuicCryptoServerStream::Helper* helper, const quic::QuicCryptoServerConfig* crypto_config, quic::QuicCompressedCertsCache* compressed_certs_cache, - Event::Dispatcher& dispatcher); + Event::Dispatcher& dispatcher, uint32_t send_buffer_limit); + + ~EnvoyQuicServerSession() override; // Network::Connection absl::string_view requestedServerName() const override; @@ -48,6 +50,8 @@ class EnvoyQuicServerSession : public quic::QuicServerSessionBase, // quic::QuicSpdySession void OnCryptoHandshakeEvent(CryptoHandshakeEvent event) override; + using quic::QuicSession::stream_map; + protected: // quic::QuicServerSessionBase quic::QuicCryptoServerStreamBase* @@ -64,6 +68,7 @@ class EnvoyQuicServerSession : public quic::QuicServerSessionBase, private: void setUpRequestDecoder(EnvoyQuicStream& stream); + std::unique_ptr quic_connection_; // These callbacks are owned by network filters and quic session should out live // them. Http::ServerConnectionCallbacks* http_connection_callbacks_{nullptr}; diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_server_stream.cc b/source/extensions/quic_listeners/quiche/envoy_quic_server_stream.cc index 35bd63f811355..9f05182af88af 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_server_stream.cc +++ b/source/extensions/quic_listeners/quiche/envoy_quic_server_stream.cc @@ -3,7 +3,7 @@ #include #include -#include +#include #pragma GCC diagnostic push // QUICHE allows unused parameters. @@ -14,10 +14,12 @@ #include "quiche/quic/core/http/quic_header_list.h" #include "quiche/quic/core/quic_session.h" #include "quiche/spdy/core/spdy_header_block.h" - +#include "extensions/quic_listeners/quiche/platform/quic_mem_slice_span_impl.h" #pragma GCC diagnostic pop #include "extensions/quic_listeners/quiche/envoy_quic_utils.h" +#include "extensions/quic_listeners/quiche/envoy_quic_server_session.h" + #include "common/buffer/buffer_impl.h" #include "common/http/header_map_impl.h" #include "common/common/assert.h" @@ -25,28 +27,86 @@ namespace Envoy { namespace Quic { +EnvoyQuicServerStream::EnvoyQuicServerStream(quic::QuicStreamId id, quic::QuicSpdySession* session, + quic::StreamType type) + : quic::QuicSpdyServerStreamBase(id, session, type), + EnvoyQuicStream( + session->config()->GetInitialStreamFlowControlWindowToSend(), + [this]() { runLowWatermarkCallbacks(); }, [this]() { runHighWatermarkCallbacks(); }) {} + +EnvoyQuicServerStream::EnvoyQuicServerStream(quic::PendingStream* pending, + quic::QuicSpdySession* session, quic::StreamType type) + : quic::QuicSpdyServerStreamBase(pending, session, type), + EnvoyQuicStream( + session->config()->GetInitialStreamFlowControlWindowToSend(), + [this]() { runLowWatermarkCallbacks(); }, [this]() { runHighWatermarkCallbacks(); }) {} + void EnvoyQuicServerStream::encode100ContinueHeaders(const Http::HeaderMap& headers) { ASSERT(headers.Status()->value() == "100"); encodeHeaders(headers, false); } -void EnvoyQuicServerStream::encodeHeaders(const Http::HeaderMap& /*headers*/, bool /*end_stream*/) { - NOT_IMPLEMENTED_GCOVR_EXCL_LINE; + +void EnvoyQuicServerStream::encodeHeaders(const Http::HeaderMap& headers, bool end_stream) { + ENVOY_STREAM_LOG(debug, "encodeHeaders (end_stream={}) {}.", *this, end_stream, headers); + WriteHeaders(envoyHeadersToSpdyHeaderBlock(headers), end_stream, nullptr); + local_end_stream_ = end_stream; } -void EnvoyQuicServerStream::encodeData(Buffer::Instance& /*data*/, bool /*end_stream*/) { - NOT_IMPLEMENTED_GCOVR_EXCL_LINE; + +void EnvoyQuicServerStream::encodeData(Buffer::Instance& data, bool end_stream) { + ENVOY_STREAM_LOG(debug, "encodeData (end_stream={}) of {} bytes.", *this, end_stream, + data.length()); + local_end_stream_ = end_stream; + // This is counting not serialized bytes in the send buffer. + uint64_t bytes_to_send_old = BufferedDataBytes(); + // QUIC stream must take all. + WriteBodySlices(quic::QuicMemSliceSpan(quic::QuicMemSliceSpanImpl(data)), end_stream); + ASSERT(data.length() == 0); + + uint64_t bytes_to_send_new = BufferedDataBytes(); + ASSERT(bytes_to_send_old <= bytes_to_send_new); + if (bytes_to_send_new > bytes_to_send_old) { + // If buffered bytes changed, update stream and session's watermark book + // keeping. + sendBufferSimulation().checkHighWatermark(bytes_to_send_new); + dynamic_cast(session())->adjustBytesToSend(bytes_to_send_new - + bytes_to_send_old); + } } -void EnvoyQuicServerStream::encodeTrailers(const Http::HeaderMap& /*trailers*/) { - NOT_IMPLEMENTED_GCOVR_EXCL_LINE; + +void EnvoyQuicServerStream::encodeTrailers(const Http::HeaderMap& trailers) { + ASSERT(!local_end_stream_); + local_end_stream_ = true; + ENVOY_STREAM_LOG(debug, "encodeTrailers: {}.", *this, trailers); + WriteTrailers(envoyHeadersToSpdyHeaderBlock(trailers), nullptr); } + void EnvoyQuicServerStream::encodeMetadata(const Http::MetadataMapVector& /*metadata_map_vector*/) { + // Metadata Frame is not supported in QUIC. NOT_IMPLEMENTED_GCOVR_EXCL_LINE; } -void EnvoyQuicServerStream::resetStream(Http::StreamResetReason /*reason*/) { - NOT_IMPLEMENTED_GCOVR_EXCL_LINE; +void EnvoyQuicServerStream::resetStream(Http::StreamResetReason reason) { + // Higher layers expect calling resetStream() to immediately raise reset callbacks. + runResetCallbacks(reason); + if (local_end_stream_ && !reading_stopped()) { + // This is after 200 early response. Reset with QUIC_STREAM_NO_ERROR instead + // of propagating original reset reason. + StopReading(); + } else { + Reset(envoyResetReasonToQuicRstError(reason)); + } } -void EnvoyQuicServerStream::readDisable(bool /*disable*/) { NOT_IMPLEMENTED_GCOVR_EXCL_LINE; } +void EnvoyQuicServerStream::switchStreamBlockState(bool should_block) { + ASSERT(FinishedReadingHeaders(), + "Upperstream buffer limit is reached before request body is delivered."); + if (should_block) { + sequencer()->SetBlockedUntilFlush(); + } else { + ASSERT(read_disable_counter_ == 0, "readDisable called in btw."); + sequencer()->SetUnblocked(); + } +} void EnvoyQuicServerStream::OnInitialHeadersComplete(bool fin, size_t frame_len, const quic::QuicHeaderList& header_list) { @@ -54,10 +114,18 @@ void EnvoyQuicServerStream::OnInitialHeadersComplete(bool fin, size_t frame_len, ASSERT(decoder() != nullptr); ASSERT(headers_decompressed()); decoder()->decodeHeaders(quicHeadersToEnvoyHeaders(header_list), /*end_stream=*/fin); + if (fin) { + end_stream_decoded_ = true; + } ConsumeHeaderList(); } void EnvoyQuicServerStream::OnBodyAvailable() { + ASSERT(FinishedReadingHeaders()); + ASSERT(read_disable_counter_ == 0); + ASSERT(!in_encode_data_callstack_); + in_encode_data_callstack_ = true; + Buffer::InstancePtr buffer = std::make_unique(); // TODO(danzh): check Envoy per stream buffer limit. // Currently read out all the data. @@ -77,18 +145,34 @@ void EnvoyQuicServerStream::OnBodyAvailable() { // True if no trailer and FIN read. bool finished_reading = IsDoneReading(); - // If this is the last stream data, set end_stream if there is no - // trailers. - ASSERT(decoder() != nullptr); - decoder()->decodeData(*buffer, finished_reading); - if (!quic::VersionUsesQpack(transport_version()) && sequencer()->IsClosed() && - !FinishedReadingTrailers()) { + bool empty_payload_with_fin = buffer->length() == 0 && finished_reading; + if (!empty_payload_with_fin || !end_stream_decoded_) { + ASSERT(decoder() != nullptr); + decoder()->decodeData(*buffer, finished_reading); + if (finished_reading) { + end_stream_decoded_ = true; + } + } + + if (!sequencer()->IsClosed()) { + in_encode_data_callstack_ = false; + if (read_disable_counter_ > 0) { + // If readDisable() was ever called during decodeData() and it meant to disable + // reading from downstream, the call must have been deferred. Call it now. + switchStreamBlockState(true); + } + return; + } + + if (!quic::VersionUsesQpack(transport_version()) && !FinishedReadingTrailers()) { // For Google QUIC implementation, trailers may arrived earlier and wait to // be consumed after reading all the body. Consume it here. // IETF QUIC shouldn't reach here because trailers are sent on same stream. decoder()->decodeTrailers(spdyHeaderBlockToEnvoyHeaders(received_trailers())); MarkTrailersConsumed(); } + OnFinRead(); + in_encode_data_callstack_ = false; } void EnvoyQuicServerStream::OnTrailingHeadersComplete(bool fin, size_t frame_len, @@ -107,25 +191,33 @@ void EnvoyQuicServerStream::OnTrailingHeadersComplete(bool fin, size_t frame_len void EnvoyQuicServerStream::OnStreamReset(const quic::QuicRstStreamFrame& frame) { quic::QuicSpdyServerStreamBase::OnStreamReset(frame); - Http::StreamResetReason reason; - if (frame.error_code == quic::QUIC_REFUSED_STREAM) { - reason = Http::StreamResetReason::RemoteRefusedStreamReset; - } else { - reason = Http::StreamResetReason::RemoteReset; - } - runResetCallbacks(reason); + runResetCallbacks(quicRstErrorToEnvoyResetReason(frame.error_code)); } void EnvoyQuicServerStream::OnConnectionClosed(quic::QuicErrorCode error, quic::ConnectionCloseSource source) { quic::QuicSpdyServerStreamBase::OnConnectionClosed(error, source); - Http::StreamResetReason reason; - if (error == quic::QUIC_NO_ERROR) { - reason = Http::StreamResetReason::ConnectionTermination; - } else { - reason = Http::StreamResetReason::ConnectionFailure; + runResetCallbacks(quicErrorCodeToEnvoyResetReason(error)); +} + +void EnvoyQuicServerStream::OnCanWrite() { + uint64_t buffered_data_old = BufferedDataBytes(); + quic::QuicSpdyServerStreamBase::OnCanWrite(); + uint64_t buffered_data_new = BufferedDataBytes(); + // As long as OnCanWriteNewData() is no-op, data to sent in buffer shouldn't + // increase. + ASSERT(buffered_data_new <= buffered_data_old); + if (buffered_data_new < buffered_data_old) { + sendBufferSimulation().checkLowWatermark(buffered_data_new); + dynamic_cast(session())->adjustBytesToSend(buffered_data_new - + buffered_data_old); } - runResetCallbacks(reason); +} + +uint32_t EnvoyQuicServerStream::streamId() { return id(); } + +Network::Connection* EnvoyQuicServerStream::connection() { + return dynamic_cast(session()); } } // namespace Quic diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_server_stream.h b/source/extensions/quic_listeners/quiche/envoy_quic_server_stream.h index 047970f4fdbed..e38317907d8ec 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_server_stream.h +++ b/source/extensions/quic_listeners/quiche/envoy_quic_server_stream.h @@ -18,11 +18,10 @@ namespace Quic { class EnvoyQuicServerStream : public quic::QuicSpdyServerStreamBase, public EnvoyQuicStream { public: EnvoyQuicServerStream(quic::QuicStreamId id, quic::QuicSpdySession* session, - quic::StreamType type) - : quic::QuicSpdyServerStreamBase(id, session, type) {} + quic::StreamType type); + EnvoyQuicServerStream(quic::PendingStream* pending, quic::QuicSpdySession* session, - quic::StreamType type) - : quic::QuicSpdyServerStreamBase(pending, session, type) {} + quic::StreamType type); // Http::StreamEncoder void encode100ContinueHeaders(const Http::HeaderMap& headers) override; @@ -33,14 +32,19 @@ class EnvoyQuicServerStream : public quic::QuicSpdyServerStreamBase, public Envo // Http::Stream void resetStream(Http::StreamResetReason reason) override; - void readDisable(bool disable) override; // quic::QuicSpdyStream void OnBodyAvailable() override; void OnStreamReset(const quic::QuicRstStreamFrame& frame) override; + void OnCanWrite() override; // quic::QuicServerSessionBase void OnConnectionClosed(quic::QuicErrorCode error, quic::ConnectionCloseSource source) override; protected: + // EnvoyQuicStream + void switchStreamBlockState(bool should_block) override; + uint32_t streamId() override; + Network::Connection* connection() override; + // quic::QuicSpdyStream void OnInitialHeadersComplete(bool fin, size_t frame_len, const quic::QuicHeaderList& header_list) override; diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_simulated_watermark_buffer.h b/source/extensions/quic_listeners/quiche/envoy_quic_simulated_watermark_buffer.h new file mode 100644 index 0000000000000..0602bd39477f5 --- /dev/null +++ b/source/extensions/quic_listeners/quiche/envoy_quic_simulated_watermark_buffer.h @@ -0,0 +1,62 @@ +#pragma once + +#include + +#include "spdlog/spdlog.h" + +namespace Envoy { +namespace Quic { + +// A class, together with a stand alone buffer, used to achieve the purpose of WatermarkBuffer. +// Itself doesn't have buffer or do bookeeping of buffered bytes. But provided with buffered_bytes, +// it re-acts upon crossing high/low watermarks. +class EnvoyQuicSimulatedWatermarkBuffer { +public: + EnvoyQuicSimulatedWatermarkBuffer(uint32_t low_watermark, uint32_t high_watermark, + std::function below_low_watermark, + std::function above_high_watermark, + spdlog::logger& logger) + : low_watermark_(low_watermark), high_watermark_(high_watermark), + below_low_watermark_(std::move(below_low_watermark)), + above_high_watermark_(std::move(above_high_watermark)), logger_(logger) { + ASSERT((high_watermark == 0 && low_watermark == 0) || (high_watermark_ > low_watermark_)); + } + + void checkHighWatermark(uint32_t bytes_buffered) { + if (high_watermark_ > 0 && !is_above_high_watermark_ && bytes_buffered > high_watermark_) { + // Just exceeds high watermark. + ENVOY_LOG_TO_LOGGER(logger_, debug, "Buffered {} bytes, cross high watermark {}", + bytes_buffered, high_watermark_); + is_above_high_watermark_ = true; + is_below_low_watermark_ = false; + above_high_watermark_(); + } + } + + void checkLowWatermark(uint32_t bytes_buffered) { + if (low_watermark_ > 0 && !is_below_low_watermark_ && bytes_buffered < low_watermark_) { + // Just cross low watermark. + ENVOY_LOG_TO_LOGGER(logger_, debug, "Buffered {} bytes, cross low watermark {}", + bytes_buffered, low_watermark_); + is_below_low_watermark_ = true; + is_above_high_watermark_ = false; + below_low_watermark_(); + } + } + + bool isAboveHighWatermark() const { return is_above_high_watermark_; } + + bool isBelowLowWatermark() const { return is_below_low_watermark_; } + +private: + uint32_t low_watermark_{0}; + bool is_below_low_watermark_{true}; + uint32_t high_watermark_{0}; + bool is_above_high_watermark_{false}; + std::function below_low_watermark_; + std::function above_high_watermark_; + spdlog::logger& logger_; +}; + +} // namespace Quic +} // namespace Envoy diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_stream.h b/source/extensions/quic_listeners/quiche/envoy_quic_stream.h index a6126224cc688..0ff2768386251 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_stream.h +++ b/source/extensions/quic_listeners/quiche/envoy_quic_stream.h @@ -1,41 +1,92 @@ #pragma once +#include "envoy/event/dispatcher.h" #include "envoy/http/codec.h" #include "common/http/codec_helper.h" +#include "extensions/quic_listeners/quiche/envoy_quic_simulated_watermark_buffer.h" + namespace Envoy { namespace Quic { // Base class for EnvoyQuicServer|ClientStream. class EnvoyQuicStream : public Http::StreamEncoder, public Http::Stream, - public Http::StreamCallbackHelper { + public Http::StreamCallbackHelper, + protected Logger::Loggable { public: + EnvoyQuicStream(uint32_t buffer_limit, std::function below_low_watermark, + std::function above_high_watermark) + : send_buffer_simulation_(buffer_limit / 2, buffer_limit, std::move(below_low_watermark), + std::move(above_high_watermark), ENVOY_LOGGER()) {} + // Http::StreamEncoder Stream& getStream() override { return *this; } // Http::Stream + void readDisable(bool disable) override { + bool status_changed{false}; + if (disable) { + ++read_disable_counter_; + ASSERT(read_disable_counter_ == 1); + if (read_disable_counter_ == 1) { + status_changed = true; + } + } else { + ASSERT(read_disable_counter_ > 0); + --read_disable_counter_; + if (read_disable_counter_ == 0) { + status_changed = true; + } + } + + if (status_changed && !in_encode_data_callstack_) { + switchStreamBlockState(disable); + } + } + void addCallbacks(Http::StreamCallbacks& callbacks) override { ASSERT(!local_end_stream_); addCallbacks_(callbacks); } void removeCallbacks(Http::StreamCallbacks& callbacks) override { removeCallbacks_(callbacks); } - uint32_t bufferLimit() override { NOT_IMPLEMENTED_GCOVR_EXCL_LINE; } + uint32_t bufferLimit() override { return quic::kStreamReceiveWindowLimit; } // Needs to be called during quic stream creation before the stream receives // any headers and data. void setDecoder(Http::StreamDecoder& decoder) { decoder_ = &decoder; } protected: + virtual void switchStreamBlockState(bool should_block) PURE; + + // Needed for ENVOY_STREAM_LOG. + virtual uint32_t streamId() PURE; + virtual Network::Connection* connection() PURE; + Http::StreamDecoder* decoder() { ASSERT(decoder_ != nullptr); return decoder_; } + EnvoyQuicSimulatedWatermarkBuffer& sendBufferSimulation() { return send_buffer_simulation_; } + // True once end of stream is propergated to Envoy. Envoy doesn't expect to be + // notified more than once about end of stream. So once this is true, no need + // to set it in the callback to Envoy stream any more. + bool end_stream_decoded_{false}; + int32_t read_disable_counter_{0}; + // If true, switchStreamBlockState() should be deferred till this variable + // becomes false. + bool in_encode_data_callstack_{false}; + private: // Not owned. Http::StreamDecoder* decoder_{nullptr}; + // Keeps the buffer state of the connection, and react upon the changes of how many bytes are + // buffered cross all streams' send buffer. The state is evaluated and may be changed upon each + // stream write. QUICHE doesn't buffer data in connection, all the data is buffered in stream's + // send buffer. + EnvoyQuicSimulatedWatermarkBuffer send_buffer_simulation_; }; } // namespace Quic diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_utils.cc b/source/extensions/quic_listeners/quiche/envoy_quic_utils.cc index 33e0c43fc035b..6be73faef9546 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_utils.cc +++ b/source/extensions/quic_listeners/quiche/envoy_quic_utils.cc @@ -62,5 +62,50 @@ Http::HeaderMapImplPtr spdyHeaderBlockToEnvoyHeaders(const spdy::SpdyHeaderBlock return headers; } +spdy::SpdyHeaderBlock envoyHeadersToSpdyHeaderBlock(const Http::HeaderMap& headers) { + spdy::SpdyHeaderBlock header_block; + headers.iterate( + [](const Http::HeaderEntry& header, void* context) -> Http::HeaderMap::Iterate { + auto spdy_headers = static_cast(context); + // The key-value pairs are copied. + spdy_headers->insert({header.key().getStringView(), header.value().getStringView()}); + return Http::HeaderMap::Iterate::Continue; + }, + &header_block); + return header_block; +} + +quic::QuicRstStreamErrorCode envoyResetReasonToQuicRstError(Http::StreamResetReason reason) { + switch (reason) { + case Http::StreamResetReason::LocalRefusedStreamReset: + return quic::QUIC_REFUSED_STREAM; + case Http::StreamResetReason::ConnectionFailure: + return quic::QUIC_STREAM_CONNECTION_ERROR; + case Http::StreamResetReason::LocalReset: + return quic::QUIC_STREAM_CANCELLED; + case Http::StreamResetReason::ConnectionTermination: + return quic::QUIC_STREAM_NO_ERROR; + default: + return quic::QUIC_BAD_APPLICATION_PAYLOAD; + } +} + +Http::StreamResetReason quicRstErrorToEnvoyResetReason(quic::QuicRstStreamErrorCode rst_err) { + switch (rst_err) { + case quic::QUIC_REFUSED_STREAM: + return Http::StreamResetReason::RemoteRefusedStreamReset; + default: + return Http::StreamResetReason::RemoteReset; + } +} + +Http::StreamResetReason quicErrorCodeToEnvoyResetReason(quic::QuicErrorCode error) { + if (error == quic::QUIC_NO_ERROR) { + return Http::StreamResetReason::ConnectionTermination; + } else { + return Http::StreamResetReason::ConnectionFailure; + } +} + } // namespace Quic } // namespace Envoy diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_utils.h b/source/extensions/quic_listeners/quiche/envoy_quic_utils.h index 54b1bf07f6036..6505b22a66e28 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_utils.h +++ b/source/extensions/quic_listeners/quiche/envoy_quic_utils.h @@ -25,5 +25,16 @@ Http::HeaderMapImplPtr quicHeadersToEnvoyHeaders(const quic::QuicHeaderList& hea Http::HeaderMapImplPtr spdyHeaderBlockToEnvoyHeaders(const spdy::SpdyHeaderBlock& header_block); +spdy::SpdyHeaderBlock envoyHeadersToSpdyHeaderBlock(const Http::HeaderMap& headers); + +// Called when Envoy wants to reset the underlying QUIC stream. +quic::QuicRstStreamErrorCode envoyResetReasonToQuicRstError(Http::StreamResetReason reason); + +// Called when a RST_STREAM frame is received. +Http::StreamResetReason quicRstErrorToEnvoyResetReason(quic::QuicRstStreamErrorCode rst_err); + +// Called when underlying QUIC connection is closed either locally or by peer. +Http::StreamResetReason quicErrorCodeToEnvoyResetReason(quic::QuicErrorCode error); + } // namespace Quic } // namespace Envoy diff --git a/source/extensions/quic_listeners/quiche/quic_filter_manager_connection_impl.cc b/source/extensions/quic_listeners/quiche/quic_filter_manager_connection_impl.cc index edfe62051d3cf..4baaed3a2c8dd 100644 --- a/source/extensions/quic_listeners/quiche/quic_filter_manager_connection_impl.cc +++ b/source/extensions/quic_listeners/quiche/quic_filter_manager_connection_impl.cc @@ -5,14 +5,17 @@ namespace Envoy { namespace Quic { -QuicFilterManagerConnectionImpl::QuicFilterManagerConnectionImpl( - std::unique_ptr connection, Event::Dispatcher& dispatcher) - : quic_connection_(std::move(connection)), filter_manager_(*this), dispatcher_(dispatcher), +QuicFilterManagerConnectionImpl::QuicFilterManagerConnectionImpl(EnvoyQuicConnection& connection, + Event::Dispatcher& dispatcher, + uint32_t send_buffer_limit) + : quic_connection_(&connection), dispatcher_(dispatcher), filter_manager_(*this), // QUIC connection id can be 18 bytes. It's easier to use hash value instead // of trying to map it into a 64-bit space. - stream_info_(dispatcher.timeSource()), id_(quic_connection_->connection_id().Hash()) { - // TODO(danzh): Use QUIC specific enum value. - stream_info_.protocol(Http::Protocol::Http2); + stream_info_(dispatcher.timeSource()), id_(quic_connection_->connection_id().Hash()), + write_buffer_watermark_simulation_( + send_buffer_limit / 2, send_buffer_limit, [this]() { onSendBufferLowWatermark(); }, + [this]() { onSendBufferHighWatermark(); }, ENVOY_LOGGER()) { + stream_info_.protocol(Http::Protocol::Http3); } void QuicFilterManagerConnectionImpl::addWriteFilter(Network::WriteFilterSharedPtr filter) { @@ -42,16 +45,26 @@ void QuicFilterManagerConnectionImpl::enableHalfClose(bool enabled) { void QuicFilterManagerConnectionImpl::setBufferLimits(uint32_t /*limit*/) { // TODO(danzh): add interface to quic for connection level buffer throttling. // Currently read buffer is capped by connection level flow control. And - // write buffer is not capped. + // write buffer limit is set during construction. Change buffer limit during + // the life time of connection is not supported. NOT_REACHED_GCOVR_EXCL_LINE; } +bool QuicFilterManagerConnectionImpl::aboveHighWatermark() const { + return write_buffer_watermark_simulation_.isAboveHighWatermark(); +} + void QuicFilterManagerConnectionImpl::close(Network::ConnectionCloseType type) { if (type != Network::ConnectionCloseType::NoFlush) { // TODO(danzh): Implement FlushWrite and FlushWriteAndDelay mode. } + if (quic_connection_ == nullptr) { + // Already detached from quic connection. + return; + } quic_connection_->CloseConnection(quic::QUIC_NO_ERROR, "Closed by application", quic::ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET); + quic_connection_ = nullptr; } void QuicFilterManagerConnectionImpl::setDelayedCloseTimeout(std::chrono::milliseconds timeout) { @@ -96,14 +109,22 @@ void QuicFilterManagerConnectionImpl::rawWrite(Buffer::Instance& /*data*/, bool NOT_REACHED_GCOVR_EXCL_LINE; } +void QuicFilterManagerConnectionImpl::adjustBytesToSend(int64_t delta) { + bytes_to_send_ += delta; + write_buffer_watermark_simulation_.checkHighWatermark(bytes_to_send_); + write_buffer_watermark_simulation_.checkLowWatermark(bytes_to_send_); +} + void QuicFilterManagerConnectionImpl::onConnectionCloseEvent( const quic::QuicConnectionCloseFrame& frame, quic::ConnectionCloseSource source) { - // Tell network callbacks about connection close. - raiseEvent(source == quic::ConnectionCloseSource::FROM_PEER - ? Network::ConnectionEvent::RemoteClose - : Network::ConnectionEvent::LocalClose); transport_failure_reason_ = absl::StrCat(quic::QuicErrorCodeToString(frame.quic_error_code), " with details: ", frame.error_details); + if (quic_connection_ != nullptr) { + // Tell network callbacks about connection close if not detached yet. + raiseEvent(source == quic::ConnectionCloseSource::FROM_PEER + ? Network::ConnectionEvent::RemoteClose + : Network::ConnectionEvent::LocalClose); + } } void QuicFilterManagerConnectionImpl::raiseEvent(Network::ConnectionEvent event) { @@ -112,5 +133,19 @@ void QuicFilterManagerConnectionImpl::raiseEvent(Network::ConnectionEvent event) } } +void QuicFilterManagerConnectionImpl::onSendBufferHighWatermark() { + ENVOY_CONN_LOG(trace, "onSendBufferHighWatermark", *this); + for (auto callback : network_connection_callbacks_) { + callback->onAboveWriteBufferHighWatermark(); + } +} + +void QuicFilterManagerConnectionImpl::onSendBufferLowWatermark() { + ENVOY_CONN_LOG(trace, "onSendBufferLowWatermark", *this); + for (auto callback : network_connection_callbacks_) { + callback->onBelowWriteBufferLowWatermark(); + } +} + } // namespace Quic } // namespace Envoy diff --git a/source/extensions/quic_listeners/quiche/quic_filter_manager_connection_impl.h b/source/extensions/quic_listeners/quiche/quic_filter_manager_connection_impl.h index fc8f57df6a039..a72364d334e53 100644 --- a/source/extensions/quic_listeners/quiche/quic_filter_manager_connection_impl.h +++ b/source/extensions/quic_listeners/quiche/quic_filter_manager_connection_impl.h @@ -9,6 +9,7 @@ #include "common/stream_info/stream_info_impl.h" #include "extensions/quic_listeners/quiche/envoy_quic_connection.h" +#include "extensions/quic_listeners/quiche/envoy_quic_simulated_watermark_buffer.h" namespace Envoy { namespace Quic { @@ -17,8 +18,8 @@ namespace Quic { class QuicFilterManagerConnectionImpl : public Network::FilterManagerConnection, protected Logger::Loggable { public: - QuicFilterManagerConnectionImpl(std::unique_ptr connection, - Event::Dispatcher& dispatcher); + QuicFilterManagerConnectionImpl(EnvoyQuicConnection& connection, Event::Dispatcher& dispatcher, + uint32_t send_buffer_limit); // Network::FilterManager // Overridden to delegate calls to filter_manager_. @@ -60,8 +61,10 @@ class QuicFilterManagerConnectionImpl : public Network::FilterManagerConnection, } Ssl::ConnectionInfoConstSharedPtr ssl() const override; Network::Connection::State state() const override { - return quic_connection_->connected() ? Network::Connection::State::Open - : Network::Connection::State::Closed; + if (quic_connection_ != nullptr && quic_connection_->connected()) { + return Network::Connection::State::Open; + } + return Network::Connection::State::Closed; } void write(Buffer::Instance& /*data*/, bool /*end_stream*/) override { // All writes should be handled by Quic internally. @@ -76,11 +79,8 @@ class QuicFilterManagerConnectionImpl : public Network::FilterManagerConnection, // SO_ORIGINAL_DST not supported by QUIC. NOT_REACHED_GCOVR_EXCL_LINE; } - bool aboveHighWatermark() const override { - // TODO(danzh) Aggregate the write buffer usage cross all the streams and - // add an upper limit for this connection. - NOT_IMPLEMENTED_GCOVR_EXCL_LINE; - } + bool aboveHighWatermark() const override; + const Network::ConnectionSocket::OptionsSharedPtr& socketOptions() const override; StreamInfo::StreamInfo& streamInfo() override { return stream_info_; } const StreamInfo::StreamInfo& streamInfo() const override { return stream_info_; } @@ -97,6 +97,8 @@ class QuicFilterManagerConnectionImpl : public Network::FilterManagerConnection, // Network::WriteBufferSource Network::StreamBuffer getWriteBuffer() override { NOT_REACHED_GCOVR_EXCL_LINE; } + void adjustBytesToSend(int64_t delta); + protected: // Propagate connection close to network_connection_callbacks_. void onConnectionCloseEvent(const quic::QuicConnectionCloseFrame& frame, @@ -104,23 +106,31 @@ class QuicFilterManagerConnectionImpl : public Network::FilterManagerConnection, void raiseEvent(Network::ConnectionEvent event); - std::unique_ptr quic_connection_; + EnvoyQuicConnection* quic_connection_{nullptr}; // TODO(danzh): populate stats. std::unique_ptr stats_; + Event::Dispatcher& dispatcher_; private: + // Called when aggregated buffered bytes across all the streams exceeds high watermark. + void onSendBufferHighWatermark(); + // Called when aggregated buffered bytes across all the streams declines to low watermark. + void onSendBufferLowWatermark(); + // Currently ConnectionManagerImpl is the one and only filter. If more network // filters are added, ConnectionManagerImpl should always be the last one. // Its onRead() is only called once to trigger ReadFilter::onNewConnection() // and the rest incoming data bypasses these filters. Network::FilterManagerImpl filter_manager_; - Event::Dispatcher& dispatcher_; + StreamInfo::StreamInfoImpl stream_info_; // These callbacks are owned by network filters and quic session should out live // them. std::list network_connection_callbacks_; std::string transport_failure_reason_; const uint64_t id_; + uint32_t bytes_to_send_{0}; + EnvoyQuicSimulatedWatermarkBuffer write_buffer_watermark_simulation_; }; } // namespace Quic diff --git a/test/config/utility.cc b/test/config/utility.cc index 2cd563e33e62b..d98be4ec59524 100644 --- a/test/config/utility.cc +++ b/test/config/utility.cc @@ -113,6 +113,37 @@ const std::string ConfigHelper::HTTP_PROXY_CONFIG = BASE_CONFIG + R"EOF( name: route_config_0 )EOF"; +const std::string ConfigHelper::QUIC_HTTP_PROXY_CONFIG = BASE_UDP_LISTENER_CONFIG + R"EOF( + filter_chains: + transport_socket: + name: quic + filters: + name: envoy.http_connection_manager + config: + stat_prefix: config_test + http_filters: + name: envoy.router + codec_type: HTTP3 + access_log: + name: envoy.file_access_log + filter: + not_health_check_filter: {} + config: + path: /dev/null + route_config: + virtual_hosts: + name: integration + routes: + route: + cluster: cluster_0 + match: + prefix: "/" + domains: "*" + name: route_config_0 + udp_listener_config: + udp_listener_name: "quiche_quic_listener" +)EOF"; + const std::string ConfigHelper::DEFAULT_BUFFER_FILTER = R"EOF( name: envoy.buffer diff --git a/test/config/utility.h b/test/config/utility.h index aa03d8583a6f3..ada7f71906f51 100644 --- a/test/config/utility.h +++ b/test/config/utility.h @@ -78,7 +78,8 @@ class ConfigHelper { static const std::string TCP_PROXY_CONFIG; // A basic configuration for L7 proxying. static const std::string HTTP_PROXY_CONFIG; - + // A basic configuration for L7 proxying with QUIC transport. + static const std::string QUIC_HTTP_PROXY_CONFIG; // A string for a basic buffer filter, which can be used with addFilter() static const std::string DEFAULT_BUFFER_FILTER; // A string for a small buffer filter, which can be used with addFilter() diff --git a/test/extensions/quic_listeners/quiche/BUILD b/test/extensions/quic_listeners/quiche/BUILD index 74659b5829587..1af4834e518ec 100644 --- a/test/extensions/quic_listeners/quiche/BUILD +++ b/test/extensions/quic_listeners/quiche/BUILD @@ -62,7 +62,8 @@ envoy_cc_test( "//source/extensions/quic_listeners/quiche:envoy_quic_alarm_factory_lib", "//source/extensions/quic_listeners/quiche:envoy_quic_connection_helper_lib", "//source/extensions/quic_listeners/quiche:envoy_quic_server_connection_lib", - "//source/extensions/quic_listeners/quiche:envoy_quic_server_stream_lib", + "//source/extensions/quic_listeners/quiche:envoy_quic_server_session_lib", + "//test/mocks/http:http_mocks", "//test/mocks/http:stream_decoder_mock", "//test/mocks/network:network_mocks", "//test/test_common:utility_lib", diff --git a/test/extensions/quic_listeners/quiche/envoy_quic_dispatcher_test.cc b/test/extensions/quic_listeners/quiche/envoy_quic_dispatcher_test.cc index 2d1c44a4ab4f7..d8d0146f0e1ff 100644 --- a/test/extensions/quic_listeners/quiche/envoy_quic_dispatcher_test.cc +++ b/test/extensions/quic_listeners/quiche/envoy_quic_dispatcher_test.cc @@ -84,6 +84,8 @@ class EnvoyQuicDispatcherTest : public testing::TestWithParam read_filter(new Network::MockReadFilter()); Network::MockConnectionCallbacks network_connection_callbacks; + testing::StrictMock read_total; + testing::StrictMock read_current; + testing::StrictMock write_total; + testing::StrictMock write_current; + std::vector filter_factory( {[&](Network::FilterManager& filter_manager) { filter_manager.addReadFilter(read_filter); read_filter->callbacks_->connection().addConnectionCallbacks(network_connection_callbacks); + read_filter->callbacks_->connection().setConnectionStats( + {read_total, read_current, write_total, write_current, nullptr, nullptr}); }}); EXPECT_CALL(filter_chain, networkFilterFactories()).WillOnce(ReturnRef(filter_factory)); EXPECT_CALL(listener_config_, filterChainFactory()); diff --git a/test/extensions/quic_listeners/quiche/envoy_quic_proof_source_test.cc b/test/extensions/quic_listeners/quiche/envoy_quic_proof_source_test.cc index 4737a532f558b..57bdf94e9e1f0 100644 --- a/test/extensions/quic_listeners/quiche/envoy_quic_proof_source_test.cc +++ b/test/extensions/quic_listeners/quiche/envoy_quic_proof_source_test.cc @@ -62,8 +62,8 @@ TEST_F(EnvoyQuicFakeProofSourceTest, TestGetProof) { TEST_F(EnvoyQuicFakeProofSourceTest, TestVerifyProof) { EXPECT_EQ(quic::QUIC_SUCCESS, proof_verifier_.VerifyProof(hostname_, /*port=*/0, server_config_, version_, chlo_hash_, - expected_certs_, "Fake timestamp", expected_signature_, - nullptr, nullptr, nullptr, nullptr)); + expected_certs_, "", expected_signature_, nullptr, nullptr, + nullptr, nullptr)); std::vector wrong_certs{"wrong cert"}; EXPECT_EQ(quic::QUIC_FAILURE, proof_verifier_.VerifyProof(hostname_, /*port=*/0, server_config_, version_, chlo_hash_, diff --git a/test/extensions/quic_listeners/quiche/envoy_quic_server_session_test.cc b/test/extensions/quic_listeners/quiche/envoy_quic_server_session_test.cc index 013c98851be4d..419953ca523f8 100644 --- a/test/extensions/quic_listeners/quiche/envoy_quic_server_session_test.cc +++ b/test/extensions/quic_listeners/quiche/envoy_quic_server_session_test.cc @@ -87,7 +87,8 @@ class EnvoyQuicServerSessionTest : public testing::TestWithParam { envoy_quic_session_(quic_config_, quic_version_, std::unique_ptr(quic_connection_), /*visitor=*/nullptr, &crypto_stream_helper_, &crypto_config_, - &compressed_certs_cache_, *dispatcher_), + &compressed_certs_cache_, *dispatcher_, + /*send_buffer_limit*/ 1024 * 1024), read_filter_(new Network::MockReadFilter()) { EXPECT_EQ(time_system_.systemTime(), envoy_quic_session_.streamInfo().startTime()); EXPECT_EQ(EMPTY_STRING, envoy_quic_session_.nextProtocol()); @@ -102,7 +103,7 @@ class EnvoyQuicServerSessionTest : public testing::TestWithParam { bool installReadFilter() { // Setup read filter. envoy_quic_session_.addReadFilter(read_filter_); - EXPECT_EQ(Http::Protocol::Http2, + EXPECT_EQ(Http::Protocol::Http3, read_filter_->callbacks_->connection().streamInfo().protocol().value()); EXPECT_EQ(envoy_quic_session_.id(), read_filter_->callbacks_->connection().id()); EXPECT_EQ(&envoy_quic_session_, &read_filter_->callbacks_->connection()); @@ -114,7 +115,7 @@ class EnvoyQuicServerSessionTest : public testing::TestWithParam { // Create ServerConnection instance and setup callbacks for it. http_connection_ = std::make_unique(envoy_quic_session_, http_connection_callbacks_); - EXPECT_EQ(Http::Protocol::Http2, http_connection_->protocol()); + EXPECT_EQ(Http::Protocol::Http3, http_connection_->protocol()); // Stop iteration to avoid calling getRead/WriteBuffer(). return Network::FilterStatus::StopIteration; })); @@ -314,7 +315,6 @@ TEST_P(EnvoyQuicServerSessionTest, ShutdownNotice) { // Not verifying dummy implementation, just to have coverage. EXPECT_DEATH(envoy_quic_session_.enableHalfClose(true), ""); EXPECT_EQ(nullptr, envoy_quic_session_.ssl()); - EXPECT_DEATH(envoy_quic_session_.aboveHighWatermark(), ""); EXPECT_DEATH(envoy_quic_session_.setDelayedCloseTimeout(std::chrono::milliseconds(1)), ""); http_connection_->shutdownNotice(); } @@ -362,6 +362,8 @@ TEST_P(EnvoyQuicServerSessionTest, InitializeFilterChain) { Network::FilterManager& filter_manager) { filter_manager.addReadFilter(read_filter_); read_filter_->callbacks_->connection().addConnectionCallbacks(network_connection_callbacks_); + read_filter_->callbacks_->connection().setConnectionStats( + {read_total_, read_current_, write_total_, write_current_, nullptr, nullptr}); }}; EXPECT_CALL(filter_chain, networkFilterFactories()).WillOnce(ReturnRef(filter_factory)); EXPECT_CALL(*read_filter_, onNewConnection()) diff --git a/test/extensions/quic_listeners/quiche/envoy_quic_server_stream_test.cc b/test/extensions/quic_listeners/quiche/envoy_quic_server_stream_test.cc index b77714985cc17..290096b9c8408 100644 --- a/test/extensions/quic_listeners/quiche/envoy_quic_server_stream_test.cc +++ b/test/extensions/quic_listeners/quiche/envoy_quic_server_stream_test.cc @@ -9,6 +9,7 @@ #include "quiche/quic/core/quic_versions.h" #include "quiche/quic/core/http/quic_server_session_base.h" #include "quiche/quic/test_tools/quic_test_utils.h" +#include "quiche/quic/core/quic_utils.h" #pragma GCC diagnostic pop @@ -18,29 +19,36 @@ #include "common/http/headers.h" #include "test/test_common/utility.h" #include "extensions/quic_listeners/quiche/envoy_quic_alarm_factory.h" +#include "extensions/quic_listeners/quiche/envoy_quic_server_session.h" #include "extensions/quic_listeners/quiche/envoy_quic_server_connection.h" #include "extensions/quic_listeners/quiche/envoy_quic_connection_helper.h" +#include "test/test_common/utility.h" #include "test/mocks/http/stream_decoder.h" +#include "test/mocks/http/mocks.h" #include "test/mocks/network/mocks.h" #include "gmock/gmock.h" #include "gtest/gtest.h" using testing::_; using testing::Invoke; +using testing::Return; namespace Envoy { namespace Quic { -class MockQuicServerSession : public quic::QuicServerSessionBase { +class MockQuicServerSession : public EnvoyQuicServerSession { public: MockQuicServerSession(const quic::QuicConfig& config, const quic::ParsedQuicVersionVector& supported_versions, - quic::QuicConnection* connection, quic::QuicSession::Visitor* visitor, + std::unique_ptr connection, + quic::QuicSession::Visitor* visitor, quic::QuicCryptoServerStream::Helper* helper, const quic::QuicCryptoServerConfig* crypto_config, - quic::QuicCompressedCertsCache* compressed_certs_cache) - : quic::QuicServerSessionBase(config, supported_versions, connection, visitor, helper, - crypto_config, compressed_certs_cache) {} + quic::QuicCompressedCertsCache* compressed_certs_cache, + Event::Dispatcher& dispatcher, uint32_t send_buffer_limit) + : EnvoyQuicServerSession(config, supported_versions, std::move(connection), visitor, helper, + crypto_config, compressed_certs_cache, dispatcher, + send_buffer_limit) {} MOCK_METHOD1(CreateIncomingStream, quic::QuicSpdyStream*(quic::QuicStreamId id)); MOCK_METHOD1(CreateIncomingStream, quic::QuicSpdyStream*(quic::PendingStream* pending)); @@ -49,9 +57,20 @@ class MockQuicServerSession : public quic::QuicServerSessionBase { MOCK_METHOD1(ShouldCreateIncomingStream, bool(quic::QuicStreamId id)); MOCK_METHOD0(ShouldCreateOutgoingBidirectionalStream, bool()); MOCK_METHOD0(ShouldCreateOutgoingUnidirectionalStream, bool()); - MOCK_METHOD2(CreateQuicCryptoServerStream, - quic::QuicCryptoServerStream*(const quic::QuicCryptoServerConfig*, - quic::QuicCompressedCertsCache*)); + MOCK_METHOD1(ShouldYield, bool(quic::QuicStreamId stream_id)); + MOCK_METHOD5(WritevData, + quic::QuicConsumedData(quic::QuicStream* stream, quic::QuicStreamId id, + size_t write_length, quic::QuicStreamOffset offset, + quic::StreamSendingState state)); + + quic::QuicCryptoServerStream* + CreateQuicCryptoServerStream(const quic::QuicCryptoServerConfig* crypto_config, + quic::QuicCompressedCertsCache* compressed_certs_cache) override { + return new quic::QuicCryptoServerStream(crypto_config, compressed_certs_cache, this, + stream_helper()); + } + + using quic::QuicServerSessionBase::ActivateStream; }; class EnvoyQuicServerStreamTest : public testing::TestWithParam { @@ -66,26 +85,45 @@ class EnvoyQuicServerStreamTest : public testing::TestWithParam { listener_stats_({ALL_LISTENER_STATS(POOL_COUNTER(listener_config_.listenerScope()), POOL_GAUGE(listener_config_.listenerScope()), POOL_HISTOGRAM(listener_config_.listenerScope()))}), - quic_connection_(quic::test::TestConnectionId(), - quic::QuicSocketAddress(quic::QuicIpAddress::Any6(), 12345), - connection_helper_, alarm_factory_, &writer_, - /*owns_writer=*/false, {quic_version_}, listener_config_, listener_stats_), - quic_session_(quic_config_, {quic_version_}, &quic_connection_, /*visitor=*/nullptr, + quic_connection_(new EnvoyQuicServerConnection( + quic::test::TestConnectionId(), + quic::QuicSocketAddress(quic::QuicIpAddress::Any6(), 12345), connection_helper_, + alarm_factory_, &writer_, + /*owns_writer=*/false, {quic_version_}, listener_config_, listener_stats_)), + quic_session_(quic_config_, {quic_version_}, + std::unique_ptr(quic_connection_), + /*visitor=*/nullptr, /*helper=*/nullptr, /*crypto_config=*/nullptr, - /*compressed_certs_cache=*/nullptr), + /*compressed_certs_cache=*/nullptr, *dispatcher_, + quic_config_.GetInitialStreamFlowControlWindowToSend()), stream_id_(quic_version_.transport_version == quic::QUIC_VERSION_99 ? 4u : 5u), - quic_stream_(stream_id_, &quic_session_, quic::BIDIRECTIONAL) { + quic_stream_(new EnvoyQuicServerStream(stream_id_, &quic_session_, quic::BIDIRECTIONAL)), + response_headers_{{":status", "200"}} { quic::SetVerbosityLogThreshold(3); - - quic_stream_.setDecoder(stream_decoder_); + quic_stream_->setDecoder(stream_decoder_); + quic_stream_->addCallbacks(stream_callbacks_); + quic_session_.ActivateStream(std::unique_ptr(quic_stream_)); + EXPECT_CALL(quic_session_, ShouldYield(_)).WillRepeatedly(Return(false)); + EXPECT_CALL(quic_session_, WritevData(_, _, _, _, _)) + .WillRepeatedly(Invoke([](quic::QuicStream*, quic::QuicStreamId, size_t write_length, + quic::QuicStreamOffset, quic::StreamSendingState) { + return quic::QuicConsumedData{write_length, true}; + })); + EXPECT_CALL(writer_, WritePacket(_, _, _, _, _)) + .WillRepeatedly(Invoke([](const char*, size_t buf_len, const quic::QuicIpAddress&, + const quic::QuicSocketAddress&, quic::PerPacketOptions*) { + return quic::WriteResult{quic::WRITE_STATUS_OK, static_cast(buf_len)}; + })); } void SetUp() override { - headers_.OnHeaderBlockStart(); - headers_.OnHeader(":authority", host_); - headers_.OnHeader(":method", "GET"); - headers_.OnHeader(":path", "/"); - headers_.OnHeaderBlockEnd(/*uncompressed_header_bytes=*/0, /*compressed_header_bytes=*/0); + quic_session_.Initialize(); + request_headers_.OnHeaderBlockStart(); + request_headers_.OnHeader(":authority", host_); + request_headers_.OnHeader(":method", "GET"); + request_headers_.OnHeader(":path", "/"); + request_headers_.OnHeaderBlockEnd(/*uncompressed_header_bytes=*/0, + /*compressed_header_bytes=*/0); trailers_.OnHeaderBlockStart(); trailers_.OnHeader("key1", "value1"); @@ -96,6 +134,12 @@ class EnvoyQuicServerStreamTest : public testing::TestWithParam { trailers_.OnHeaderBlockEnd(/*uncompressed_header_bytes=*/0, /*compressed_header_bytes=*/0); } + void TearDown() override { + if (quic_connection_->connected()) { + quic_session_.close(Network::ConnectionCloseType::NoFlush); + } + } + protected: Api::ApiPtr api_; Event::DispatcherPtr dispatcher_; @@ -106,12 +150,14 @@ class EnvoyQuicServerStreamTest : public testing::TestWithParam { quic::QuicConfig quic_config_; testing::NiceMock listener_config_; Server::ListenerStats listener_stats_; - EnvoyQuicServerConnection quic_connection_; + EnvoyQuicServerConnection* quic_connection_; MockQuicServerSession quic_session_; quic::QuicStreamId stream_id_; - EnvoyQuicServerStream quic_stream_; + EnvoyQuicServerStream* quic_stream_; Http::MockStreamDecoder stream_decoder_; - quic::QuicHeaderList headers_; + Http::MockStreamCallbacks stream_callbacks_; + quic::QuicHeaderList request_headers_; + Http::TestHeaderMapImpl response_headers_; quic::QuicHeaderList trailers_; std::string host_{"www.abc.com"}; std::string request_body_{"Hello world"}; @@ -120,7 +166,7 @@ class EnvoyQuicServerStreamTest : public testing::TestWithParam { INSTANTIATE_TEST_SUITE_P(EnvoyQuicServerStreamTests, EnvoyQuicServerStreamTest, testing::ValuesIn({true, false})); -TEST_P(EnvoyQuicServerStreamTest, DecodeHeadersAndBody) { +TEST_P(EnvoyQuicServerStreamTest, PostRequestAndResponse) { EXPECT_CALL(stream_decoder_, decodeHeaders_(_, /*end_stream=*/false)) .WillOnce(Invoke([this](const Http::HeaderMapPtr& headers, bool) { EXPECT_EQ(host_, headers->Host()->value().getStringView()); @@ -129,11 +175,12 @@ TEST_P(EnvoyQuicServerStreamTest, DecodeHeadersAndBody) { headers->Method()->value().getStringView()); })); if (quic_version_.transport_version == quic::QUIC_VERSION_99) { - quic_stream_.OnHeadersDecoded(headers_); + quic_stream_->OnHeadersDecoded(request_headers_); } else { - quic_stream_.OnStreamHeaderList(/*fin=*/false, headers_.uncompressed_header_bytes(), headers_); + quic_stream_->OnStreamHeaderList(/*fin=*/false, request_headers_.uncompressed_header_bytes(), + request_headers_); } - EXPECT_TRUE(quic_stream_.FinishedReadingHeaders()); + EXPECT_TRUE(quic_stream_->FinishedReadingHeaders()); EXPECT_CALL(stream_decoder_, decodeData(_, _)) .WillOnce(Invoke([this](Buffer::Instance& buffer, bool finished_reading) { @@ -150,7 +197,9 @@ TEST_P(EnvoyQuicServerStreamTest, DecodeHeadersAndBody) { data = absl::StrCat(data_frame_header, request_body_); } quic::QuicStreamFrame frame(stream_id_, true, 0, data); - quic_stream_.OnStreamFrame(frame); + quic_stream_->OnStreamFrame(frame); + + quic_stream_->encodeHeaders(response_headers_, /*end_stream=*/true); } TEST_P(EnvoyQuicServerStreamTest, DecodeHeadersBodyAndTrailers) { @@ -161,8 +210,9 @@ TEST_P(EnvoyQuicServerStreamTest, DecodeHeadersBodyAndTrailers) { EXPECT_EQ(Http::Headers::get().MethodValues.Get, headers->Method()->value().getStringView()); })); - quic_stream_.OnStreamHeaderList(/*fin=*/false, headers_.uncompressed_header_bytes(), headers_); - EXPECT_TRUE(quic_stream_.FinishedReadingHeaders()); + quic_stream_->OnStreamHeaderList(/*fin=*/false, request_headers_.uncompressed_header_bytes(), + request_headers_); + EXPECT_TRUE(quic_stream_->FinishedReadingHeaders()); std::string data = request_body_; if (quic_version_.transport_version == quic::QUIC_VERSION_99) { @@ -186,7 +236,7 @@ TEST_P(EnvoyQuicServerStreamTest, DecodeHeadersBodyAndTrailers) { EXPECT_FALSE(finished_reading); EXPECT_EQ(0, buffer.length()); })); - quic_stream_.OnStreamFrame(frame); + quic_stream_->OnStreamFrame(frame); EXPECT_CALL(stream_decoder_, decodeTrailers_(_)) .WillOnce(Invoke([](const Http::HeaderMapPtr& headers) { @@ -195,10 +245,12 @@ TEST_P(EnvoyQuicServerStreamTest, DecodeHeadersBodyAndTrailers) { EXPECT_EQ("value1", headers->get(key1)->value().getStringView()); EXPECT_EQ(nullptr, headers->get(key2)); })); - quic_stream_.OnStreamHeaderList(/*fin=*/true, trailers_.uncompressed_header_bytes(), trailers_); + quic_stream_->OnStreamHeaderList(/*fin=*/true, trailers_.uncompressed_header_bytes(), trailers_); + EXPECT_CALL(stream_callbacks_, onResetStream(_, _)); } TEST_P(EnvoyQuicServerStreamTest, OutOfOrderTrailers) { + EXPECT_CALL(stream_callbacks_, onResetStream(_, _)); if (quic::VersionUsesQpack(quic_version_.transport_version)) { return; } @@ -209,11 +261,12 @@ TEST_P(EnvoyQuicServerStreamTest, OutOfOrderTrailers) { EXPECT_EQ(Http::Headers::get().MethodValues.Get, headers->Method()->value().getStringView()); })); - quic_stream_.OnStreamHeaderList(/*fin=*/false, headers_.uncompressed_header_bytes(), headers_); - EXPECT_TRUE(quic_stream_.FinishedReadingHeaders()); + quic_stream_->OnStreamHeaderList(/*fin=*/false, request_headers_.uncompressed_header_bytes(), + request_headers_); + EXPECT_TRUE(quic_stream_->FinishedReadingHeaders()); // Trailer should be delivered to HCM later after body arrives. - quic_stream_.OnStreamHeaderList(/*fin=*/true, trailers_.uncompressed_header_bytes(), trailers_); + quic_stream_->OnStreamHeaderList(/*fin=*/true, trailers_.uncompressed_header_bytes(), trailers_); std::string data = request_body_; if (quic_version_.transport_version == quic::QUIC_VERSION_99) { @@ -245,7 +298,91 @@ TEST_P(EnvoyQuicServerStreamTest, OutOfOrderTrailers) { EXPECT_EQ("value1", headers->get(key1)->value().getStringView()); EXPECT_EQ(nullptr, headers->get(key2)); })); - quic_stream_.OnStreamFrame(frame); + quic_stream_->OnStreamFrame(frame); +} + +TEST_P(EnvoyQuicServerStreamTest, WatermarkSendBuffer) { + EXPECT_CALL(stream_decoder_, decodeHeaders_(_, /*end_stream=*/false)) + .WillOnce(Invoke([this](const Http::HeaderMapPtr& headers, bool) { + EXPECT_EQ(host_, headers->Host()->value().getStringView()); + EXPECT_EQ("/", headers->Path()->value().getStringView()); + EXPECT_EQ(Http::Headers::get().MethodValues.Get, + headers->Method()->value().getStringView()); + })); + if (quic_version_.transport_version == quic::QUIC_VERSION_99) { + quic_stream_->OnHeadersDecoded(request_headers_); + } else { + quic_stream_->OnStreamHeaderList(/*fin=*/false, request_headers_.uncompressed_header_bytes(), + request_headers_); + } + EXPECT_TRUE(quic_stream_->FinishedReadingHeaders()); + + EXPECT_CALL(stream_decoder_, decodeData(_, _)) + .WillOnce(Invoke([this](Buffer::Instance& buffer, bool finished_reading) { + EXPECT_EQ(request_body_, buffer.toString()); + EXPECT_TRUE(finished_reading); + })); + std::string data = request_body_; + if (quic_version_.transport_version == quic::QUIC_VERSION_99) { + std::unique_ptr data_buffer; + quic::HttpEncoder encoder; + quic::QuicByteCount data_frame_header_length = + encoder.SerializeDataFrameHeader(request_body_.length(), &data_buffer); + quic::QuicStringPiece data_frame_header(data_buffer.get(), data_frame_header_length); + data = absl::StrCat(data_frame_header, request_body_); + } + quic::QuicStreamFrame frame(stream_id_, true, 0, data); + quic_stream_->OnStreamFrame(frame); + + response_headers_.addCopy(":content-length", "32770"); // 32KB + 2 byte + quic_stream_->encodeHeaders(response_headers_, /*end_stream=*/false); + // encode 32kB response body. first 16KB shoudl be written out right away. The + // rest should be buffered. The high watermark is 16KB, so this call should + // make the send buffer reach its high watermark. + std::string response(32 * 1024 + 1, 'a'); + Buffer::OwnedImpl buffer(response); + EXPECT_CALL(stream_callbacks_, onAboveWriteBufferHighWatermark()); + quic_stream_->encodeData(buffer, false); + + EXPECT_EQ(0u, buffer.length()); + EXPECT_TRUE(quic_stream_->flow_controller()->IsBlocked()); + // Bump connection flow control window large enough not to cause connection + // level flow control blocked. + quic::QuicWindowUpdateFrame window_update( + quic::kInvalidControlFrameId, + quic::QuicUtils::GetInvalidStreamId(quic_version_.transport_version), 1024 * 1024); + quic_session_.OnWindowUpdateFrame(window_update); + + // Receive a WINDOW_UPDATE frame not large enough to drain half of the send + // buffer. + quic::QuicWindowUpdateFrame window_update1(quic::kInvalidControlFrameId, quic_stream_->id(), + 16 * 1024 + 8 * 1024); + quic_stream_->OnWindowUpdateFrame(window_update1); + EXPECT_FALSE(quic_stream_->flow_controller()->IsBlocked()); + quic_session_.OnCanWrite(); + EXPECT_TRUE(quic_stream_->flow_controller()->IsBlocked()); + + // Receive another WINDOW_UPDATE frame to drain the send buffer till below low + // watermark. + quic::QuicWindowUpdateFrame window_update2(quic::kInvalidControlFrameId, quic_stream_->id(), + 16 * 1024 + 8 * 1024 + 1024); + quic_stream_->OnWindowUpdateFrame(window_update2); + EXPECT_FALSE(quic_stream_->flow_controller()->IsBlocked()); + EXPECT_CALL(stream_callbacks_, onBelowWriteBufferLowWatermark()).WillOnce(Invoke([this]() { + std::string rest_response(1, 'a'); + Buffer::OwnedImpl buffer(rest_response); + quic_stream_->encodeData(buffer, true); + })); + quic_session_.OnCanWrite(); + EXPECT_TRUE(quic_stream_->flow_controller()->IsBlocked()); + + quic::QuicWindowUpdateFrame window_update3(quic::kInvalidControlFrameId, quic_stream_->id(), + 32 * 1024 + 1024); + quic_stream_->OnWindowUpdateFrame(window_update3); + quic_session_.OnCanWrite(); + + EXPECT_TRUE(quic_stream_->local_end_stream_); + EXPECT_TRUE(quic_stream_->write_side_closed()); } } // namespace Quic diff --git a/test/extensions/quic_listeners/quiche/integration/BUILD b/test/extensions/quic_listeners/quiche/integration/BUILD new file mode 100644 index 0000000000000..4e2cde2d9bd8d --- /dev/null +++ b/test/extensions/quic_listeners/quiche/integration/BUILD @@ -0,0 +1,29 @@ +licenses(["notice"]) # Apache 2 + +load( + "//bazel:envoy_build_system.bzl", + "envoy_cc_fuzz_test", + "envoy_cc_test", + "envoy_cc_test_binary", + "envoy_cc_test_library", + "envoy_package", + "envoy_proto_library", +) + +envoy_package() + +envoy_cc_test( + name = "quic_http_integration_test", + srcs = ["quic_http_integration_test.cc"], + data = ["//test/config/integration/certs"], + tags = ["nofips"], + deps = [ + "//source/extensions/quic_listeners/quiche:active_quic_listener_config_lib", + "//source/extensions/quic_listeners/quiche:envoy_quic_client_connection_lib", + "//source/extensions/quic_listeners/quiche:envoy_quic_client_session_lib", + "//source/extensions/quic_listeners/quiche:envoy_quic_connection_helper_lib", + "//source/extensions/quic_listeners/quiche:envoy_quic_proof_verifier_lib", + "//source/extensions/quic_listeners/quiche:quic_transport_socket_factory_lib", + "//test/integration:http_integration_lib", + ], +) diff --git a/test/extensions/quic_listeners/quiche/integration/quic_http_integration_test.cc b/test/extensions/quic_listeners/quiche/integration/quic_http_integration_test.cc new file mode 100644 index 0000000000000..86d5f08b48f58 --- /dev/null +++ b/test/extensions/quic_listeners/quiche/integration/quic_http_integration_test.cc @@ -0,0 +1,185 @@ +#include "test/config/utility.h" +#include "test/integration/http_integration.h" +#include "test/test_common/utility.h" + +#pragma GCC diagnostic push +// QUICHE allows unused parameters. +#pragma GCC diagnostic ignored "-Wunused-parameter" +// QUICHE uses offsetof(). +#pragma GCC diagnostic ignored "-Winvalid-offsetof" + +#include "quiche/quic/core/http/quic_client_push_promise_index.h" +#include "quiche/quic/core/quic_utils.h" + +#pragma GCC diagnostic pop + +#include "extensions/quic_listeners/quiche/envoy_quic_client_session.h" +#include "extensions/quic_listeners/quiche/envoy_quic_client_connection.h" +#include "extensions/quic_listeners/quiche/envoy_quic_fake_proof_verifier.h" +#include "extensions/quic_listeners/quiche/envoy_quic_connection_helper.h" +#include "extensions/quic_listeners/quiche/envoy_quic_alarm_factory.h" +#include "extensions/quic_listeners/quiche/envoy_quic_packet_writer.h" + +namespace Envoy { +namespace Quic { + +class CodecClientCallbacksForTest : public Http::CodecClientCallbacks { +public: + void onStreamDestroy() override {} + + void onStreamReset(Http::StreamResetReason reason) override { + last_stream_reset_reason_ = reason; + } + + Http::StreamResetReason last_stream_reset_reason_{Http::StreamResetReason::LocalReset}; +}; + +class QuicHttpIntegrationTest : public testing::TestWithParam, + public HttpIntegrationTest { +public: + QuicHttpIntegrationTest() + : HttpIntegrationTest(Http::CodecClient::Type::HTTP3, GetParam(), + ConfigHelper::QUIC_HTTP_PROXY_CONFIG), + supported_versions_(quic::CurrentSupportedVersions()), + crypto_config_(std::make_unique()), conn_helper_(*dispatcher_), + alarm_factory_(*dispatcher_, *conn_helper_.GetClock()) { + quic::SetVerbosityLogThreshold(1); + } + + Network::ClientConnectionPtr makeClientConnection(uint32_t port) override { + Network::Address::InstanceConstSharedPtr server_addr = Network::Utility::resolveUrl( + fmt::format("udp://{}:{}", Network::Test::getLoopbackAddressUrlString(version_), port)); + Network::Address::InstanceConstSharedPtr local_addr = + Network::Test::getCanonicalLoopbackAddress(version_); + // Initiate a QUIC connection with the highest supported version. If not + // supported by server, this connection will fail. + // TODO(danzh) Implement retry upon version mismatch and modify test frame work to specify a + // different version set on server side to test that. + auto connection = std::make_unique( + getNextServerDesignatedConnectionId(), server_addr, conn_helper_, alarm_factory_, + quic::ParsedQuicVersionVector{supported_versions_[0]}, local_addr, *dispatcher_, nullptr); + auto session = std::make_unique( + quic_config_, supported_versions_, std::move(connection), server_id_, &crypto_config_, + &push_promise_index_, *dispatcher_, 0); + return session; + } + + // This call may fail because of INVALID_VERSION, because QUIC connection doesn't support + // in-connection version negotiation. + // TODO(#8479) Popagate INVALID_VERSION error to caller and let caller to use server advertised + // version list to create a new connection with mutually supported version and make client codec + // again. + IntegrationCodecClientPtr makeRawHttpConnection(Network::ClientConnectionPtr&& conn) override { + IntegrationCodecClientPtr codec = HttpIntegrationTest::makeRawHttpConnection(std::move(conn)); + ASSERT(!codec->connected()); + dynamic_cast(codec->connection())->cryptoConnect(); + if (codec->disconnected()) { + // Connection may get closed during version negotiation or handshake. + ENVOY_LOG(error, "Fail to connect to server with error: {}", + codec->connection()->transportFailureReason()); + } else { + codec->setCodecClientCallbacks(client_codec_callback_); + } + return codec; + } + + quic::QuicConnectionId getNextServerDesignatedConnectionId() { + quic::QuicCryptoClientConfig::CachedState* cached = crypto_config_.LookupOrCreate(server_id_); + // If the cached state indicates that we should use a server-designated + // connection ID, then return that connection ID. + quic::QuicConnectionId conn_id = cached->has_server_designated_connection_id() + ? cached->GetNextServerDesignatedConnectionId() + : quic::EmptyQuicConnectionId(); + return conn_id.IsEmpty() ? quic::QuicUtils::CreateRandomConnectionId() : conn_id; + } + + void initialize() override { + config_helper_.addConfigModifier([](envoy::config::bootstrap::v2::Bootstrap& bootstrap) { + envoy::api::v2::auth::DownstreamTlsContext tls_context; + ConfigHelper::initializeTls({}, *tls_context.mutable_common_tls_context()); + auto* filter_chain = + bootstrap.mutable_static_resources()->mutable_listeners(0)->mutable_filter_chains(0); + auto* transport_socket = filter_chain->mutable_transport_socket(); + TestUtility::jsonConvert(tls_context, *transport_socket->mutable_config()); + }); + config_helper_.addConfigModifier( + [](envoy::config::filter::network::http_connection_manager::v2::HttpConnectionManager& + hcm) { + hcm.mutable_delayed_close_timeout()->set_nanos(0); + EXPECT_EQ(hcm.codec_type(), envoy::config::filter::network::http_connection_manager::v2:: + HttpConnectionManager::HTTP3); + }); + + HttpIntegrationTest::initialize(); + registerTestServerPorts({"http"}); + } + +protected: + quic::QuicConfig quic_config_; + quic::QuicServerId server_id_; + quic::QuicClientPushPromiseIndex push_promise_index_; + quic::ParsedQuicVersionVector supported_versions_; + quic::QuicCryptoClientConfig crypto_config_; + EnvoyQuicConnectionHelper conn_helper_; + EnvoyQuicAlarmFactory alarm_factory_; + CodecClientCallbacksForTest client_codec_callback_; +}; + +INSTANTIATE_TEST_SUITE_P(IpVersions, QuicHttpIntegrationTest, + testing::ValuesIn(TestEnvironment::getIpVersionsForTest()), + TestUtility::ipTestParamsToString); + +TEST_P(QuicHttpIntegrationTest, GetRequestAndEmptyResponse) { + testRouterHeaderOnlyRequestAndResponse(); +} + +TEST_P(QuicHttpIntegrationTest, GetRequestAndResponseWithBody) { + initialize(); + sendRequestAndVerifyResponse(default_request_headers_, /*request_size=*/0, + default_response_headers_, /*response_size=*/1024, + /*backend_index*/ 0); +} + +TEST_P(QuicHttpIntegrationTest, PostRequestAndResponseWithBody) { + testRouterRequestAndResponseWithBody(1024, 512, false); +} + +TEST_P(QuicHttpIntegrationTest, PostRequestWithBigHeadersAndResponseWithBody) { + testRouterRequestAndResponseWithBody(1024, 512, true); +} + +TEST_P(QuicHttpIntegrationTest, RouterUpstreamDisconnectBeforeRequestcomplete) { + testRouterUpstreamDisconnectBeforeRequestComplete(); +} + +TEST_P(QuicHttpIntegrationTest, RouterUpstreamDisconnectBeforeResponseComplete) { + testRouterUpstreamDisconnectBeforeResponseComplete(); + EXPECT_EQ(Http::StreamResetReason::RemoteReset, client_codec_callback_.last_stream_reset_reason_); +} + +TEST_P(QuicHttpIntegrationTest, RouterDownstreamDisconnectBeforeRequestComplete) { + testRouterDownstreamDisconnectBeforeRequestComplete(); +} + +TEST_P(QuicHttpIntegrationTest, RouterDownstreamDisconnectBeforeResponseComplete) { + testRouterDownstreamDisconnectBeforeResponseComplete(); +} + +TEST_P(QuicHttpIntegrationTest, RouterUpstreamResponseBeforeRequestComplete) { + testRouterUpstreamResponseBeforeRequestComplete(); +} + +TEST_P(QuicHttpIntegrationTest, Retry) { testRetry(); } + +TEST_P(QuicHttpIntegrationTest, UpstreamThrottlingOnGiantResponseBody) { + config_helper_.setBufferLimits(/*upstream_buffer_limit=*/1024, /*downstream_buffer_limit=*/1024); + testRouterRequestAndResponseWithBody(/*request_size=*/512, /*response_size=*/1024 * 1024, false); +} + +TEST_P(QuicHttpIntegrationTest, DownstreamThrottlingOnGiantPost) { + config_helper_.setBufferLimits(/*upstream_buffer_limit=*/1024, /*downstream_buffer_limit=*/1024); + testRouterRequestAndResponseWithBody(/*request_size=*/1024 * 1024, /*response_size=*/1024, false); +} + +} // namespace Quic +} // namespace Envoy diff --git a/test/integration/http_integration.cc b/test/integration/http_integration.cc index be622d93f3b81..99ff9d5f91b5d 100644 --- a/test/integration/http_integration.cc +++ b/test/integration/http_integration.cc @@ -46,6 +46,9 @@ typeToCodecType(Http::CodecClient::Type type) { case Http::CodecClient::Type::HTTP2: return envoy::config::filter::network::http_connection_manager::v2::HttpConnectionManager:: HTTP2; + case Http::CodecClient::Type::HTTP3: + return envoy::config::filter::network::http_connection_manager::v2::HttpConnectionManager:: + HTTP3; default: RELEASE_ASSERT(0, ""); } @@ -60,7 +63,12 @@ IntegrationCodecClient::IntegrationCodecClient( callbacks_(*this), codec_callbacks_(*this) { connection_->addConnectionCallbacks(callbacks_); setCodecConnectionCallbacks(codec_callbacks_); - dispatcher.run(Event::Dispatcher::RunType::Block); + if (type != CodecClient::Type::HTTP3) { + // Only expect to have IO event if it's not QUIC. Because call to connect() in CodecClientProd + // for QUIC doesn't send anything to server, but just register file event. QUIC connection won't + // have any IO event till cryptoConnect() is called later. + dispatcher.run(Event::Dispatcher::RunType::Block); + } } void IntegrationCodecClient::flushWrite() { @@ -569,7 +577,9 @@ void HttpIntegrationTest::testRouterUpstreamResponseBeforeRequestComplete() { ASSERT_TRUE(fake_upstreams_[0]->waitForHttpConnection(*dispatcher_, fake_upstream_connection_)); ASSERT_TRUE(fake_upstream_connection_->waitForNewStream(*dispatcher_, upstream_request_)); ASSERT_TRUE(upstream_request_->waitForHeadersComplete()); + upstream_request_->encodeHeaders(default_response_headers_, false); + upstream_request_->encodeData(512, true); response->waitForEndStream(); diff --git a/test/integration/http_integration.h b/test/integration/http_integration.h index 1a2193556e654..35871f0e47073 100644 --- a/test/integration/http_integration.h +++ b/test/integration/http_integration.h @@ -108,7 +108,7 @@ class HttpIntegrationTest : public BaseIntegrationTest { IntegrationCodecClientPtr makeHttpConnection(uint32_t port); // Makes a http connection object without checking its connected state. - IntegrationCodecClientPtr makeRawHttpConnection(Network::ClientConnectionPtr&& conn); + virtual IntegrationCodecClientPtr makeRawHttpConnection(Network::ClientConnectionPtr&& conn); // Makes a http connection object with asserting a connected state. IntegrationCodecClientPtr makeHttpConnection(Network::ClientConnectionPtr&& conn); diff --git a/test/integration/integration.h b/test/integration/integration.h index 7cd2109cc8593..0e2b7d48c6fe1 100644 --- a/test/integration/integration.h +++ b/test/integration/integration.h @@ -138,7 +138,7 @@ struct ApiFilesystemConfig { /** * Test fixture for all integration tests. */ -class BaseIntegrationTest : Logger::Loggable { +class BaseIntegrationTest : protected Logger::Loggable { public: using TestTimeSystemPtr = std::unique_ptr; using InstanceConstSharedPtrFn = std::function; @@ -191,7 +191,7 @@ class BaseIntegrationTest : Logger::Loggable { void setUpstreamAddress(uint32_t upstream_index, envoy::api::v2::endpoint::LbEndpoint& endpoint) const; - Network::ClientConnectionPtr makeClientConnection(uint32_t port); + virtual Network::ClientConnectionPtr makeClientConnection(uint32_t port); void registerTestServerPorts(const std::vector& port_names); void createTestServer(const std::string& json_path, const std::vector& port_names); diff --git a/test/integration/xfcc_integration_test.cc b/test/integration/xfcc_integration_test.cc index 4e2bed984cfbd..0b62da4439de2 100644 --- a/test/integration/xfcc_integration_test.cc +++ b/test/integration/xfcc_integration_test.cc @@ -92,7 +92,7 @@ Network::TransportSocketFactoryPtr XfccIntegrationTest::createUpstreamSslContext std::move(cfg), *context_manager_, *upstream_stats_store, std::vector{}); } -Network::ClientConnectionPtr XfccIntegrationTest::makeClientConnection() { +Network::ClientConnectionPtr XfccIntegrationTest::makeTcpClientConnection() { Network::Address::InstanceConstSharedPtr address = Network::Utility::resolveUrl("tcp://" + Network::Test::getLoopbackAddressUrlString(version_) + ":" + std::to_string(lookupPort("http"))); @@ -143,7 +143,7 @@ void XfccIntegrationTest::initialize() { void XfccIntegrationTest::testRequestAndResponseWithXfccHeader(std::string previous_xfcc, std::string expected_xfcc) { - Network::ClientConnectionPtr conn = tls_ ? makeMtlsClientConnection() : makeClientConnection(); + Network::ClientConnectionPtr conn = tls_ ? makeMtlsClientConnection() : makeTcpClientConnection(); Http::TestHeaderMapImpl header_map; if (previous_xfcc.empty()) { header_map = Http::TestHeaderMapImpl{{":method", "GET"}, diff --git a/test/integration/xfcc_integration_test.h b/test/integration/xfcc_integration_test.h index 488ff98aad65d..5e1ad2082890d 100644 --- a/test/integration/xfcc_integration_test.h +++ b/test/integration/xfcc_integration_test.h @@ -46,7 +46,7 @@ class XfccIntegrationTest : public testing::TestWithParam Date: Tue, 8 Oct 2019 18:55:06 -0400 Subject: [PATCH 10/76] revert HCM, client and integration test Signed-off-by: Dan Zhang --- .../v2/http_connection_manager.proto | 1 - api/envoy/data/accesslog/v2/accesslog.proto | 1 - bazel/external/quiche.BUILD | 1 - include/envoy/http/protocol.h | 4 +- source/common/http/BUILD | 2 - source/common/http/codec_client.cc | 24 +- source/common/http/codec_client.h | 2 +- source/common/http/conn_manager_config.h | 3 - source/common/http/conn_manager_impl.cc | 30 +-- source/common/http/conn_manager_impl.h | 2 +- source/common/http/headers.h | 1 - source/common/http/utility.cc | 2 - .../grpc/http_grpc_access_log_impl.cc | 3 - .../network/http_connection_manager/config.cc | 11 - .../network/http_connection_manager/config.h | 2 +- source/extensions/quic_listeners/quiche/BUILD | 38 --- .../quiche/active_quic_listener.cc | 6 +- .../quiche/active_quic_listener.h | 2 - .../quic_listeners/quiche/codec_impl.cc | 31 --- .../quic_listeners/quiche/codec_impl.h | 22 +- .../quic_listeners/quiche/envoy_quic_alarm.h | 6 +- .../quiche/envoy_quic_client_connection.cc | 176 -------------- .../quiche/envoy_quic_client_connection.h | 66 ------ .../quiche/envoy_quic_client_session.cc | 87 ------- .../quiche/envoy_quic_client_session.h | 82 ------- .../quiche/envoy_quic_client_stream.cc | 219 ------------------ .../quiche/envoy_quic_client_stream.h | 56 ----- .../quiche/envoy_quic_connection.cc | 5 +- .../quiche/envoy_quic_fake_proof_verifier.h | 3 +- .../quiche/envoy_quic_server_session.cc | 4 +- .../quiche/envoy_quic_server_session.h | 1 - .../quic_filter_manager_connection_impl.cc | 6 +- .../quic_filter_manager_connection_impl.h | 4 +- .../quiche/envoy_quic_proof_source_test.cc | 4 +- .../quiche/envoy_quic_server_session_test.cc | 4 +- .../quic_listeners/quiche/integration/BUILD | 29 --- .../integration/quic_http_integration_test.cc | 185 --------------- test/integration/http_integration.cc | 12 +- test/integration/http_integration.h | 2 +- test/integration/integration.h | 4 +- test/integration/xfcc_integration_test.cc | 4 +- test/integration/xfcc_integration_test.h | 2 +- 42 files changed, 36 insertions(+), 1113 deletions(-) delete mode 100644 source/extensions/quic_listeners/quiche/envoy_quic_client_connection.cc delete mode 100644 source/extensions/quic_listeners/quiche/envoy_quic_client_connection.h delete mode 100644 source/extensions/quic_listeners/quiche/envoy_quic_client_session.cc delete mode 100644 source/extensions/quic_listeners/quiche/envoy_quic_client_session.h delete mode 100644 source/extensions/quic_listeners/quiche/envoy_quic_client_stream.cc delete mode 100644 source/extensions/quic_listeners/quiche/envoy_quic_client_stream.h delete mode 100644 test/extensions/quic_listeners/quiche/integration/BUILD delete mode 100644 test/extensions/quic_listeners/quiche/integration/quic_http_integration_test.cc diff --git a/api/envoy/config/filter/network/http_connection_manager/v2/http_connection_manager.proto b/api/envoy/config/filter/network/http_connection_manager/v2/http_connection_manager.proto index e223cec94bf78..6f4132e93504e 100644 --- a/api/envoy/config/filter/network/http_connection_manager/v2/http_connection_manager.proto +++ b/api/envoy/config/filter/network/http_connection_manager/v2/http_connection_manager.proto @@ -40,7 +40,6 @@ message HttpConnectionManager { // (Envoy does not require HTTP/2 to take place over TLS or to use ALPN. // Prior knowledge is allowed). HTTP2 = 2; - HTTP3 = 3; } enum ServerHeaderTransformation { diff --git a/api/envoy/data/accesslog/v2/accesslog.proto b/api/envoy/data/accesslog/v2/accesslog.proto index 290f69a5cd6c3..1cb7d13112e57 100644 --- a/api/envoy/data/accesslog/v2/accesslog.proto +++ b/api/envoy/data/accesslog/v2/accesslog.proto @@ -41,7 +41,6 @@ message HTTPAccessLogEntry { HTTP10 = 1; HTTP11 = 2; HTTP2 = 3; - HTTP3 = 4; } // Common properties shared by all Envoy access logs. diff --git a/bazel/external/quiche.BUILD b/bazel/external/quiche.BUILD index 47505a2d131fb..67d7d4207ce90 100644 --- a/bazel/external/quiche.BUILD +++ b/bazel/external/quiche.BUILD @@ -1993,7 +1993,6 @@ envoy_cc_library( copts = quiche_copt, repository = "@envoy", tags = ["nofips"], - visibility = ["//visibility:public"], deps = [ ":quic_core_alarm_interface_lib", ":quic_core_crypto_encryption_lib", diff --git a/include/envoy/http/protocol.h b/include/envoy/http/protocol.h index 42c159fe90ece..2f4dcce601259 100644 --- a/include/envoy/http/protocol.h +++ b/include/envoy/http/protocol.h @@ -9,8 +9,8 @@ namespace Http { * Possible HTTP connection/request protocols. The parallel NumProtocols constant allows defining * fixed arrays for each protocol, but does not pollute the enum. */ -enum class Protocol { Http10, Http11, Http2, Http3 }; -const size_t NumProtocols = 4; +enum class Protocol { Http10, Http11, Http2 }; +const size_t NumProtocols = 3; } // namespace Http } // namespace Envoy diff --git a/source/common/http/BUILD b/source/common/http/BUILD index c55267de21ec6..7650f165a3977 100644 --- a/source/common/http/BUILD +++ b/source/common/http/BUILD @@ -51,7 +51,6 @@ envoy_cc_library( "//source/common/http/http1:codec_lib", "//source/common/http/http2:codec_lib", "//source/common/network:filter_lib", - "//source/extensions/quic_listeners/quiche:codec_lib", ], ) @@ -189,7 +188,6 @@ envoy_cc_library( "//source/common/runtime:uuid_util_lib", "//source/common/stream_info:stream_info_lib", "//source/common/tracing:http_tracer_lib", - "//source/extensions/quic_listeners/quiche:codec_lib", ], ) diff --git a/source/common/http/codec_client.cc b/source/common/http/codec_client.cc index 642d4d962b2ad..b13448992b163 100644 --- a/source/common/http/codec_client.cc +++ b/source/common/http/codec_client.cc @@ -9,8 +9,6 @@ #include "common/http/http2/codec_impl.h" #include "common/http/utility.h" -#include "extensions/quic_listeners/quiche/codec_impl.h" - namespace Envoy { namespace Http { @@ -19,12 +17,9 @@ CodecClient::CodecClient(Type type, Network::ClientConnectionPtr&& connection, Event::Dispatcher& dispatcher) : type_(type), connection_(std::move(connection)), host_(host), idle_timeout_(host_->cluster().idleTimeout()) { - if (type_ != Type::HTTP3) { - // Make sure upstream connections process data and then the FIN, rather than processing - // TCP disconnects immediately. (see https://github.com/envoyproxy/envoy/issues/1679 for - // details) - connection_->detectEarlyCloseWhenReadDisabled(false); - } + // Make sure upstream connections process data and then the FIN, rather than processing + // TCP disconnects immediately. (see https://github.com/envoyproxy/envoy/issues/1679 for details) + connection_->detectEarlyCloseWhenReadDisabled(false); connection_->addConnectionCallbacks(*this); connection_->addReadFilter(Network::ReadFilterSharedPtr{new CodecReadFilter(*this)}); @@ -157,19 +152,6 @@ CodecClientProd::CodecClientProd(Type type, Network::ClientConnectionPtr&& conne Http::DEFAULT_MAX_REQUEST_HEADERS_KB, host->cluster().maxResponseHeadersCount()); break; } - case Type::HTTP3: { - // TODO(danzh) this enforce dependency from core code to QUICHE. Is there a - // better way to aoivd such dependency in case QUICHE breaks Envoy build. - // Alternatives: - // 1) move codec creation to Network::Connection instance, in - // QUIC's case, EnvoyQuicClientSession. This is not ideal as - // Network::Connection is not necessart to speak HTTP. - // 2) make codec creation in a static registered factory again. It can be - // only necessary for QUIC and for HTTP2 and HTTP1 just use the existing - // logic. - codec_ = std::make_unique( - dynamic_cast(*connection_), *this); - } } } diff --git a/source/common/http/codec_client.h b/source/common/http/codec_client.h index ab29c15db85a4..66c6c6d6bbc63 100644 --- a/source/common/http/codec_client.h +++ b/source/common/http/codec_client.h @@ -51,7 +51,7 @@ class CodecClient : Logger::Loggable, /** * Type of HTTP codec to use. */ - enum class Type { HTTP1, HTTP2, HTTP3 }; + enum class Type { HTTP1, HTTP2 }; ~CodecClient() override; diff --git a/source/common/http/conn_manager_config.h b/source/common/http/conn_manager_config.h index 31f6d819bcb87..1f25eca3be85a 100644 --- a/source/common/http/conn_manager_config.h +++ b/source/common/http/conn_manager_config.h @@ -26,7 +26,6 @@ namespace Http { COUNTER(downstream_cx_drain_close) \ COUNTER(downstream_cx_http1_total) \ COUNTER(downstream_cx_http2_total) \ - COUNTER(downstream_cx_http3_total) \ COUNTER(downstream_cx_idle_timeout) \ COUNTER(downstream_cx_overload_disable_keepalive) \ COUNTER(downstream_cx_protocol_error) \ @@ -45,7 +44,6 @@ namespace Http { COUNTER(downstream_rq_completed) \ COUNTER(downstream_rq_http1_total) \ COUNTER(downstream_rq_http2_total) \ - COUNTER(downstream_rq_http3_total) \ COUNTER(downstream_rq_idle_timeout) \ COUNTER(downstream_rq_non_relative_path) \ COUNTER(downstream_rq_overload_close) \ @@ -60,7 +58,6 @@ namespace Http { GAUGE(downstream_cx_active, Accumulate) \ GAUGE(downstream_cx_http1_active, Accumulate) \ GAUGE(downstream_cx_http2_active, Accumulate) \ - GAUGE(downstream_cx_http3_active, Accumulate) \ GAUGE(downstream_cx_rx_bytes_buffered, Accumulate) \ GAUGE(downstream_cx_ssl_active, Accumulate) \ GAUGE(downstream_cx_tx_bytes_buffered, Accumulate) \ diff --git a/source/common/http/conn_manager_impl.cc b/source/common/http/conn_manager_impl.cc index 4ff0c09d481da..33354eb6e9219 100644 --- a/source/common/http/conn_manager_impl.cc +++ b/source/common/http/conn_manager_impl.cc @@ -156,8 +156,6 @@ ConnectionManagerImpl::~ConnectionManagerImpl() { if (codec_) { if (codec_->protocol() == Protocol::Http2) { stats_.named_.downstream_cx_http2_active_.dec(); - } else if (codec_->protocol() == Protocol::Http3) { - stats_.named_.downstream_cx_http3_active_.dec(); } else { stats_.named_.downstream_cx_http1_active_.dec(); } @@ -200,7 +198,7 @@ void ConnectionManagerImpl::doEndStream(ActiveStream& stream) { doDeferredStreamDestroy(stream); } - if (reset_stream && codec_->protocol() < Protocol::Http2) { + if (reset_stream && codec_->protocol() != Protocol::Http2) { drain_state_ = DrainState::Closing; } @@ -209,7 +207,7 @@ void ConnectionManagerImpl::doEndStream(ActiveStream& stream) { // Reading may have been disabled for the non-multiplexing case, so enable it again. // Also be sure to unwind any read-disable done by the prior downstream // connection. - if (drain_state_ != DrainState::Closing && codec_->protocol() < Protocol::Http2) { + if (drain_state_ != DrainState::Closing && codec_->protocol() != Protocol::Http2) { while (!read_callbacks_->connection().readEnabled()) { read_callbacks_->connection().readDisable(false); } @@ -276,13 +274,11 @@ void ConnectionManagerImpl::handleCodecException(const char* error) { Network::FilterStatus ConnectionManagerImpl::onData(Buffer::Instance& data, bool) { if (!codec_) { - // Http3 codec should have been instantiated by now. codec_ = config_.createCodec(read_callbacks_->connection(), data, *this); if (codec_->protocol() == Protocol::Http2) { stats_.named_.downstream_cx_http2_total_.inc(); stats_.named_.downstream_cx_http2_active_.inc(); } else { - ASSERT(codec_->protocol() != Protocol::Http3); stats_.named_.downstream_cx_http1_total_.inc(); stats_.named_.downstream_cx_http1_active_.inc(); } @@ -324,7 +320,7 @@ Network::FilterStatus ConnectionManagerImpl::onData(Buffer::Instance& data, bool // either redispatch if there are no streams and we have more data. If we have a single // complete non-WebSocket stream but have not responded yet we will pause socket reads // to apply back pressure. - if (codec_->protocol() < Protocol::Http2) { + if (codec_->protocol() != Protocol::Http2) { if (read_callbacks_->connection().state() == Network::Connection::State::Open && data.length() > 0 && streams_.empty()) { redispatch = true; @@ -339,18 +335,6 @@ Network::FilterStatus ConnectionManagerImpl::onData(Buffer::Instance& data, bool return Network::FilterStatus::StopIteration; } -Network::FilterStatus ConnectionManagerImpl::onNewConnection() { - if (!read_callbacks_->connection().streamInfo().protocol()) { - return Network::FilterStatus::Continue; - } - // Only QUIC connection's stream_info_ specifies protocol. - Buffer::OwnedImpl dummy; - codec_ = config_.createCodec(read_callbacks_->connection(), dummy, *this); - stats_.named_.downstream_cx_http3_total_.inc(); - stats_.named_.downstream_cx_http3_active_.inc(); - return Network::FilterStatus::StopIteration; -} - void ConnectionManagerImpl::resetAllStreams() { while (!streams_.empty()) { // Mimic a downstream reset in this case. We must also remove callbacks here. Though we are @@ -478,8 +462,6 @@ ConnectionManagerImpl::ActiveStream::ActiveStream(ConnectionManagerImpl& connect connection_manager_.stats_.named_.downstream_rq_active_.inc(); if (connection_manager_.codec_->protocol() == Protocol::Http2) { connection_manager_.stats_.named_.downstream_rq_http2_total_.inc(); - } else if (connection_manager_.codec_->protocol() == Protocol::Http3) { - connection_manager_.stats_.named_.downstream_rq_http3_total_.inc(); } else { connection_manager_.stats_.named_.downstream_rq_http1_total_.inc(); } @@ -783,7 +765,7 @@ void ConnectionManagerImpl::ActiveStream::decodeHeaders(HeaderMapPtr&& headers, // Note: Proxy-Connection is not a standard header, but is supported here // since it is supported by http-parser the underlying parser for http // requests. - if (protocol < Protocol::Http2 && !state_.saw_connection_close_ && + if (protocol != Protocol::Http2 && !state_.saw_connection_close_ && request_headers_->ProxyConnection() && absl::EqualsIgnoreCase(request_headers_->ProxyConnection()->value().getStringView(), Http::Headers::get().ConnectionValues.Close)) { @@ -1463,7 +1445,7 @@ void ConnectionManagerImpl::ActiveStream::encodeHeaders(ActiveStreamEncoderFilte // multiplexing, we should disconnect since we don't want to wait around for the request to // finish. if (!state_.remote_complete_) { - if (connection_manager_.codec_->protocol() < Protocol::Http2) { + if (connection_manager_.codec_->protocol() != Protocol::Http2) { connection_manager_.drain_state_ = DrainState::Closing; } @@ -1471,7 +1453,7 @@ void ConnectionManagerImpl::ActiveStream::encodeHeaders(ActiveStreamEncoderFilte } if (connection_manager_.drain_state_ != DrainState::NotDraining && - connection_manager_.codec_->protocol() < Protocol::Http2) { + connection_manager_.codec_->protocol() != Protocol::Http2) { // If the connection manager is draining send "Connection: Close" on HTTP/1.1 connections. // Do not do this for H2 (which drains via GOAWAY) or Upgrade (as the upgrade // payload is no longer HTTP/1.1) diff --git a/source/common/http/conn_manager_impl.h b/source/common/http/conn_manager_impl.h index ab1af23a7ddbe..384c18e4caff7 100644 --- a/source/common/http/conn_manager_impl.h +++ b/source/common/http/conn_manager_impl.h @@ -68,7 +68,7 @@ class ConnectionManagerImpl : Logger::Loggable, // Network::ReadFilter Network::FilterStatus onData(Buffer::Instance& data, bool end_stream) override; - Network::FilterStatus onNewConnection() override; + Network::FilterStatus onNewConnection() override { return Network::FilterStatus::Continue; } void initializeReadFilterCallbacks(Network::ReadFilterCallbacks& callbacks) override; // Http::ConnectionCallbacks diff --git a/source/common/http/headers.h b/source/common/http/headers.h index c16738d8042d9..1bdeb0909014f 100644 --- a/source/common/http/headers.h +++ b/source/common/http/headers.h @@ -273,7 +273,6 @@ class HeaderValues { const std::string Http10String{"HTTP/1.0"}; const std::string Http11String{"HTTP/1.1"}; const std::string Http2String{"HTTP/2"}; - const std::string Http3String{"HTTP/3"}; } ProtocolStrings; struct { diff --git a/source/common/http/utility.cc b/source/common/http/utility.cc index e7c800538a943..65a708903745c 100644 --- a/source/common/http/utility.cc +++ b/source/common/http/utility.cc @@ -388,8 +388,6 @@ const std::string& Utility::getProtocolString(const Protocol protocol) { return Headers::get().ProtocolStrings.Http11String; case Protocol::Http2: return Headers::get().ProtocolStrings.Http2String; - case Protocol::Http3: - return Headers::get().ProtocolStrings.Http3String; } NOT_REACHED_GCOVR_EXCL_LINE; diff --git a/source/extensions/access_loggers/grpc/http_grpc_access_log_impl.cc b/source/extensions/access_loggers/grpc/http_grpc_access_log_impl.cc index 053fae1d92bd5..def967e6089ca 100644 --- a/source/extensions/access_loggers/grpc/http_grpc_access_log_impl.cc +++ b/source/extensions/access_loggers/grpc/http_grpc_access_log_impl.cc @@ -60,9 +60,6 @@ void HttpGrpcAccessLog::emitLog(const Http::HeaderMap& request_headers, case Http::Protocol::Http2: log_entry.set_protocol_version(envoy::data::accesslog::v2::HTTPAccessLogEntry::HTTP2); break; - case Http::Protocol::Http3: - log_entry.set_protocol_version(envoy::data::accesslog::v2::HTTPAccessLogEntry::HTTP3); - break; } } diff --git a/source/extensions/filters/network/http_connection_manager/config.cc b/source/extensions/filters/network/http_connection_manager/config.cc index 3ac21e7a68ee5..aa47daf32a279 100644 --- a/source/extensions/filters/network/http_connection_manager/config.cc +++ b/source/extensions/filters/network/http_connection_manager/config.cc @@ -26,8 +26,6 @@ #include "common/router/scoped_rds.h" #include "common/runtime/runtime_impl.h" -#include "extensions/quic_listeners/quiche/codec_impl.h" - namespace Envoy { namespace Extensions { namespace NetworkFilters { @@ -338,9 +336,6 @@ HttpConnectionManagerConfig::HttpConnectionManagerConfig( case envoy::config::filter::network::http_connection_manager::v2::HttpConnectionManager::HTTP2: codec_type_ = CodecType::HTTP2; break; - case envoy::config::filter::network::http_connection_manager::v2::HttpConnectionManager::HTTP3: - codec_type_ = CodecType::HTTP3; - break; default: NOT_REACHED_GCOVR_EXCL_LINE; } @@ -423,12 +418,6 @@ HttpConnectionManagerConfig::createCodec(Network::Connection& connection, return std::make_unique( connection, callbacks, context_.scope(), http2_settings_, maxRequestHeadersKb(), maxRequestHeadersCount()); - case CodecType::HTTP3: - // TODO(danzh) same as client side. This enforce dependency on QUICHE. Is there a - // better way to aoivd such dependency in case QUICHE breaks this extension. - return std::make_unique( - dynamic_cast(connection), callbacks); - case CodecType::AUTO: return Http::ConnectionManagerUtility::autoCreateCodec( connection, data, callbacks, context_.scope(), http1_settings_, http2_settings_, diff --git a/source/extensions/filters/network/http_connection_manager/config.h b/source/extensions/filters/network/http_connection_manager/config.h index 6074f818b809c..82dd1a16bb724 100644 --- a/source/extensions/filters/network/http_connection_manager/config.h +++ b/source/extensions/filters/network/http_connection_manager/config.h @@ -146,7 +146,7 @@ class HttpConnectionManagerConfig : Logger::Loggable, std::chrono::milliseconds delayedCloseTimeout() const override { return delayed_close_timeout_; } private: - enum class CodecType { HTTP1, HTTP2, HTTP3, AUTO }; + enum class CodecType { HTTP1, HTTP2, AUTO }; void processFilter( const envoy::config::filter::network::http_connection_manager::v2::HttpFilter& proto_config, int i, absl::string_view prefix, FilterFactoriesList& filter_factories, bool& is_terminal); diff --git a/source/extensions/quic_listeners/quiche/BUILD b/source/extensions/quic_listeners/quiche/BUILD index b5898d29b942e..656ee91314eec 100644 --- a/source/extensions/quic_listeners/quiche/BUILD +++ b/source/extensions/quic_listeners/quiche/BUILD @@ -109,7 +109,6 @@ envoy_cc_library( hdrs = ["codec_impl.h"], tags = ["nofips"], deps = [ - ":envoy_quic_client_session_lib", ":envoy_quic_server_session_lib", "//include/envoy/http:codec_interface", "@com_googlesource_quiche//:quic_core_http_spdy_session_lib", @@ -158,30 +157,6 @@ envoy_cc_library( ], ) -envoy_cc_library( - name = "envoy_quic_client_session_lib", - srcs = [ - "envoy_quic_client_session.cc", - "envoy_quic_client_stream.cc", - ], - hdrs = [ - "envoy_quic_client_session.h", - "envoy_quic_client_stream.h", - ], - tags = ["nofips"], - deps = [ - ":envoy_quic_client_connection_lib", - ":envoy_quic_stream_lib", - ":envoy_quic_utils_lib", - ":quic_filter_manager_connection_lib", - "//source/common/buffer:buffer_lib", - "//source/common/common:assert_lib", - "//source/common/http:header_map_lib", - "//source/extensions/quic_listeners/quiche/platform:quic_platform_mem_slice_storage_impl_lib", - "@com_googlesource_quiche//:quic_core_http_client_lib", - ], -) - envoy_cc_library( name = "quic_io_handle_wrapper_lib", hdrs = ["quic_io_handle_wrapper.h"], @@ -217,19 +192,6 @@ envoy_cc_library( ], ) -envoy_cc_library( - name = "envoy_quic_client_connection_lib", - srcs = ["envoy_quic_client_connection.cc"], - hdrs = ["envoy_quic_client_connection.h"], - tags = ["nofips"], - deps = [ - ":envoy_quic_connection_lib", - ":envoy_quic_packet_writer_lib", - "//include/envoy/event:dispatcher_interface", - "//source/common/network:socket_option_factory_lib", - ], -) - envoy_cc_library( name = "envoy_quic_dispatcher_lib", srcs = ["envoy_quic_dispatcher.cc"], diff --git a/source/extensions/quic_listeners/quiche/active_quic_listener.cc b/source/extensions/quic_listeners/quiche/active_quic_listener.cc index af6d92fad2a17..15d1b700e0b44 100644 --- a/source/extensions/quic_listeners/quiche/active_quic_listener.cc +++ b/source/extensions/quic_listeners/quiche/active_quic_listener.cc @@ -43,8 +43,6 @@ ActiveQuicListener::ActiveQuicListener(Event::Dispatcher& dispatcher, quic::QuicRandom::GetInstance(), std::make_unique(), quic::KeyExchangeSource::Default()); auto connection_helper = std::make_unique(dispatcher_); - crypto_config_->AddDefaultConfig(random, connection_helper->GetClock(), - quic::QuicCryptoServerConfig::ConfigOptions()); auto alarm_factory = std::make_unique(dispatcher_, *connection_helper->GetClock()); quic_dispatcher_ = std::make_unique( @@ -54,8 +52,6 @@ ActiveQuicListener::ActiveQuicListener(Event::Dispatcher& dispatcher, quic_dispatcher_->InitializeWithWriter(writer.release()); } -ActiveQuicListener::~ActiveQuicListener() { onListenerShutdown(); } - void ActiveQuicListener::onListenerShutdown() { ENVOY_LOG(info, "Quic listener {} shutdown.", config_.name()); quic_dispatcher_->Shutdown(); @@ -67,7 +63,7 @@ void ActiveQuicListener::onData(Network::UdpRecvData& data) { envoyAddressInstanceToQuicSocketAddress(data.local_address_)); quic::QuicTime timestamp = quic::QuicTime::Zero() + - quic::QuicTime::Delta::FromMicroseconds(std::chrono::duration_cast( + quic::QuicTime::Delta::FromMilliseconds(std::chrono::duration_cast( data.receive_time_.time_since_epoch()) .count()); uint64_t num_slice = data.buffer_->getRawSlices(nullptr, 0); diff --git a/source/extensions/quic_listeners/quiche/active_quic_listener.h b/source/extensions/quic_listeners/quiche/active_quic_listener.h index d724790bd5299..89ef57d83727d 100644 --- a/source/extensions/quic_listeners/quiche/active_quic_listener.h +++ b/source/extensions/quic_listeners/quiche/active_quic_listener.h @@ -32,8 +32,6 @@ class ActiveQuicListener : public Network::UdpListenerCallbacks, Network::UdpListenerPtr&& listener, Network::ListenerConfig& listener_config, const quic::QuicConfig& quic_config); - ~ActiveQuicListener() override; - // TODO(#7465): Make this a callback. void onListenerShutdown(); diff --git a/source/extensions/quic_listeners/quiche/codec_impl.cc b/source/extensions/quic_listeners/quiche/codec_impl.cc index 4d00c6a8d679f..bad608ed8a281 100644 --- a/source/extensions/quic_listeners/quiche/codec_impl.cc +++ b/source/extensions/quic_listeners/quiche/codec_impl.cc @@ -1,6 +1,5 @@ #include "extensions/quic_listeners/quiche/codec_impl.h" -#include "extensions/quic_listeners/quiche/envoy_quic_client_stream.h" #include "extensions/quic_listeners/quiche/envoy_quic_server_stream.h" namespace Envoy { @@ -34,35 +33,5 @@ void QuicHttpServerConnectionImpl::goAway() { quic_server_session_.SendGoAway(quic::QUIC_PEER_GOING_AWAY, "server shutdown imminent"); } -QuicHttpClientConnectionImpl::QuicHttpClientConnectionImpl(EnvoyQuicClientSession& session, - Http::ConnectionCallbacks& callbacks) - : QuicHttpConnectionImplBase(session), quic_client_session_(session) { - session.setHttpConnectionCallbacks(callbacks); -} - -Http::StreamEncoder& -QuicHttpClientConnectionImpl::newStream(Http::StreamDecoder& response_decoder) { - auto stream = dynamic_cast( - quic_client_session_.CreateOutgoingBidirectionalStream()); - stream->setDecoder(response_decoder); - return *stream; -} - -void QuicHttpClientConnectionImpl::onUnderlyingConnectionAboveWriteBufferHighWatermark() { - for (auto& it : quic_client_session_.stream_map()) { - if (!it.second->is_static()) { - dynamic_cast(it.second.get())->runHighWatermarkCallbacks(); - } - } -} - -void QuicHttpClientConnectionImpl::onUnderlyingConnectionBelowWriteBufferLowWatermark() { - for (const auto& it : quic_client_session_.stream_map()) { - if (!it.second->is_static()) { - dynamic_cast(it.second.get())->runLowWatermarkCallbacks(); - } - } -} - } // namespace Quic } // namespace Envoy diff --git a/source/extensions/quic_listeners/quiche/codec_impl.h b/source/extensions/quic_listeners/quiche/codec_impl.h index 141d73220d98a..9b3d5e08a62b8 100644 --- a/source/extensions/quic_listeners/quiche/codec_impl.h +++ b/source/extensions/quic_listeners/quiche/codec_impl.h @@ -3,7 +3,6 @@ #include "common/common/assert.h" #include "common/common/logger.h" -#include "extensions/quic_listeners/quiche/envoy_quic_client_session.h" #include "extensions/quic_listeners/quiche/envoy_quic_server_session.h" namespace Envoy { @@ -22,7 +21,7 @@ class QuicHttpConnectionImplBase : public virtual Http::Connection, // Bypassed. QUIC connection already hands all data to streams. NOT_REACHED_GCOVR_EXCL_LINE; } - Http::Protocol protocol() override { return Http::Protocol::Http3; } + Http::Protocol protocol() override { return Http::Protocol::Http2; } // Returns true if the session has data to send but queued in connection or // stream send buffer. @@ -51,24 +50,5 @@ class QuicHttpServerConnectionImpl : public QuicHttpConnectionImplBase, EnvoyQuicServerSession& quic_server_session_; }; -class QuicHttpClientConnectionImpl : public QuicHttpConnectionImplBase, - public Http::ClientConnection { -public: - QuicHttpClientConnectionImpl(EnvoyQuicClientSession& session, - Http::ConnectionCallbacks& callbacks); - - // Http::ClientConnection - Http::StreamEncoder& newStream(Http::StreamDecoder& response_decoder) override; - - // Http::Connection - void goAway() override { NOT_REACHED_GCOVR_EXCL_LINE; } - void shutdownNotice() override { NOT_REACHED_GCOVR_EXCL_LINE; } - void onUnderlyingConnectionAboveWriteBufferHighWatermark() override; - void onUnderlyingConnectionBelowWriteBufferLowWatermark() override; - -private: - EnvoyQuicClientSession& quic_client_session_; -}; - } // namespace Quic } // namespace Envoy diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_alarm.h b/source/extensions/quic_listeners/quiche/envoy_quic_alarm.h index 22010987c4def..4152f4c101c3f 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_alarm.h +++ b/source/extensions/quic_listeners/quiche/envoy_quic_alarm.h @@ -20,11 +20,7 @@ class EnvoyQuicAlarm : public quic::QuicAlarm { EnvoyQuicAlarm(Event::Dispatcher& dispatcher, const quic::QuicClock& clock, quic::QuicArenaScopedPtr delegate); - ~EnvoyQuicAlarm() override { - if (IsSet()) { - Cancel(); - } - }; + ~EnvoyQuicAlarm() override { ASSERT(!IsSet()); }; // quic::QuicAlarm void CancelImpl() override; diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_client_connection.cc b/source/extensions/quic_listeners/quiche/envoy_quic_client_connection.cc deleted file mode 100644 index 9a6dfb01028cf..0000000000000 --- a/source/extensions/quic_listeners/quiche/envoy_quic_client_connection.cc +++ /dev/null @@ -1,176 +0,0 @@ -#include "extensions/quic_listeners/quiche/envoy_quic_client_connection.h" - -#include "common/network/listen_socket_impl.h" -#include "common/network/socket_option_factory.h" - -#include "extensions/quic_listeners/quiche/envoy_quic_packet_writer.h" -#include "extensions/quic_listeners/quiche/envoy_quic_utils.h" -#include "extensions/transport_sockets/well_known_names.h" - -namespace Envoy { -namespace Quic { - -EnvoyQuicClientConnection::EnvoyQuicClientConnection( - const quic::QuicConnectionId& server_connection_id, - Network::Address::InstanceConstSharedPtr& initial_peer_address, - quic::QuicConnectionHelperInterface& helper, quic::QuicAlarmFactory& alarm_factory, - const quic::ParsedQuicVersionVector& supported_versions, - Network::Address::InstanceConstSharedPtr local_addr, Event::Dispatcher& dispatcher, - const Network::ConnectionSocket::OptionsSharedPtr& options) - : EnvoyQuicClientConnection(server_connection_id, helper, alarm_factory, supported_versions, - dispatcher, - createConnectionSocket(initial_peer_address, local_addr, options)) { -} - -EnvoyQuicClientConnection::EnvoyQuicClientConnection( - const quic::QuicConnectionId& server_connection_id, quic::QuicConnectionHelperInterface& helper, - quic::QuicAlarmFactory& alarm_factory, const quic::ParsedQuicVersionVector& supported_versions, - Event::Dispatcher& dispatcher, Network::ConnectionSocketPtr&& connection_socket) - : EnvoyQuicClientConnection(server_connection_id, helper, alarm_factory, - new EnvoyQuicPacketWriter(*connection_socket), true, - supported_versions, dispatcher, std::move(connection_socket)) {} - -EnvoyQuicClientConnection::EnvoyQuicClientConnection( - const quic::QuicConnectionId& server_connection_id, quic::QuicConnectionHelperInterface& helper, - quic::QuicAlarmFactory& alarm_factory, quic::QuicPacketWriter* writer, bool owns_writer, - const quic::ParsedQuicVersionVector& supported_versions, Event::Dispatcher& dispatcher, - Network::ConnectionSocketPtr&& connection_socket) - : EnvoyQuicConnection( - server_connection_id, - envoyAddressInstanceToQuicSocketAddress(connection_socket->remoteAddress()), helper, - alarm_factory, writer, owns_writer, quic::Perspective::IS_CLIENT, supported_versions, - std::move(connection_socket)), - dispatcher_(dispatcher) {} - -EnvoyQuicClientConnection::~EnvoyQuicClientConnection() { file_event_->setEnabled(0); } - -void EnvoyQuicClientConnection::processPacket( - Network::Address::InstanceConstSharedPtr local_address, - Network::Address::InstanceConstSharedPtr peer_address, Buffer::InstancePtr buffer, - MonotonicTime receive_time) { - quic::QuicTime timestamp = - quic::QuicTime::Zero() + - quic::QuicTime::Delta::FromMicroseconds( - std::chrono::duration_cast(receive_time.time_since_epoch()) - .count()); - uint64_t num_slice = buffer->getRawSlices(nullptr, 0); - ASSERT(num_slice == 1); - Buffer::RawSlice slice; - buffer->getRawSlices(&slice, 1); - quic::QuicReceivedPacket packet(reinterpret_cast(slice.mem_), slice.len_, timestamp, - /*owns_buffer=*/false, /*ttl=*/0, /*ttl_valid=*/true, - /*packet_headers=*/nullptr, /*headers_length=*/0, - /*owns_header_buffer*/ false); - ProcessUdpPacket(envoyAddressInstanceToQuicSocketAddress(local_address), - envoyAddressInstanceToQuicSocketAddress(peer_address), packet); -} - -uint64_t EnvoyQuicClientConnection::maxPacketSize() const { - // TODO(danzh) make this variable configurable to support jumbo frames. - return Network::MAX_UDP_PACKET_SIZE; -} - -void EnvoyQuicClientConnection::setUpConnectionSocket() { - if (connectionSocket()->ioHandle().isOpen()) { - file_event_ = dispatcher_.createFileEvent( - connectionSocket()->ioHandle().fd(), - [this](uint32_t events) -> void { onFileEvent(events); }, Event::FileTriggerType::Edge, - Event::FileReadyType::Read | Event::FileReadyType::Write); - - if (!Network::Socket::applyOptions(connectionSocket()->options(), *connectionSocket(), - envoy::api::v2::core::SocketOption::STATE_LISTENING)) { - ENVOY_LOG_MISC(error, "Fail to apply listening options"); - connectionSocket()->close(); - } - } - if (!connectionSocket()->ioHandle().isOpen()) { - CloseConnection(quic::QUIC_CONNECTION_CANCELLED, "Fail to setup connection socket.", - quic::ConnectionCloseBehavior::SILENT_CLOSE); - } -} - -void EnvoyQuicClientConnection::onFileEvent(uint32_t events) { - ENVOY_CONN_LOG(trace, "socket event: {}", *this, events); - ASSERT(events & (Event::FileReadyType::Read | Event::FileReadyType::Write)); - - if (events & Event::FileReadyType::Write) { - OnCanWrite(); - } - - // It's possible for a write event callback to close the connection, in such case ignore read - // event processing. - if (connected() && (events & Event::FileReadyType::Read)) { - uint32_t old_packets_dropped = packets_dropped_; - while (connected()) { - // Read till socket is drained. - // TODO(danzh): limit read times here. - MonotonicTime receive_time = dispatcher_.timeSource().monotonicTime(); - Api::IoCallUint64Result result = Network::Utility::readFromSocket( - *connectionSocket(), *this, receive_time, &packets_dropped_); - if (!result.ok()) { - if (result.err_->getErrorCode() != Api::IoError::IoErrorCode::Again) { - ENVOY_CONN_LOG(error, "recvmsg result {}: {}", *this, - static_cast(result.err_->getErrorCode()), - result.err_->getErrorDetails()); - } - // Stop reading. - break; - } - - if (result.rc_ == 0) { - // TODO(conqerAtapple): Is zero length packet interesting? If so add stats - // for it. Otherwise remove the warning log below. - ENVOY_CONN_LOG(trace, "received 0-length packet", *this); - } - - if (packets_dropped_ != old_packets_dropped) { - // The kernel tracks SO_RXQ_OVFL as a uint32 which can overflow to a smaller - // value. So as long as this count differs from previously recorded value, - // more packets are dropped by kernel. - uint32_t delta = (packets_dropped_ > old_packets_dropped) - ? (packets_dropped_ - old_packets_dropped) - : (packets_dropped_ + - (std::numeric_limits::max() - old_packets_dropped) + 1); - // TODO(danzh) add stats for this. - ENVOY_CONN_LOG(debug, - "Kernel dropped {} more packets. Consider increase receive buffer size.", - *this, delta); - } - } - } -} - -Network::ConnectionSocketPtr EnvoyQuicClientConnection::createConnectionSocket( - Network::Address::InstanceConstSharedPtr& peer_addr, - Network::Address::InstanceConstSharedPtr& local_addr, - const Network::ConnectionSocket::OptionsSharedPtr& options) { - Network::IoHandlePtr io_handle = peer_addr->socket(Network::Address::SocketType::Datagram); - auto connection_socket = - std::make_unique(std::move(io_handle), local_addr, peer_addr); - connection_socket->addOptions(Network::SocketOptionFactory::buildIpPacketInfoOptions()); - connection_socket->addOptions(Network::SocketOptionFactory::buildRxQueueOverFlowOptions()); - if (options != nullptr) { - connection_socket->addOptions(options); - } - if (!Network::Socket::applyOptions(connection_socket->options(), *connection_socket, - envoy::api::v2::core::SocketOption::STATE_PREBIND)) { - connection_socket->close(); - ENVOY_LOG_MISC(error, "Fail to apply pre-bind options"); - return connection_socket; - } - local_addr->bind(connection_socket->ioHandle().fd()); - ASSERT(local_addr->ip()); - if (local_addr->ip()->port() == 0) { - // Get ephemeral port number. - local_addr = Network::Address::addressFromFd(connection_socket->ioHandle().fd()); - } - if (!Network::Socket::applyOptions(connection_socket->options(), *connection_socket, - envoy::api::v2::core::SocketOption::STATE_BOUND)) { - ENVOY_LOG_MISC(error, "Fail to apply post-bind options"); - connection_socket->close(); - } - return connection_socket; -} - -} // namespace Quic -} // namespace Envoy diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_client_connection.h b/source/extensions/quic_listeners/quiche/envoy_quic_client_connection.h deleted file mode 100644 index f9d79af6acca8..0000000000000 --- a/source/extensions/quic_listeners/quiche/envoy_quic_client_connection.h +++ /dev/null @@ -1,66 +0,0 @@ -#pragma once - -#include "envoy/event/dispatcher.h" - -#include "common/network/utility.h" - -#include "extensions/quic_listeners/quiche/envoy_quic_connection.h" - -namespace Envoy { -namespace Quic { - -// A client QuicConnection instance manages its own I/O events. -class EnvoyQuicClientConnection : public EnvoyQuicConnection, public Network::UdpPacketProcessor { -public: - // A connection socket will be created with given |local_addr|. If binding - // port not provided in |local_addr|, pick up a random port. - EnvoyQuicClientConnection(const quic::QuicConnectionId& server_connection_id, - Network::Address::InstanceConstSharedPtr& initial_peer_address, - quic::QuicConnectionHelperInterface& helper, - quic::QuicAlarmFactory& alarm_factory, - const quic::ParsedQuicVersionVector& supported_versions, - Network::Address::InstanceConstSharedPtr local_addr, - Event::Dispatcher& dispatcher, - const Network::ConnectionSocket::OptionsSharedPtr& options); - - // Overridden to un-register all file events. - ~EnvoyQuicClientConnection() override; - - void processPacket(Network::Address::InstanceConstSharedPtr local_address, - Network::Address::InstanceConstSharedPtr peer_address, - Buffer::InstancePtr buffer, MonotonicTime receive_time) override; - - uint64_t maxPacketSize() const override; - - // Register file event and apply socket options. - void setUpConnectionSocket(); - -private: - EnvoyQuicClientConnection(const quic::QuicConnectionId& server_connection_id, - quic::QuicConnectionHelperInterface& helper, - quic::QuicAlarmFactory& alarm_factory, - const quic::ParsedQuicVersionVector& supported_versions, - Event::Dispatcher& dispatcher, - Network::ConnectionSocketPtr&& connection_socket); - - EnvoyQuicClientConnection(const quic::QuicConnectionId& server_connection_id, - quic::QuicConnectionHelperInterface& helper, - quic::QuicAlarmFactory& alarm_factory, quic::QuicPacketWriter* writer, - bool owns_writer, - const quic::ParsedQuicVersionVector& supported_versions, - Event::Dispatcher& dispatcher, - Network::ConnectionSocketPtr&& connection_socket); - - Network::ConnectionSocketPtr - createConnectionSocket(Network::Address::InstanceConstSharedPtr& peer_addr, - Network::Address::InstanceConstSharedPtr& local_addr, - const Network::ConnectionSocket::OptionsSharedPtr& options); - - void onFileEvent(uint32_t events); - uint32_t packets_dropped_{0}; - Event::Dispatcher& dispatcher_; - Event::FileEventPtr file_event_; -}; - -} // namespace Quic -} // namespace Envoy diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_client_session.cc b/source/extensions/quic_listeners/quiche/envoy_quic_client_session.cc deleted file mode 100644 index f9aeb81df3230..0000000000000 --- a/source/extensions/quic_listeners/quiche/envoy_quic_client_session.cc +++ /dev/null @@ -1,87 +0,0 @@ -#include "extensions/quic_listeners/quiche/envoy_quic_client_session.h" - -namespace Envoy { -namespace Quic { - -EnvoyQuicClientSession::EnvoyQuicClientSession( - const quic::QuicConfig& config, const quic::ParsedQuicVersionVector& supported_versions, - std::unique_ptr connection, const quic::QuicServerId& server_id, - quic::QuicCryptoClientConfig* crypto_config, - quic::QuicClientPushPromiseIndex* push_promise_index, Event::Dispatcher& dispatcher, - uint32_t send_buffer_limit) - : QuicFilterManagerConnectionImpl(*connection, dispatcher, send_buffer_limit), - quic::QuicSpdyClientSession(config, supported_versions, connection.release(), server_id, - crypto_config, push_promise_index) { - Initialize(); -} - -EnvoyQuicClientSession::~EnvoyQuicClientSession() { - ASSERT(!connection()->connected()); - QuicFilterManagerConnectionImpl::quic_connection_ = nullptr; -} - -absl::string_view EnvoyQuicClientSession::requestedServerName() const { - return {GetCryptoStream()->crypto_negotiated_params().sni}; -} - -void EnvoyQuicClientSession::connect() { - dynamic_cast(quic_connection_)->setUpConnectionSocket(); -} - -void EnvoyQuicClientSession::OnConnectionClosed(const quic::QuicConnectionCloseFrame& frame, - quic::ConnectionCloseSource source) { - quic::QuicSpdyClientSession::OnConnectionClosed(frame, source); - onConnectionCloseEvent(frame, source); -} - -void EnvoyQuicClientSession::Initialize() { - quic::QuicSpdyClientSession::Initialize(); - quic_connection_->setEnvoyConnection(*this); -} - -void EnvoyQuicClientSession::OnGoAway(const quic::QuicGoAwayFrame& frame) { - ENVOY_CONN_LOG(debug, "GOAWAY received with error {}: {}", *this, - quic::QuicErrorCodeToString(frame.error_code), frame.reason_phrase); - quic::QuicSpdyClientSession::OnGoAway(frame); - if (http_connection_callbacks_ != nullptr) { - http_connection_callbacks_->onGoAway(); - } -} - -void EnvoyQuicClientSession::OnCryptoHandshakeEvent(CryptoHandshakeEvent event) { - quic::QuicSpdyClientSession::OnCryptoHandshakeEvent(event); - if (event == HANDSHAKE_CONFIRMED) { - raiseEvent(Network::ConnectionEvent::Connected); - } -} - -void EnvoyQuicClientSession::cryptoConnect() { - CryptoConnect(); - set_max_allowed_push_id(0u); - // Wait for finishing handshake with server. - dispatcher_.run(Event::Dispatcher::RunType::Block); -} - -std::unique_ptr EnvoyQuicClientSession::CreateClientStream() { - auto stream = std::make_unique(GetNextOutgoingBidirectionalStreamId(), - this, quic::BIDIRECTIONAL); - return stream; -} - -quic::QuicSpdyStream* EnvoyQuicClientSession::CreateIncomingStream(quic::QuicStreamId id) { - if (!ShouldCreateIncomingStream(id)) { - return nullptr; - } - auto stream = new EnvoyQuicClientStream(id, this, quic::READ_UNIDIRECTIONAL); - ActivateStream(std::unique_ptr(stream)); - return stream; -} - -quic::QuicSpdyStream* EnvoyQuicClientSession::CreateIncomingStream(quic::PendingStream* pending) { - auto stream = new EnvoyQuicClientStream(pending, this, quic::READ_UNIDIRECTIONAL); - ActivateStream(std::unique_ptr(stream)); - return stream; -} - -} // namespace Quic -} // namespace Envoy diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_client_session.h b/source/extensions/quic_listeners/quiche/envoy_quic_client_session.h deleted file mode 100644 index e5dd9862173db..0000000000000 --- a/source/extensions/quic_listeners/quiche/envoy_quic_client_session.h +++ /dev/null @@ -1,82 +0,0 @@ -#pragma once - -#pragma GCC diagnostic push -// QUICHE allows unused parameters. -#pragma GCC diagnostic ignored "-Wunused-parameter" -// QUICHE uses offsetof(). -#pragma GCC diagnostic ignored "-Winvalid-offsetof" -#pragma GCC diagnostic ignored "-Wtype-limits" - -#include "quiche/quic/core/http/quic_spdy_client_session.h" - -#pragma GCC diagnostic pop - -#include "extensions/quic_listeners/quiche/envoy_quic_client_stream.h" -#include "extensions/quic_listeners/quiche/envoy_quic_client_connection.h" -#include "extensions/quic_listeners/quiche/quic_filter_manager_connection_impl.h" - -namespace Envoy { -namespace Quic { - -// Act as a Network::ClientConnection to ClientCodec. -// TODO(danzh) This class doesn't need to inherit Network::FilterManager -// interface but need all other Network::Connection implementation in -// QuicFilterManagerConnectionImpl. Refactor QuicFilterManagerConnectionImpl to -// move FilterManager interface to EnvoyQuicServerSession. -class EnvoyQuicClientSession : public QuicFilterManagerConnectionImpl, - public quic::QuicSpdyClientSession, - public Network::ClientConnection { -public: - EnvoyQuicClientSession(const quic::QuicConfig& config, - const quic::ParsedQuicVersionVector& supported_versions, - std::unique_ptr connection, - const quic::QuicServerId& server_id, - quic::QuicCryptoClientConfig* crypto_config, - quic::QuicClientPushPromiseIndex* push_promise_index, - Event::Dispatcher& dispatcher, uint32_t send_buffer_limit); - - ~EnvoyQuicClientSession() override; - - // Called by QuicHttpClientConnectionImpl before creating data streams. - void setHttpConnectionCallbacks(Http::ConnectionCallbacks& callbacks) { - http_connection_callbacks_ = &callbacks; - } - - // Network::Connection - absl::string_view requestedServerName() const override; - - // Network::ClientConnection - // Only register socket and set socket options. - void connect() override; - - // quic::QuicSession - void OnConnectionClosed(const quic::QuicConnectionCloseFrame& frame, - quic::ConnectionCloseSource source) override; - void Initialize() override; - void OnGoAway(const quic::QuicGoAwayFrame& frame) override; - // quic::QuicSpdyClientSessionBase - void OnCryptoHandshakeEvent(CryptoHandshakeEvent event) override; - - // Do version negotiation and crypto handshake. Fail the connection if server - // doesn't support the one and only supported version. - // This call will block till the handshake finished with either success to - // failure. - void cryptoConnect(); - - using quic::QuicSpdyClientSession::stream_map; - -protected: - // quic::QuicSpdyClientSession - std::unique_ptr CreateClientStream() override; - // quic::QuicSpdySession - quic::QuicSpdyStream* CreateIncomingStream(quic::QuicStreamId id) override; - quic::QuicSpdyStream* CreateIncomingStream(quic::PendingStream* pending) override; - -private: - // These callbacks are owned by network filters and quic session should out live - // them. - Http::ConnectionCallbacks* http_connection_callbacks_{nullptr}; -}; - -} // namespace Quic -} // namespace Envoy diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_client_stream.cc b/source/extensions/quic_listeners/quiche/envoy_quic_client_stream.cc deleted file mode 100644 index 3fc24dea4e25c..0000000000000 --- a/source/extensions/quic_listeners/quiche/envoy_quic_client_stream.cc +++ /dev/null @@ -1,219 +0,0 @@ -#include "extensions/quic_listeners/quiche/envoy_quic_client_stream.h" - -#pragma GCC diagnostic push -// QUICHE allows unused parameters. -#pragma GCC diagnostic ignored "-Wunused-parameter" -// QUICHE uses offsetof(). -#pragma GCC diagnostic ignored "-Winvalid-offsetof" - -#include "quiche/quic/core/quic_session.h" -#include "quiche/quic/core/http/quic_header_list.h" -#include "quiche/quic/core/quic_session.h" -#include "quiche/spdy/core/spdy_header_block.h" -#include "extensions/quic_listeners/quiche/platform/quic_mem_slice_span_impl.h" - -#pragma GCC diagnostic pop - -#include "extensions/quic_listeners/quiche/envoy_quic_utils.h" -#include "extensions/quic_listeners/quiche/envoy_quic_client_session.h" - -#include "common/buffer/buffer_impl.h" -#include "common/http/header_map_impl.h" -#include "common/common/assert.h" - -namespace Envoy { -namespace Quic { - -EnvoyQuicClientStream::EnvoyQuicClientStream(quic::QuicStreamId id, - quic::QuicSpdyClientSession* client_session, - quic::StreamType type) - : quic::QuicSpdyClientStream(id, client_session, type), - EnvoyQuicStream( - session()->config()->GetInitialStreamFlowControlWindowToSend(), - [this]() { runLowWatermarkCallbacks(); }, [this]() { runHighWatermarkCallbacks(); }) {} - -EnvoyQuicClientStream::EnvoyQuicClientStream(quic::PendingStream* pending, - quic::QuicSpdyClientSession* client_session, - quic::StreamType type) - : quic::QuicSpdyClientStream(pending, client_session, type), - EnvoyQuicStream( - session()->config()->GetInitialStreamFlowControlWindowToSend(), - [this]() { runLowWatermarkCallbacks(); }, [this]() { runHighWatermarkCallbacks(); }) {} - -void EnvoyQuicClientStream::encode100ContinueHeaders(const Http::HeaderMap& headers) { - ASSERT(headers.Status()->value() == "100"); - encodeHeaders(headers, false); -} - -void EnvoyQuicClientStream::encodeHeaders(const Http::HeaderMap& headers, bool end_stream) { - ENVOY_STREAM_LOG(debug, "encodeHeaders: (end_stream={}) {}.", *this, end_stream, headers); - WriteHeaders(envoyHeadersToSpdyHeaderBlock(headers), end_stream, nullptr); - local_end_stream_ = end_stream; -} - -void EnvoyQuicClientStream::encodeData(Buffer::Instance& data, bool end_stream) { - ENVOY_STREAM_LOG(debug, "encodeData (end_stream={}) of {} bytes.", *this, end_stream, - data.length()); - local_end_stream_ = end_stream; - // This is counting not serialized bytes in the send buffer. - uint64_t bytes_to_send_old = BufferedDataBytes(); - // QUIC stream must take all. - WriteBodySlices(quic::QuicMemSliceSpan(quic::QuicMemSliceSpanImpl(data)), end_stream); - ASSERT(data.length() == 0); - - uint64_t bytes_to_send_new = BufferedDataBytes(); - ASSERT(bytes_to_send_old <= bytes_to_send_new); - if (bytes_to_send_new > bytes_to_send_old) { - // If buffered bytes changed, update stream and session's watermark book - // keeping. - sendBufferSimulation().checkHighWatermark(bytes_to_send_new); - dynamic_cast(session())->adjustBytesToSend(bytes_to_send_new - - bytes_to_send_old); - } -} - -void EnvoyQuicClientStream::encodeTrailers(const Http::HeaderMap& trailers) { - ASSERT(!local_end_stream_); - local_end_stream_ = true; - ENVOY_STREAM_LOG(debug, "encodeTrailers: {}.", *this, trailers); - WriteTrailers(envoyHeadersToSpdyHeaderBlock(trailers), nullptr); -} - -void EnvoyQuicClientStream::encodeMetadata(const Http::MetadataMapVector& /*metadata_map_vector*/) { - ASSERT(false, "Metadata Frame is not supported in QUIC"); -} - -void EnvoyQuicClientStream::resetStream(Http::StreamResetReason reason) { - // Higher layers expect calling resetStream() to immediately raise reset callbacks. - runResetCallbacks(reason); - - Reset(envoyResetReasonToQuicRstError(reason)); -} - -void EnvoyQuicClientStream::switchStreamBlockState(bool should_block) { - ASSERT(FinishedReadingHeaders(), - "codec buffer limit is reached before response body is delivered."); - if (should_block) { - sequencer()->SetBlockedUntilFlush(); - } else { - sequencer()->SetUnblocked(); - } -} - -void EnvoyQuicClientStream::OnInitialHeadersComplete(bool fin, size_t frame_len, - const quic::QuicHeaderList& header_list) { - quic::QuicSpdyStream::OnInitialHeadersComplete(fin, frame_len, header_list); - if (rst_sent()) { - return; - } - ASSERT(decoder() != nullptr); - ASSERT(headers_decompressed()); - decoder()->decodeHeaders(quicHeadersToEnvoyHeaders(header_list), /*end_stream=*/fin); - if (fin) { - end_stream_decoded_ = true; - } - ConsumeHeaderList(); -} - -void EnvoyQuicClientStream::OnBodyAvailable() { - ASSERT(FinishedReadingHeaders()); - ASSERT(read_disable_counter_ == 0); - ASSERT(!in_encode_data_callstack_); - in_encode_data_callstack_ = true; - - Buffer::InstancePtr buffer = std::make_unique(); - // TODO(danzh): check Envoy per stream buffer limit. - // Currently read out all the data. - while (HasBytesToRead()) { - struct iovec iov; - int num_regions = GetReadableRegions(&iov, 1); - ASSERT(num_regions > 0); - size_t bytes_read = iov.iov_len; - Buffer::RawSlice slice; - buffer->reserve(bytes_read, &slice, 1); - ASSERT(slice.len_ >= bytes_read); - slice.len_ = bytes_read; - memcpy(slice.mem_, iov.iov_base, iov.iov_len); - buffer->commit(&slice, 1); - MarkConsumed(bytes_read); - } - - // True if no trailer and FIN read. - bool finished_reading = IsDoneReading(); - bool empty_payload_with_fin = buffer->length() == 0 && finished_reading; - if (!empty_payload_with_fin || !end_stream_decoded_) { - ASSERT(decoder() != nullptr); - decoder()->decodeData(*buffer, finished_reading); - if (finished_reading) { - end_stream_decoded_ = true; - } - } - - if (!sequencer()->IsClosed()) { - in_encode_data_callstack_ = false; - if (read_disable_counter_ > 0) { - // If readDisable() was ever called during decodeData() and it meant to disable - // reading from downstream, the call must have been deferred. Call it now. - switchStreamBlockState(true); - } - return; - } - - if (!quic::VersionUsesQpack(transport_version()) && !FinishedReadingTrailers()) { - // For Google QUIC implementation, trailers may arrived earlier and wait to - // be consumed after reading all the body. Consume it here. - // IETF QUIC shouldn't reach here because trailers are sent on same stream. - decoder()->decodeTrailers(spdyHeaderBlockToEnvoyHeaders(received_trailers())); - MarkTrailersConsumed(); - } - OnFinRead(); - in_encode_data_callstack_ = false; -} - -void EnvoyQuicClientStream::OnTrailingHeadersComplete(bool fin, size_t frame_len, - const quic::QuicHeaderList& header_list) { - quic::QuicSpdyStream::OnTrailingHeadersComplete(fin, frame_len, header_list); - if (session()->connection()->connected() && - (quic::VersionUsesQpack(transport_version()) || sequencer()->IsClosed()) && - !FinishedReadingTrailers()) { - // Before QPack trailers can arrive before body. Only decode trailers after finishing decoding - // body. - ASSERT(decoder() != nullptr); - decoder()->decodeTrailers(spdyHeaderBlockToEnvoyHeaders(received_trailers())); - MarkTrailersConsumed(); - } -} - -void EnvoyQuicClientStream::OnStreamReset(const quic::QuicRstStreamFrame& frame) { - quic::QuicSpdyClientStream::OnStreamReset(frame); - runResetCallbacks(quicRstErrorToEnvoyResetReason(frame.error_code)); -} - -void EnvoyQuicClientStream::OnConnectionClosed(quic::QuicErrorCode error, - quic::ConnectionCloseSource source) { - quic::QuicSpdyClientStream::OnConnectionClosed(error, source); - runResetCallbacks(quicErrorCodeToEnvoyResetReason(error)); -} - -void EnvoyQuicClientStream::OnCanWrite() { - uint64_t buffered_data_old = BufferedDataBytes(); - quic::QuicSpdyClientStream::OnCanWrite(); - uint64_t buffered_data_new = BufferedDataBytes(); - // As long as OnCanWriteNewData() is no-op, data to sent in buffer shouldn't - // increase. - ASSERT(buffered_data_new <= buffered_data_old); - if (buffered_data_new < buffered_data_old) { - sendBufferSimulation().checkLowWatermark(buffered_data_new); - dynamic_cast(session())->adjustBytesToSend(buffered_data_new - - buffered_data_old); - } -} - -uint32_t EnvoyQuicClientStream::streamId() { return id(); } - -Network::Connection* EnvoyQuicClientStream::connection() { - return dynamic_cast(session()); -} - -} // namespace Quic -} // namespace Envoy diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_client_stream.h b/source/extensions/quic_listeners/quiche/envoy_quic_client_stream.h deleted file mode 100644 index 6bc88ff8a9e81..0000000000000 --- a/source/extensions/quic_listeners/quiche/envoy_quic_client_stream.h +++ /dev/null @@ -1,56 +0,0 @@ -#pragma once - -#pragma GCC diagnostic push -// QUICHE allows unused parameters. -#pragma GCC diagnostic ignored "-Wunused-parameter" -// QUICHE uses offsetof(). -#pragma GCC diagnostic ignored "-Winvalid-offsetof" -#include "quiche/quic/core/http/quic_spdy_client_stream.h" - -#pragma GCC diagnostic pop - -#include "extensions/quic_listeners/quiche/envoy_quic_stream.h" - -namespace Envoy { -namespace Quic { - -// This class is a quic stream and also a request encoder. -class EnvoyQuicClientStream : public quic::QuicSpdyClientStream, public EnvoyQuicStream { -public: - EnvoyQuicClientStream(quic::QuicStreamId id, quic::QuicSpdyClientSession* client_session, - quic::StreamType type); - EnvoyQuicClientStream(quic::PendingStream* pending, quic::QuicSpdyClientSession* client_session, - quic::StreamType type); - - // Http::StreamEncoder - void encode100ContinueHeaders(const Http::HeaderMap& headers) override; - void encodeHeaders(const Http::HeaderMap& headers, bool end_stream) override; - void encodeData(Buffer::Instance& data, bool end_stream) override; - void encodeTrailers(const Http::HeaderMap& trailers) override; - void encodeMetadata(const Http::MetadataMapVector& metadata_map_vector) override; - - // Http::Stream - void resetStream(Http::StreamResetReason reason) override; - // quic::QuicSpdyStream - void OnBodyAvailable() override; - void OnStreamReset(const quic::QuicRstStreamFrame& frame) override; - void OnCanWrite() override; - // quic::Stream - void OnConnectionClosed(quic::QuicErrorCode error, quic::ConnectionCloseSource source) override; - -protected: - // EnvoyQuicStream - void switchStreamBlockState(bool should_block) override; - uint32_t streamId() override; - Network::Connection* connection() override; - - // quic::QuicSpdyStream - // Overridden to pass headers to decoder. - void OnInitialHeadersComplete(bool fin, size_t frame_len, - const quic::QuicHeaderList& header_list) override; - void OnTrailingHeadersComplete(bool fin, size_t frame_len, - const quic::QuicHeaderList& header_list) override; -}; - -} // namespace Quic -} // namespace Envoy diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_connection.cc b/source/extensions/quic_listeners/quiche/envoy_quic_connection.cc index f2459bf79a190..dcc311a6eaac6 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_connection.cc +++ b/source/extensions/quic_listeners/quiche/envoy_quic_connection.cc @@ -19,10 +19,7 @@ EnvoyQuicConnection::EnvoyQuicConnection(const quic::QuicConnectionId& server_co EnvoyQuicConnection::~EnvoyQuicConnection() { connection_socket_->close(); } -uint64_t EnvoyQuicConnection::id() const { - ASSERT(envoy_connection_ != nullptr); - return envoy_connection_->id(); -} +uint64_t EnvoyQuicConnection::id() const { return envoy_connection_->id(); } } // namespace Quic } // namespace Envoy diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_fake_proof_verifier.h b/source/extensions/quic_listeners/quiche/envoy_quic_fake_proof_verifier.h index c8355717bccb8..0861e09fb4d9b 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_fake_proof_verifier.h +++ b/source/extensions/quic_listeners/quiche/envoy_quic_fake_proof_verifier.h @@ -48,8 +48,7 @@ class EnvoyQuicFakeProofVerifier : public quic::ProofVerifier { const quic::ProofVerifyContext* /*context*/, std::string* /*error_details*/, std::unique_ptr* /*details*/, std::unique_ptr /*callback*/) override { - // Cert SCT support is not enabled for fake ProofSource. - if (cert_sct == "" && certs.size() == 1 && certs[0] == "Fake cert") { + if (cert_sct == "Fake timestamp" && certs.size() == 1 && certs[0] == "Fake cert") { return quic::QUIC_SUCCESS; } return quic::QUIC_FAILURE; diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_server_session.cc b/source/extensions/quic_listeners/quiche/envoy_quic_server_session.cc index 456cc77da297e..1d48b93bf3149 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_server_session.cc +++ b/source/extensions/quic_listeners/quiche/envoy_quic_server_session.cc @@ -23,11 +23,9 @@ EnvoyQuicServerSession::EnvoyQuicServerSession( uint32_t send_buffer_limit) : quic::QuicServerSessionBase(config, supported_versions, connection.get(), visitor, helper, crypto_config, compressed_certs_cache), - QuicFilterManagerConnectionImpl(*connection, dispatcher, send_buffer_limit), - quic_connection_(std::move(connection)) {} + QuicFilterManagerConnectionImpl(std::move(connection), dispatcher, send_buffer_limit) {} EnvoyQuicServerSession::~EnvoyQuicServerSession() { - ASSERT(!quic_connection_->connected()); QuicFilterManagerConnectionImpl::quic_connection_ = nullptr; } diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_server_session.h b/source/extensions/quic_listeners/quiche/envoy_quic_server_session.h index 5c5561cbb7f52..ff5d254a23c44 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_server_session.h +++ b/source/extensions/quic_listeners/quiche/envoy_quic_server_session.h @@ -68,7 +68,6 @@ class EnvoyQuicServerSession : public quic::QuicServerSessionBase, private: void setUpRequestDecoder(EnvoyQuicStream& stream); - std::unique_ptr quic_connection_; // These callbacks are owned by network filters and quic session should out live // them. Http::ServerConnectionCallbacks* http_connection_callbacks_{nullptr}; diff --git a/source/extensions/quic_listeners/quiche/quic_filter_manager_connection_impl.cc b/source/extensions/quic_listeners/quiche/quic_filter_manager_connection_impl.cc index 4baaed3a2c8dd..3ea596f3a4542 100644 --- a/source/extensions/quic_listeners/quiche/quic_filter_manager_connection_impl.cc +++ b/source/extensions/quic_listeners/quiche/quic_filter_manager_connection_impl.cc @@ -5,17 +5,17 @@ namespace Envoy { namespace Quic { -QuicFilterManagerConnectionImpl::QuicFilterManagerConnectionImpl(EnvoyQuicConnection& connection, +QuicFilterManagerConnectionImpl::QuicFilterManagerConnectionImpl(std::unique_ptr connection, Event::Dispatcher& dispatcher, uint32_t send_buffer_limit) - : quic_connection_(&connection), dispatcher_(dispatcher), filter_manager_(*this), + : quic_connection_(std::move(connection)), dispatcher_(dispatcher), filter_manager_(*this), // QUIC connection id can be 18 bytes. It's easier to use hash value instead // of trying to map it into a 64-bit space. stream_info_(dispatcher.timeSource()), id_(quic_connection_->connection_id().Hash()), write_buffer_watermark_simulation_( send_buffer_limit / 2, send_buffer_limit, [this]() { onSendBufferLowWatermark(); }, [this]() { onSendBufferHighWatermark(); }, ENVOY_LOGGER()) { - stream_info_.protocol(Http::Protocol::Http3); + stream_info_.protocol(Http::Protocol::Http2); } void QuicFilterManagerConnectionImpl::addWriteFilter(Network::WriteFilterSharedPtr filter) { diff --git a/source/extensions/quic_listeners/quiche/quic_filter_manager_connection_impl.h b/source/extensions/quic_listeners/quiche/quic_filter_manager_connection_impl.h index a72364d334e53..ace95927c9e79 100644 --- a/source/extensions/quic_listeners/quiche/quic_filter_manager_connection_impl.h +++ b/source/extensions/quic_listeners/quiche/quic_filter_manager_connection_impl.h @@ -18,7 +18,7 @@ namespace Quic { class QuicFilterManagerConnectionImpl : public Network::FilterManagerConnection, protected Logger::Loggable { public: - QuicFilterManagerConnectionImpl(EnvoyQuicConnection& connection, Event::Dispatcher& dispatcher, + QuicFilterManagerConnectionImpl(std::unique_ptr connection, Event::Dispatcher& dispatcher, uint32_t send_buffer_limit); // Network::FilterManager @@ -106,7 +106,7 @@ class QuicFilterManagerConnectionImpl : public Network::FilterManagerConnection, void raiseEvent(Network::ConnectionEvent event); - EnvoyQuicConnection* quic_connection_{nullptr}; + std::unique_ptr quic_connection_; // TODO(danzh): populate stats. std::unique_ptr stats_; Event::Dispatcher& dispatcher_; diff --git a/test/extensions/quic_listeners/quiche/envoy_quic_proof_source_test.cc b/test/extensions/quic_listeners/quiche/envoy_quic_proof_source_test.cc index 57bdf94e9e1f0..4737a532f558b 100644 --- a/test/extensions/quic_listeners/quiche/envoy_quic_proof_source_test.cc +++ b/test/extensions/quic_listeners/quiche/envoy_quic_proof_source_test.cc @@ -62,8 +62,8 @@ TEST_F(EnvoyQuicFakeProofSourceTest, TestGetProof) { TEST_F(EnvoyQuicFakeProofSourceTest, TestVerifyProof) { EXPECT_EQ(quic::QUIC_SUCCESS, proof_verifier_.VerifyProof(hostname_, /*port=*/0, server_config_, version_, chlo_hash_, - expected_certs_, "", expected_signature_, nullptr, nullptr, - nullptr, nullptr)); + expected_certs_, "Fake timestamp", expected_signature_, + nullptr, nullptr, nullptr, nullptr)); std::vector wrong_certs{"wrong cert"}; EXPECT_EQ(quic::QUIC_FAILURE, proof_verifier_.VerifyProof(hostname_, /*port=*/0, server_config_, version_, chlo_hash_, diff --git a/test/extensions/quic_listeners/quiche/envoy_quic_server_session_test.cc b/test/extensions/quic_listeners/quiche/envoy_quic_server_session_test.cc index 419953ca523f8..dc4fbbe642682 100644 --- a/test/extensions/quic_listeners/quiche/envoy_quic_server_session_test.cc +++ b/test/extensions/quic_listeners/quiche/envoy_quic_server_session_test.cc @@ -103,7 +103,7 @@ class EnvoyQuicServerSessionTest : public testing::TestWithParam { bool installReadFilter() { // Setup read filter. envoy_quic_session_.addReadFilter(read_filter_); - EXPECT_EQ(Http::Protocol::Http3, + EXPECT_EQ(Http::Protocol::Http2, read_filter_->callbacks_->connection().streamInfo().protocol().value()); EXPECT_EQ(envoy_quic_session_.id(), read_filter_->callbacks_->connection().id()); EXPECT_EQ(&envoy_quic_session_, &read_filter_->callbacks_->connection()); @@ -115,7 +115,7 @@ class EnvoyQuicServerSessionTest : public testing::TestWithParam { // Create ServerConnection instance and setup callbacks for it. http_connection_ = std::make_unique(envoy_quic_session_, http_connection_callbacks_); - EXPECT_EQ(Http::Protocol::Http3, http_connection_->protocol()); + EXPECT_EQ(Http::Protocol::Http2, http_connection_->protocol()); // Stop iteration to avoid calling getRead/WriteBuffer(). return Network::FilterStatus::StopIteration; })); diff --git a/test/extensions/quic_listeners/quiche/integration/BUILD b/test/extensions/quic_listeners/quiche/integration/BUILD deleted file mode 100644 index 4e2cde2d9bd8d..0000000000000 --- a/test/extensions/quic_listeners/quiche/integration/BUILD +++ /dev/null @@ -1,29 +0,0 @@ -licenses(["notice"]) # Apache 2 - -load( - "//bazel:envoy_build_system.bzl", - "envoy_cc_fuzz_test", - "envoy_cc_test", - "envoy_cc_test_binary", - "envoy_cc_test_library", - "envoy_package", - "envoy_proto_library", -) - -envoy_package() - -envoy_cc_test( - name = "quic_http_integration_test", - srcs = ["quic_http_integration_test.cc"], - data = ["//test/config/integration/certs"], - tags = ["nofips"], - deps = [ - "//source/extensions/quic_listeners/quiche:active_quic_listener_config_lib", - "//source/extensions/quic_listeners/quiche:envoy_quic_client_connection_lib", - "//source/extensions/quic_listeners/quiche:envoy_quic_client_session_lib", - "//source/extensions/quic_listeners/quiche:envoy_quic_connection_helper_lib", - "//source/extensions/quic_listeners/quiche:envoy_quic_proof_verifier_lib", - "//source/extensions/quic_listeners/quiche:quic_transport_socket_factory_lib", - "//test/integration:http_integration_lib", - ], -) diff --git a/test/extensions/quic_listeners/quiche/integration/quic_http_integration_test.cc b/test/extensions/quic_listeners/quiche/integration/quic_http_integration_test.cc deleted file mode 100644 index 86d5f08b48f58..0000000000000 --- a/test/extensions/quic_listeners/quiche/integration/quic_http_integration_test.cc +++ /dev/null @@ -1,185 +0,0 @@ -#include "test/config/utility.h" -#include "test/integration/http_integration.h" -#include "test/test_common/utility.h" - -#pragma GCC diagnostic push -// QUICHE allows unused parameters. -#pragma GCC diagnostic ignored "-Wunused-parameter" -// QUICHE uses offsetof(). -#pragma GCC diagnostic ignored "-Winvalid-offsetof" - -#include "quiche/quic/core/http/quic_client_push_promise_index.h" -#include "quiche/quic/core/quic_utils.h" - -#pragma GCC diagnostic pop - -#include "extensions/quic_listeners/quiche/envoy_quic_client_session.h" -#include "extensions/quic_listeners/quiche/envoy_quic_client_connection.h" -#include "extensions/quic_listeners/quiche/envoy_quic_fake_proof_verifier.h" -#include "extensions/quic_listeners/quiche/envoy_quic_connection_helper.h" -#include "extensions/quic_listeners/quiche/envoy_quic_alarm_factory.h" -#include "extensions/quic_listeners/quiche/envoy_quic_packet_writer.h" - -namespace Envoy { -namespace Quic { - -class CodecClientCallbacksForTest : public Http::CodecClientCallbacks { -public: - void onStreamDestroy() override {} - - void onStreamReset(Http::StreamResetReason reason) override { - last_stream_reset_reason_ = reason; - } - - Http::StreamResetReason last_stream_reset_reason_{Http::StreamResetReason::LocalReset}; -}; - -class QuicHttpIntegrationTest : public testing::TestWithParam, - public HttpIntegrationTest { -public: - QuicHttpIntegrationTest() - : HttpIntegrationTest(Http::CodecClient::Type::HTTP3, GetParam(), - ConfigHelper::QUIC_HTTP_PROXY_CONFIG), - supported_versions_(quic::CurrentSupportedVersions()), - crypto_config_(std::make_unique()), conn_helper_(*dispatcher_), - alarm_factory_(*dispatcher_, *conn_helper_.GetClock()) { - quic::SetVerbosityLogThreshold(1); - } - - Network::ClientConnectionPtr makeClientConnection(uint32_t port) override { - Network::Address::InstanceConstSharedPtr server_addr = Network::Utility::resolveUrl( - fmt::format("udp://{}:{}", Network::Test::getLoopbackAddressUrlString(version_), port)); - Network::Address::InstanceConstSharedPtr local_addr = - Network::Test::getCanonicalLoopbackAddress(version_); - // Initiate a QUIC connection with the highest supported version. If not - // supported by server, this connection will fail. - // TODO(danzh) Implement retry upon version mismatch and modify test frame work to specify a - // different version set on server side to test that. - auto connection = std::make_unique( - getNextServerDesignatedConnectionId(), server_addr, conn_helper_, alarm_factory_, - quic::ParsedQuicVersionVector{supported_versions_[0]}, local_addr, *dispatcher_, nullptr); - auto session = std::make_unique( - quic_config_, supported_versions_, std::move(connection), server_id_, &crypto_config_, - &push_promise_index_, *dispatcher_, 0); - return session; - } - - // This call may fail because of INVALID_VERSION, because QUIC connection doesn't support - // in-connection version negotiation. - // TODO(#8479) Popagate INVALID_VERSION error to caller and let caller to use server advertised - // version list to create a new connection with mutually supported version and make client codec - // again. - IntegrationCodecClientPtr makeRawHttpConnection(Network::ClientConnectionPtr&& conn) override { - IntegrationCodecClientPtr codec = HttpIntegrationTest::makeRawHttpConnection(std::move(conn)); - ASSERT(!codec->connected()); - dynamic_cast(codec->connection())->cryptoConnect(); - if (codec->disconnected()) { - // Connection may get closed during version negotiation or handshake. - ENVOY_LOG(error, "Fail to connect to server with error: {}", - codec->connection()->transportFailureReason()); - } else { - codec->setCodecClientCallbacks(client_codec_callback_); - } - return codec; - } - - quic::QuicConnectionId getNextServerDesignatedConnectionId() { - quic::QuicCryptoClientConfig::CachedState* cached = crypto_config_.LookupOrCreate(server_id_); - // If the cached state indicates that we should use a server-designated - // connection ID, then return that connection ID. - quic::QuicConnectionId conn_id = cached->has_server_designated_connection_id() - ? cached->GetNextServerDesignatedConnectionId() - : quic::EmptyQuicConnectionId(); - return conn_id.IsEmpty() ? quic::QuicUtils::CreateRandomConnectionId() : conn_id; - } - - void initialize() override { - config_helper_.addConfigModifier([](envoy::config::bootstrap::v2::Bootstrap& bootstrap) { - envoy::api::v2::auth::DownstreamTlsContext tls_context; - ConfigHelper::initializeTls({}, *tls_context.mutable_common_tls_context()); - auto* filter_chain = - bootstrap.mutable_static_resources()->mutable_listeners(0)->mutable_filter_chains(0); - auto* transport_socket = filter_chain->mutable_transport_socket(); - TestUtility::jsonConvert(tls_context, *transport_socket->mutable_config()); - }); - config_helper_.addConfigModifier( - [](envoy::config::filter::network::http_connection_manager::v2::HttpConnectionManager& - hcm) { - hcm.mutable_delayed_close_timeout()->set_nanos(0); - EXPECT_EQ(hcm.codec_type(), envoy::config::filter::network::http_connection_manager::v2:: - HttpConnectionManager::HTTP3); - }); - - HttpIntegrationTest::initialize(); - registerTestServerPorts({"http"}); - } - -protected: - quic::QuicConfig quic_config_; - quic::QuicServerId server_id_; - quic::QuicClientPushPromiseIndex push_promise_index_; - quic::ParsedQuicVersionVector supported_versions_; - quic::QuicCryptoClientConfig crypto_config_; - EnvoyQuicConnectionHelper conn_helper_; - EnvoyQuicAlarmFactory alarm_factory_; - CodecClientCallbacksForTest client_codec_callback_; -}; - -INSTANTIATE_TEST_SUITE_P(IpVersions, QuicHttpIntegrationTest, - testing::ValuesIn(TestEnvironment::getIpVersionsForTest()), - TestUtility::ipTestParamsToString); - -TEST_P(QuicHttpIntegrationTest, GetRequestAndEmptyResponse) { - testRouterHeaderOnlyRequestAndResponse(); -} - -TEST_P(QuicHttpIntegrationTest, GetRequestAndResponseWithBody) { - initialize(); - sendRequestAndVerifyResponse(default_request_headers_, /*request_size=*/0, - default_response_headers_, /*response_size=*/1024, - /*backend_index*/ 0); -} - -TEST_P(QuicHttpIntegrationTest, PostRequestAndResponseWithBody) { - testRouterRequestAndResponseWithBody(1024, 512, false); -} - -TEST_P(QuicHttpIntegrationTest, PostRequestWithBigHeadersAndResponseWithBody) { - testRouterRequestAndResponseWithBody(1024, 512, true); -} - -TEST_P(QuicHttpIntegrationTest, RouterUpstreamDisconnectBeforeRequestcomplete) { - testRouterUpstreamDisconnectBeforeRequestComplete(); -} - -TEST_P(QuicHttpIntegrationTest, RouterUpstreamDisconnectBeforeResponseComplete) { - testRouterUpstreamDisconnectBeforeResponseComplete(); - EXPECT_EQ(Http::StreamResetReason::RemoteReset, client_codec_callback_.last_stream_reset_reason_); -} - -TEST_P(QuicHttpIntegrationTest, RouterDownstreamDisconnectBeforeRequestComplete) { - testRouterDownstreamDisconnectBeforeRequestComplete(); -} - -TEST_P(QuicHttpIntegrationTest, RouterDownstreamDisconnectBeforeResponseComplete) { - testRouterDownstreamDisconnectBeforeResponseComplete(); -} - -TEST_P(QuicHttpIntegrationTest, RouterUpstreamResponseBeforeRequestComplete) { - testRouterUpstreamResponseBeforeRequestComplete(); -} - -TEST_P(QuicHttpIntegrationTest, Retry) { testRetry(); } - -TEST_P(QuicHttpIntegrationTest, UpstreamThrottlingOnGiantResponseBody) { - config_helper_.setBufferLimits(/*upstream_buffer_limit=*/1024, /*downstream_buffer_limit=*/1024); - testRouterRequestAndResponseWithBody(/*request_size=*/512, /*response_size=*/1024 * 1024, false); -} - -TEST_P(QuicHttpIntegrationTest, DownstreamThrottlingOnGiantPost) { - config_helper_.setBufferLimits(/*upstream_buffer_limit=*/1024, /*downstream_buffer_limit=*/1024); - testRouterRequestAndResponseWithBody(/*request_size=*/1024 * 1024, /*response_size=*/1024, false); -} - -} // namespace Quic -} // namespace Envoy diff --git a/test/integration/http_integration.cc b/test/integration/http_integration.cc index 8bc0f9d76ed4f..eaa88e0b8bd27 100644 --- a/test/integration/http_integration.cc +++ b/test/integration/http_integration.cc @@ -46,9 +46,6 @@ typeToCodecType(Http::CodecClient::Type type) { case Http::CodecClient::Type::HTTP2: return envoy::config::filter::network::http_connection_manager::v2::HttpConnectionManager:: HTTP2; - case Http::CodecClient::Type::HTTP3: - return envoy::config::filter::network::http_connection_manager::v2::HttpConnectionManager:: - HTTP3; default: RELEASE_ASSERT(0, ""); } @@ -63,12 +60,7 @@ IntegrationCodecClient::IntegrationCodecClient( callbacks_(*this), codec_callbacks_(*this) { connection_->addConnectionCallbacks(callbacks_); setCodecConnectionCallbacks(codec_callbacks_); - if (type != CodecClient::Type::HTTP3) { - // Only expect to have IO event if it's not QUIC. Because call to connect() in CodecClientProd - // for QUIC doesn't send anything to server, but just register file event. QUIC connection won't - // have any IO event till cryptoConnect() is called later. - dispatcher.run(Event::Dispatcher::RunType::Block); - } + dispatcher.run(Event::Dispatcher::RunType::Block); } void IntegrationCodecClient::flushWrite() { @@ -581,9 +573,7 @@ void HttpIntegrationTest::testRouterUpstreamResponseBeforeRequestComplete() { ASSERT_TRUE(fake_upstreams_[0]->waitForHttpConnection(*dispatcher_, fake_upstream_connection_)); ASSERT_TRUE(fake_upstream_connection_->waitForNewStream(*dispatcher_, upstream_request_)); ASSERT_TRUE(upstream_request_->waitForHeadersComplete()); - upstream_request_->encodeHeaders(default_response_headers_, false); - upstream_request_->encodeData(512, true); response->waitForEndStream(); diff --git a/test/integration/http_integration.h b/test/integration/http_integration.h index 1a8f860b87d96..8bbae101304b0 100644 --- a/test/integration/http_integration.h +++ b/test/integration/http_integration.h @@ -108,7 +108,7 @@ class HttpIntegrationTest : public BaseIntegrationTest { IntegrationCodecClientPtr makeHttpConnection(uint32_t port); // Makes a http connection object without checking its connected state. - virtual IntegrationCodecClientPtr makeRawHttpConnection(Network::ClientConnectionPtr&& conn); + IntegrationCodecClientPtr makeRawHttpConnection(Network::ClientConnectionPtr&& conn); // Makes a http connection object with asserting a connected state. IntegrationCodecClientPtr makeHttpConnection(Network::ClientConnectionPtr&& conn); diff --git a/test/integration/integration.h b/test/integration/integration.h index 0e2b7d48c6fe1..7cd2109cc8593 100644 --- a/test/integration/integration.h +++ b/test/integration/integration.h @@ -138,7 +138,7 @@ struct ApiFilesystemConfig { /** * Test fixture for all integration tests. */ -class BaseIntegrationTest : protected Logger::Loggable { +class BaseIntegrationTest : Logger::Loggable { public: using TestTimeSystemPtr = std::unique_ptr; using InstanceConstSharedPtrFn = std::function; @@ -191,7 +191,7 @@ class BaseIntegrationTest : protected Logger::Loggable { void setUpstreamAddress(uint32_t upstream_index, envoy::api::v2::endpoint::LbEndpoint& endpoint) const; - virtual Network::ClientConnectionPtr makeClientConnection(uint32_t port); + Network::ClientConnectionPtr makeClientConnection(uint32_t port); void registerTestServerPorts(const std::vector& port_names); void createTestServer(const std::string& json_path, const std::vector& port_names); diff --git a/test/integration/xfcc_integration_test.cc b/test/integration/xfcc_integration_test.cc index 0b62da4439de2..4e2bed984cfbd 100644 --- a/test/integration/xfcc_integration_test.cc +++ b/test/integration/xfcc_integration_test.cc @@ -92,7 +92,7 @@ Network::TransportSocketFactoryPtr XfccIntegrationTest::createUpstreamSslContext std::move(cfg), *context_manager_, *upstream_stats_store, std::vector{}); } -Network::ClientConnectionPtr XfccIntegrationTest::makeTcpClientConnection() { +Network::ClientConnectionPtr XfccIntegrationTest::makeClientConnection() { Network::Address::InstanceConstSharedPtr address = Network::Utility::resolveUrl("tcp://" + Network::Test::getLoopbackAddressUrlString(version_) + ":" + std::to_string(lookupPort("http"))); @@ -143,7 +143,7 @@ void XfccIntegrationTest::initialize() { void XfccIntegrationTest::testRequestAndResponseWithXfccHeader(std::string previous_xfcc, std::string expected_xfcc) { - Network::ClientConnectionPtr conn = tls_ ? makeMtlsClientConnection() : makeTcpClientConnection(); + Network::ClientConnectionPtr conn = tls_ ? makeMtlsClientConnection() : makeClientConnection(); Http::TestHeaderMapImpl header_map; if (previous_xfcc.empty()) { header_map = Http::TestHeaderMapImpl{{":method", "GET"}, diff --git a/test/integration/xfcc_integration_test.h b/test/integration/xfcc_integration_test.h index 5e1ad2082890d..488ff98aad65d 100644 --- a/test/integration/xfcc_integration_test.h +++ b/test/integration/xfcc_integration_test.h @@ -46,7 +46,7 @@ class XfccIntegrationTest : public testing::TestWithParam Date: Tue, 8 Oct 2019 19:05:22 -0400 Subject: [PATCH 11/76] Revert "revert HCM, client and integration test" This reverts commit 95eb51d5967998418597cb0e1f07c8736f8ecb45. Signed-off-by: Dan Zhang --- .../v2/http_connection_manager.proto | 1 + api/envoy/data/accesslog/v2/accesslog.proto | 1 + bazel/external/quiche.BUILD | 1 + include/envoy/http/protocol.h | 4 +- source/common/http/BUILD | 2 + source/common/http/codec_client.cc | 24 +- source/common/http/codec_client.h | 2 +- source/common/http/conn_manager_config.h | 3 + source/common/http/conn_manager_impl.cc | 30 ++- source/common/http/conn_manager_impl.h | 2 +- source/common/http/headers.h | 1 + source/common/http/utility.cc | 2 + .../grpc/http_grpc_access_log_impl.cc | 3 + .../network/http_connection_manager/config.cc | 11 + .../network/http_connection_manager/config.h | 2 +- source/extensions/quic_listeners/quiche/BUILD | 38 +++ .../quiche/active_quic_listener.cc | 6 +- .../quiche/active_quic_listener.h | 2 + .../quic_listeners/quiche/codec_impl.cc | 31 +++ .../quic_listeners/quiche/codec_impl.h | 22 +- .../quic_listeners/quiche/envoy_quic_alarm.h | 6 +- .../quiche/envoy_quic_client_connection.cc | 176 ++++++++++++++ .../quiche/envoy_quic_client_connection.h | 66 ++++++ .../quiche/envoy_quic_client_session.cc | 87 +++++++ .../quiche/envoy_quic_client_session.h | 82 +++++++ .../quiche/envoy_quic_client_stream.cc | 219 ++++++++++++++++++ .../quiche/envoy_quic_client_stream.h | 56 +++++ .../quiche/envoy_quic_connection.cc | 5 +- .../quiche/envoy_quic_fake_proof_verifier.h | 3 +- .../quiche/envoy_quic_server_session.cc | 4 +- .../quiche/envoy_quic_server_session.h | 1 + .../quic_filter_manager_connection_impl.cc | 6 +- .../quic_filter_manager_connection_impl.h | 4 +- .../quiche/envoy_quic_proof_source_test.cc | 4 +- .../quiche/envoy_quic_server_session_test.cc | 4 +- .../quic_listeners/quiche/integration/BUILD | 29 +++ .../integration/quic_http_integration_test.cc | 185 +++++++++++++++ test/integration/http_integration.cc | 12 +- test/integration/http_integration.h | 2 +- test/integration/integration.h | 4 +- test/integration/xfcc_integration_test.cc | 4 +- test/integration/xfcc_integration_test.h | 2 +- 42 files changed, 1113 insertions(+), 36 deletions(-) create mode 100644 source/extensions/quic_listeners/quiche/envoy_quic_client_connection.cc create mode 100644 source/extensions/quic_listeners/quiche/envoy_quic_client_connection.h create mode 100644 source/extensions/quic_listeners/quiche/envoy_quic_client_session.cc create mode 100644 source/extensions/quic_listeners/quiche/envoy_quic_client_session.h create mode 100644 source/extensions/quic_listeners/quiche/envoy_quic_client_stream.cc create mode 100644 source/extensions/quic_listeners/quiche/envoy_quic_client_stream.h create mode 100644 test/extensions/quic_listeners/quiche/integration/BUILD create mode 100644 test/extensions/quic_listeners/quiche/integration/quic_http_integration_test.cc diff --git a/api/envoy/config/filter/network/http_connection_manager/v2/http_connection_manager.proto b/api/envoy/config/filter/network/http_connection_manager/v2/http_connection_manager.proto index 6f4132e93504e..e223cec94bf78 100644 --- a/api/envoy/config/filter/network/http_connection_manager/v2/http_connection_manager.proto +++ b/api/envoy/config/filter/network/http_connection_manager/v2/http_connection_manager.proto @@ -40,6 +40,7 @@ message HttpConnectionManager { // (Envoy does not require HTTP/2 to take place over TLS or to use ALPN. // Prior knowledge is allowed). HTTP2 = 2; + HTTP3 = 3; } enum ServerHeaderTransformation { diff --git a/api/envoy/data/accesslog/v2/accesslog.proto b/api/envoy/data/accesslog/v2/accesslog.proto index 1cb7d13112e57..290f69a5cd6c3 100644 --- a/api/envoy/data/accesslog/v2/accesslog.proto +++ b/api/envoy/data/accesslog/v2/accesslog.proto @@ -41,6 +41,7 @@ message HTTPAccessLogEntry { HTTP10 = 1; HTTP11 = 2; HTTP2 = 3; + HTTP3 = 4; } // Common properties shared by all Envoy access logs. diff --git a/bazel/external/quiche.BUILD b/bazel/external/quiche.BUILD index 67d7d4207ce90..47505a2d131fb 100644 --- a/bazel/external/quiche.BUILD +++ b/bazel/external/quiche.BUILD @@ -1993,6 +1993,7 @@ envoy_cc_library( copts = quiche_copt, repository = "@envoy", tags = ["nofips"], + visibility = ["//visibility:public"], deps = [ ":quic_core_alarm_interface_lib", ":quic_core_crypto_encryption_lib", diff --git a/include/envoy/http/protocol.h b/include/envoy/http/protocol.h index 2f4dcce601259..42c159fe90ece 100644 --- a/include/envoy/http/protocol.h +++ b/include/envoy/http/protocol.h @@ -9,8 +9,8 @@ namespace Http { * Possible HTTP connection/request protocols. The parallel NumProtocols constant allows defining * fixed arrays for each protocol, but does not pollute the enum. */ -enum class Protocol { Http10, Http11, Http2 }; -const size_t NumProtocols = 3; +enum class Protocol { Http10, Http11, Http2, Http3 }; +const size_t NumProtocols = 4; } // namespace Http } // namespace Envoy diff --git a/source/common/http/BUILD b/source/common/http/BUILD index 7650f165a3977..c55267de21ec6 100644 --- a/source/common/http/BUILD +++ b/source/common/http/BUILD @@ -51,6 +51,7 @@ envoy_cc_library( "//source/common/http/http1:codec_lib", "//source/common/http/http2:codec_lib", "//source/common/network:filter_lib", + "//source/extensions/quic_listeners/quiche:codec_lib", ], ) @@ -188,6 +189,7 @@ envoy_cc_library( "//source/common/runtime:uuid_util_lib", "//source/common/stream_info:stream_info_lib", "//source/common/tracing:http_tracer_lib", + "//source/extensions/quic_listeners/quiche:codec_lib", ], ) diff --git a/source/common/http/codec_client.cc b/source/common/http/codec_client.cc index b13448992b163..642d4d962b2ad 100644 --- a/source/common/http/codec_client.cc +++ b/source/common/http/codec_client.cc @@ -9,6 +9,8 @@ #include "common/http/http2/codec_impl.h" #include "common/http/utility.h" +#include "extensions/quic_listeners/quiche/codec_impl.h" + namespace Envoy { namespace Http { @@ -17,9 +19,12 @@ CodecClient::CodecClient(Type type, Network::ClientConnectionPtr&& connection, Event::Dispatcher& dispatcher) : type_(type), connection_(std::move(connection)), host_(host), idle_timeout_(host_->cluster().idleTimeout()) { - // Make sure upstream connections process data and then the FIN, rather than processing - // TCP disconnects immediately. (see https://github.com/envoyproxy/envoy/issues/1679 for details) - connection_->detectEarlyCloseWhenReadDisabled(false); + if (type_ != Type::HTTP3) { + // Make sure upstream connections process data and then the FIN, rather than processing + // TCP disconnects immediately. (see https://github.com/envoyproxy/envoy/issues/1679 for + // details) + connection_->detectEarlyCloseWhenReadDisabled(false); + } connection_->addConnectionCallbacks(*this); connection_->addReadFilter(Network::ReadFilterSharedPtr{new CodecReadFilter(*this)}); @@ -152,6 +157,19 @@ CodecClientProd::CodecClientProd(Type type, Network::ClientConnectionPtr&& conne Http::DEFAULT_MAX_REQUEST_HEADERS_KB, host->cluster().maxResponseHeadersCount()); break; } + case Type::HTTP3: { + // TODO(danzh) this enforce dependency from core code to QUICHE. Is there a + // better way to aoivd such dependency in case QUICHE breaks Envoy build. + // Alternatives: + // 1) move codec creation to Network::Connection instance, in + // QUIC's case, EnvoyQuicClientSession. This is not ideal as + // Network::Connection is not necessart to speak HTTP. + // 2) make codec creation in a static registered factory again. It can be + // only necessary for QUIC and for HTTP2 and HTTP1 just use the existing + // logic. + codec_ = std::make_unique( + dynamic_cast(*connection_), *this); + } } } diff --git a/source/common/http/codec_client.h b/source/common/http/codec_client.h index 66c6c6d6bbc63..ab29c15db85a4 100644 --- a/source/common/http/codec_client.h +++ b/source/common/http/codec_client.h @@ -51,7 +51,7 @@ class CodecClient : Logger::Loggable, /** * Type of HTTP codec to use. */ - enum class Type { HTTP1, HTTP2 }; + enum class Type { HTTP1, HTTP2, HTTP3 }; ~CodecClient() override; diff --git a/source/common/http/conn_manager_config.h b/source/common/http/conn_manager_config.h index 1f25eca3be85a..31f6d819bcb87 100644 --- a/source/common/http/conn_manager_config.h +++ b/source/common/http/conn_manager_config.h @@ -26,6 +26,7 @@ namespace Http { COUNTER(downstream_cx_drain_close) \ COUNTER(downstream_cx_http1_total) \ COUNTER(downstream_cx_http2_total) \ + COUNTER(downstream_cx_http3_total) \ COUNTER(downstream_cx_idle_timeout) \ COUNTER(downstream_cx_overload_disable_keepalive) \ COUNTER(downstream_cx_protocol_error) \ @@ -44,6 +45,7 @@ namespace Http { COUNTER(downstream_rq_completed) \ COUNTER(downstream_rq_http1_total) \ COUNTER(downstream_rq_http2_total) \ + COUNTER(downstream_rq_http3_total) \ COUNTER(downstream_rq_idle_timeout) \ COUNTER(downstream_rq_non_relative_path) \ COUNTER(downstream_rq_overload_close) \ @@ -58,6 +60,7 @@ namespace Http { GAUGE(downstream_cx_active, Accumulate) \ GAUGE(downstream_cx_http1_active, Accumulate) \ GAUGE(downstream_cx_http2_active, Accumulate) \ + GAUGE(downstream_cx_http3_active, Accumulate) \ GAUGE(downstream_cx_rx_bytes_buffered, Accumulate) \ GAUGE(downstream_cx_ssl_active, Accumulate) \ GAUGE(downstream_cx_tx_bytes_buffered, Accumulate) \ diff --git a/source/common/http/conn_manager_impl.cc b/source/common/http/conn_manager_impl.cc index 33354eb6e9219..4ff0c09d481da 100644 --- a/source/common/http/conn_manager_impl.cc +++ b/source/common/http/conn_manager_impl.cc @@ -156,6 +156,8 @@ ConnectionManagerImpl::~ConnectionManagerImpl() { if (codec_) { if (codec_->protocol() == Protocol::Http2) { stats_.named_.downstream_cx_http2_active_.dec(); + } else if (codec_->protocol() == Protocol::Http3) { + stats_.named_.downstream_cx_http3_active_.dec(); } else { stats_.named_.downstream_cx_http1_active_.dec(); } @@ -198,7 +200,7 @@ void ConnectionManagerImpl::doEndStream(ActiveStream& stream) { doDeferredStreamDestroy(stream); } - if (reset_stream && codec_->protocol() != Protocol::Http2) { + if (reset_stream && codec_->protocol() < Protocol::Http2) { drain_state_ = DrainState::Closing; } @@ -207,7 +209,7 @@ void ConnectionManagerImpl::doEndStream(ActiveStream& stream) { // Reading may have been disabled for the non-multiplexing case, so enable it again. // Also be sure to unwind any read-disable done by the prior downstream // connection. - if (drain_state_ != DrainState::Closing && codec_->protocol() != Protocol::Http2) { + if (drain_state_ != DrainState::Closing && codec_->protocol() < Protocol::Http2) { while (!read_callbacks_->connection().readEnabled()) { read_callbacks_->connection().readDisable(false); } @@ -274,11 +276,13 @@ void ConnectionManagerImpl::handleCodecException(const char* error) { Network::FilterStatus ConnectionManagerImpl::onData(Buffer::Instance& data, bool) { if (!codec_) { + // Http3 codec should have been instantiated by now. codec_ = config_.createCodec(read_callbacks_->connection(), data, *this); if (codec_->protocol() == Protocol::Http2) { stats_.named_.downstream_cx_http2_total_.inc(); stats_.named_.downstream_cx_http2_active_.inc(); } else { + ASSERT(codec_->protocol() != Protocol::Http3); stats_.named_.downstream_cx_http1_total_.inc(); stats_.named_.downstream_cx_http1_active_.inc(); } @@ -320,7 +324,7 @@ Network::FilterStatus ConnectionManagerImpl::onData(Buffer::Instance& data, bool // either redispatch if there are no streams and we have more data. If we have a single // complete non-WebSocket stream but have not responded yet we will pause socket reads // to apply back pressure. - if (codec_->protocol() != Protocol::Http2) { + if (codec_->protocol() < Protocol::Http2) { if (read_callbacks_->connection().state() == Network::Connection::State::Open && data.length() > 0 && streams_.empty()) { redispatch = true; @@ -335,6 +339,18 @@ Network::FilterStatus ConnectionManagerImpl::onData(Buffer::Instance& data, bool return Network::FilterStatus::StopIteration; } +Network::FilterStatus ConnectionManagerImpl::onNewConnection() { + if (!read_callbacks_->connection().streamInfo().protocol()) { + return Network::FilterStatus::Continue; + } + // Only QUIC connection's stream_info_ specifies protocol. + Buffer::OwnedImpl dummy; + codec_ = config_.createCodec(read_callbacks_->connection(), dummy, *this); + stats_.named_.downstream_cx_http3_total_.inc(); + stats_.named_.downstream_cx_http3_active_.inc(); + return Network::FilterStatus::StopIteration; +} + void ConnectionManagerImpl::resetAllStreams() { while (!streams_.empty()) { // Mimic a downstream reset in this case. We must also remove callbacks here. Though we are @@ -462,6 +478,8 @@ ConnectionManagerImpl::ActiveStream::ActiveStream(ConnectionManagerImpl& connect connection_manager_.stats_.named_.downstream_rq_active_.inc(); if (connection_manager_.codec_->protocol() == Protocol::Http2) { connection_manager_.stats_.named_.downstream_rq_http2_total_.inc(); + } else if (connection_manager_.codec_->protocol() == Protocol::Http3) { + connection_manager_.stats_.named_.downstream_rq_http3_total_.inc(); } else { connection_manager_.stats_.named_.downstream_rq_http1_total_.inc(); } @@ -765,7 +783,7 @@ void ConnectionManagerImpl::ActiveStream::decodeHeaders(HeaderMapPtr&& headers, // Note: Proxy-Connection is not a standard header, but is supported here // since it is supported by http-parser the underlying parser for http // requests. - if (protocol != Protocol::Http2 && !state_.saw_connection_close_ && + if (protocol < Protocol::Http2 && !state_.saw_connection_close_ && request_headers_->ProxyConnection() && absl::EqualsIgnoreCase(request_headers_->ProxyConnection()->value().getStringView(), Http::Headers::get().ConnectionValues.Close)) { @@ -1445,7 +1463,7 @@ void ConnectionManagerImpl::ActiveStream::encodeHeaders(ActiveStreamEncoderFilte // multiplexing, we should disconnect since we don't want to wait around for the request to // finish. if (!state_.remote_complete_) { - if (connection_manager_.codec_->protocol() != Protocol::Http2) { + if (connection_manager_.codec_->protocol() < Protocol::Http2) { connection_manager_.drain_state_ = DrainState::Closing; } @@ -1453,7 +1471,7 @@ void ConnectionManagerImpl::ActiveStream::encodeHeaders(ActiveStreamEncoderFilte } if (connection_manager_.drain_state_ != DrainState::NotDraining && - connection_manager_.codec_->protocol() != Protocol::Http2) { + connection_manager_.codec_->protocol() < Protocol::Http2) { // If the connection manager is draining send "Connection: Close" on HTTP/1.1 connections. // Do not do this for H2 (which drains via GOAWAY) or Upgrade (as the upgrade // payload is no longer HTTP/1.1) diff --git a/source/common/http/conn_manager_impl.h b/source/common/http/conn_manager_impl.h index 384c18e4caff7..ab1af23a7ddbe 100644 --- a/source/common/http/conn_manager_impl.h +++ b/source/common/http/conn_manager_impl.h @@ -68,7 +68,7 @@ class ConnectionManagerImpl : Logger::Loggable, // Network::ReadFilter Network::FilterStatus onData(Buffer::Instance& data, bool end_stream) override; - Network::FilterStatus onNewConnection() override { return Network::FilterStatus::Continue; } + Network::FilterStatus onNewConnection() override; void initializeReadFilterCallbacks(Network::ReadFilterCallbacks& callbacks) override; // Http::ConnectionCallbacks diff --git a/source/common/http/headers.h b/source/common/http/headers.h index 1bdeb0909014f..c16738d8042d9 100644 --- a/source/common/http/headers.h +++ b/source/common/http/headers.h @@ -273,6 +273,7 @@ class HeaderValues { const std::string Http10String{"HTTP/1.0"}; const std::string Http11String{"HTTP/1.1"}; const std::string Http2String{"HTTP/2"}; + const std::string Http3String{"HTTP/3"}; } ProtocolStrings; struct { diff --git a/source/common/http/utility.cc b/source/common/http/utility.cc index 65a708903745c..e7c800538a943 100644 --- a/source/common/http/utility.cc +++ b/source/common/http/utility.cc @@ -388,6 +388,8 @@ const std::string& Utility::getProtocolString(const Protocol protocol) { return Headers::get().ProtocolStrings.Http11String; case Protocol::Http2: return Headers::get().ProtocolStrings.Http2String; + case Protocol::Http3: + return Headers::get().ProtocolStrings.Http3String; } NOT_REACHED_GCOVR_EXCL_LINE; diff --git a/source/extensions/access_loggers/grpc/http_grpc_access_log_impl.cc b/source/extensions/access_loggers/grpc/http_grpc_access_log_impl.cc index def967e6089ca..053fae1d92bd5 100644 --- a/source/extensions/access_loggers/grpc/http_grpc_access_log_impl.cc +++ b/source/extensions/access_loggers/grpc/http_grpc_access_log_impl.cc @@ -60,6 +60,9 @@ void HttpGrpcAccessLog::emitLog(const Http::HeaderMap& request_headers, case Http::Protocol::Http2: log_entry.set_protocol_version(envoy::data::accesslog::v2::HTTPAccessLogEntry::HTTP2); break; + case Http::Protocol::Http3: + log_entry.set_protocol_version(envoy::data::accesslog::v2::HTTPAccessLogEntry::HTTP3); + break; } } diff --git a/source/extensions/filters/network/http_connection_manager/config.cc b/source/extensions/filters/network/http_connection_manager/config.cc index aa47daf32a279..3ac21e7a68ee5 100644 --- a/source/extensions/filters/network/http_connection_manager/config.cc +++ b/source/extensions/filters/network/http_connection_manager/config.cc @@ -26,6 +26,8 @@ #include "common/router/scoped_rds.h" #include "common/runtime/runtime_impl.h" +#include "extensions/quic_listeners/quiche/codec_impl.h" + namespace Envoy { namespace Extensions { namespace NetworkFilters { @@ -336,6 +338,9 @@ HttpConnectionManagerConfig::HttpConnectionManagerConfig( case envoy::config::filter::network::http_connection_manager::v2::HttpConnectionManager::HTTP2: codec_type_ = CodecType::HTTP2; break; + case envoy::config::filter::network::http_connection_manager::v2::HttpConnectionManager::HTTP3: + codec_type_ = CodecType::HTTP3; + break; default: NOT_REACHED_GCOVR_EXCL_LINE; } @@ -418,6 +423,12 @@ HttpConnectionManagerConfig::createCodec(Network::Connection& connection, return std::make_unique( connection, callbacks, context_.scope(), http2_settings_, maxRequestHeadersKb(), maxRequestHeadersCount()); + case CodecType::HTTP3: + // TODO(danzh) same as client side. This enforce dependency on QUICHE. Is there a + // better way to aoivd such dependency in case QUICHE breaks this extension. + return std::make_unique( + dynamic_cast(connection), callbacks); + case CodecType::AUTO: return Http::ConnectionManagerUtility::autoCreateCodec( connection, data, callbacks, context_.scope(), http1_settings_, http2_settings_, diff --git a/source/extensions/filters/network/http_connection_manager/config.h b/source/extensions/filters/network/http_connection_manager/config.h index 82dd1a16bb724..6074f818b809c 100644 --- a/source/extensions/filters/network/http_connection_manager/config.h +++ b/source/extensions/filters/network/http_connection_manager/config.h @@ -146,7 +146,7 @@ class HttpConnectionManagerConfig : Logger::Loggable, std::chrono::milliseconds delayedCloseTimeout() const override { return delayed_close_timeout_; } private: - enum class CodecType { HTTP1, HTTP2, AUTO }; + enum class CodecType { HTTP1, HTTP2, HTTP3, AUTO }; void processFilter( const envoy::config::filter::network::http_connection_manager::v2::HttpFilter& proto_config, int i, absl::string_view prefix, FilterFactoriesList& filter_factories, bool& is_terminal); diff --git a/source/extensions/quic_listeners/quiche/BUILD b/source/extensions/quic_listeners/quiche/BUILD index 656ee91314eec..b5898d29b942e 100644 --- a/source/extensions/quic_listeners/quiche/BUILD +++ b/source/extensions/quic_listeners/quiche/BUILD @@ -109,6 +109,7 @@ envoy_cc_library( hdrs = ["codec_impl.h"], tags = ["nofips"], deps = [ + ":envoy_quic_client_session_lib", ":envoy_quic_server_session_lib", "//include/envoy/http:codec_interface", "@com_googlesource_quiche//:quic_core_http_spdy_session_lib", @@ -157,6 +158,30 @@ envoy_cc_library( ], ) +envoy_cc_library( + name = "envoy_quic_client_session_lib", + srcs = [ + "envoy_quic_client_session.cc", + "envoy_quic_client_stream.cc", + ], + hdrs = [ + "envoy_quic_client_session.h", + "envoy_quic_client_stream.h", + ], + tags = ["nofips"], + deps = [ + ":envoy_quic_client_connection_lib", + ":envoy_quic_stream_lib", + ":envoy_quic_utils_lib", + ":quic_filter_manager_connection_lib", + "//source/common/buffer:buffer_lib", + "//source/common/common:assert_lib", + "//source/common/http:header_map_lib", + "//source/extensions/quic_listeners/quiche/platform:quic_platform_mem_slice_storage_impl_lib", + "@com_googlesource_quiche//:quic_core_http_client_lib", + ], +) + envoy_cc_library( name = "quic_io_handle_wrapper_lib", hdrs = ["quic_io_handle_wrapper.h"], @@ -192,6 +217,19 @@ envoy_cc_library( ], ) +envoy_cc_library( + name = "envoy_quic_client_connection_lib", + srcs = ["envoy_quic_client_connection.cc"], + hdrs = ["envoy_quic_client_connection.h"], + tags = ["nofips"], + deps = [ + ":envoy_quic_connection_lib", + ":envoy_quic_packet_writer_lib", + "//include/envoy/event:dispatcher_interface", + "//source/common/network:socket_option_factory_lib", + ], +) + envoy_cc_library( name = "envoy_quic_dispatcher_lib", srcs = ["envoy_quic_dispatcher.cc"], diff --git a/source/extensions/quic_listeners/quiche/active_quic_listener.cc b/source/extensions/quic_listeners/quiche/active_quic_listener.cc index 15d1b700e0b44..af6d92fad2a17 100644 --- a/source/extensions/quic_listeners/quiche/active_quic_listener.cc +++ b/source/extensions/quic_listeners/quiche/active_quic_listener.cc @@ -43,6 +43,8 @@ ActiveQuicListener::ActiveQuicListener(Event::Dispatcher& dispatcher, quic::QuicRandom::GetInstance(), std::make_unique(), quic::KeyExchangeSource::Default()); auto connection_helper = std::make_unique(dispatcher_); + crypto_config_->AddDefaultConfig(random, connection_helper->GetClock(), + quic::QuicCryptoServerConfig::ConfigOptions()); auto alarm_factory = std::make_unique(dispatcher_, *connection_helper->GetClock()); quic_dispatcher_ = std::make_unique( @@ -52,6 +54,8 @@ ActiveQuicListener::ActiveQuicListener(Event::Dispatcher& dispatcher, quic_dispatcher_->InitializeWithWriter(writer.release()); } +ActiveQuicListener::~ActiveQuicListener() { onListenerShutdown(); } + void ActiveQuicListener::onListenerShutdown() { ENVOY_LOG(info, "Quic listener {} shutdown.", config_.name()); quic_dispatcher_->Shutdown(); @@ -63,7 +67,7 @@ void ActiveQuicListener::onData(Network::UdpRecvData& data) { envoyAddressInstanceToQuicSocketAddress(data.local_address_)); quic::QuicTime timestamp = quic::QuicTime::Zero() + - quic::QuicTime::Delta::FromMilliseconds(std::chrono::duration_cast( + quic::QuicTime::Delta::FromMicroseconds(std::chrono::duration_cast( data.receive_time_.time_since_epoch()) .count()); uint64_t num_slice = data.buffer_->getRawSlices(nullptr, 0); diff --git a/source/extensions/quic_listeners/quiche/active_quic_listener.h b/source/extensions/quic_listeners/quiche/active_quic_listener.h index 89ef57d83727d..d724790bd5299 100644 --- a/source/extensions/quic_listeners/quiche/active_quic_listener.h +++ b/source/extensions/quic_listeners/quiche/active_quic_listener.h @@ -32,6 +32,8 @@ class ActiveQuicListener : public Network::UdpListenerCallbacks, Network::UdpListenerPtr&& listener, Network::ListenerConfig& listener_config, const quic::QuicConfig& quic_config); + ~ActiveQuicListener() override; + // TODO(#7465): Make this a callback. void onListenerShutdown(); diff --git a/source/extensions/quic_listeners/quiche/codec_impl.cc b/source/extensions/quic_listeners/quiche/codec_impl.cc index bad608ed8a281..4d00c6a8d679f 100644 --- a/source/extensions/quic_listeners/quiche/codec_impl.cc +++ b/source/extensions/quic_listeners/quiche/codec_impl.cc @@ -1,5 +1,6 @@ #include "extensions/quic_listeners/quiche/codec_impl.h" +#include "extensions/quic_listeners/quiche/envoy_quic_client_stream.h" #include "extensions/quic_listeners/quiche/envoy_quic_server_stream.h" namespace Envoy { @@ -33,5 +34,35 @@ void QuicHttpServerConnectionImpl::goAway() { quic_server_session_.SendGoAway(quic::QUIC_PEER_GOING_AWAY, "server shutdown imminent"); } +QuicHttpClientConnectionImpl::QuicHttpClientConnectionImpl(EnvoyQuicClientSession& session, + Http::ConnectionCallbacks& callbacks) + : QuicHttpConnectionImplBase(session), quic_client_session_(session) { + session.setHttpConnectionCallbacks(callbacks); +} + +Http::StreamEncoder& +QuicHttpClientConnectionImpl::newStream(Http::StreamDecoder& response_decoder) { + auto stream = dynamic_cast( + quic_client_session_.CreateOutgoingBidirectionalStream()); + stream->setDecoder(response_decoder); + return *stream; +} + +void QuicHttpClientConnectionImpl::onUnderlyingConnectionAboveWriteBufferHighWatermark() { + for (auto& it : quic_client_session_.stream_map()) { + if (!it.second->is_static()) { + dynamic_cast(it.second.get())->runHighWatermarkCallbacks(); + } + } +} + +void QuicHttpClientConnectionImpl::onUnderlyingConnectionBelowWriteBufferLowWatermark() { + for (const auto& it : quic_client_session_.stream_map()) { + if (!it.second->is_static()) { + dynamic_cast(it.second.get())->runLowWatermarkCallbacks(); + } + } +} + } // namespace Quic } // namespace Envoy diff --git a/source/extensions/quic_listeners/quiche/codec_impl.h b/source/extensions/quic_listeners/quiche/codec_impl.h index 9b3d5e08a62b8..141d73220d98a 100644 --- a/source/extensions/quic_listeners/quiche/codec_impl.h +++ b/source/extensions/quic_listeners/quiche/codec_impl.h @@ -3,6 +3,7 @@ #include "common/common/assert.h" #include "common/common/logger.h" +#include "extensions/quic_listeners/quiche/envoy_quic_client_session.h" #include "extensions/quic_listeners/quiche/envoy_quic_server_session.h" namespace Envoy { @@ -21,7 +22,7 @@ class QuicHttpConnectionImplBase : public virtual Http::Connection, // Bypassed. QUIC connection already hands all data to streams. NOT_REACHED_GCOVR_EXCL_LINE; } - Http::Protocol protocol() override { return Http::Protocol::Http2; } + Http::Protocol protocol() override { return Http::Protocol::Http3; } // Returns true if the session has data to send but queued in connection or // stream send buffer. @@ -50,5 +51,24 @@ class QuicHttpServerConnectionImpl : public QuicHttpConnectionImplBase, EnvoyQuicServerSession& quic_server_session_; }; +class QuicHttpClientConnectionImpl : public QuicHttpConnectionImplBase, + public Http::ClientConnection { +public: + QuicHttpClientConnectionImpl(EnvoyQuicClientSession& session, + Http::ConnectionCallbacks& callbacks); + + // Http::ClientConnection + Http::StreamEncoder& newStream(Http::StreamDecoder& response_decoder) override; + + // Http::Connection + void goAway() override { NOT_REACHED_GCOVR_EXCL_LINE; } + void shutdownNotice() override { NOT_REACHED_GCOVR_EXCL_LINE; } + void onUnderlyingConnectionAboveWriteBufferHighWatermark() override; + void onUnderlyingConnectionBelowWriteBufferLowWatermark() override; + +private: + EnvoyQuicClientSession& quic_client_session_; +}; + } // namespace Quic } // namespace Envoy diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_alarm.h b/source/extensions/quic_listeners/quiche/envoy_quic_alarm.h index 4152f4c101c3f..22010987c4def 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_alarm.h +++ b/source/extensions/quic_listeners/quiche/envoy_quic_alarm.h @@ -20,7 +20,11 @@ class EnvoyQuicAlarm : public quic::QuicAlarm { EnvoyQuicAlarm(Event::Dispatcher& dispatcher, const quic::QuicClock& clock, quic::QuicArenaScopedPtr delegate); - ~EnvoyQuicAlarm() override { ASSERT(!IsSet()); }; + ~EnvoyQuicAlarm() override { + if (IsSet()) { + Cancel(); + } + }; // quic::QuicAlarm void CancelImpl() override; diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_client_connection.cc b/source/extensions/quic_listeners/quiche/envoy_quic_client_connection.cc new file mode 100644 index 0000000000000..9a6dfb01028cf --- /dev/null +++ b/source/extensions/quic_listeners/quiche/envoy_quic_client_connection.cc @@ -0,0 +1,176 @@ +#include "extensions/quic_listeners/quiche/envoy_quic_client_connection.h" + +#include "common/network/listen_socket_impl.h" +#include "common/network/socket_option_factory.h" + +#include "extensions/quic_listeners/quiche/envoy_quic_packet_writer.h" +#include "extensions/quic_listeners/quiche/envoy_quic_utils.h" +#include "extensions/transport_sockets/well_known_names.h" + +namespace Envoy { +namespace Quic { + +EnvoyQuicClientConnection::EnvoyQuicClientConnection( + const quic::QuicConnectionId& server_connection_id, + Network::Address::InstanceConstSharedPtr& initial_peer_address, + quic::QuicConnectionHelperInterface& helper, quic::QuicAlarmFactory& alarm_factory, + const quic::ParsedQuicVersionVector& supported_versions, + Network::Address::InstanceConstSharedPtr local_addr, Event::Dispatcher& dispatcher, + const Network::ConnectionSocket::OptionsSharedPtr& options) + : EnvoyQuicClientConnection(server_connection_id, helper, alarm_factory, supported_versions, + dispatcher, + createConnectionSocket(initial_peer_address, local_addr, options)) { +} + +EnvoyQuicClientConnection::EnvoyQuicClientConnection( + const quic::QuicConnectionId& server_connection_id, quic::QuicConnectionHelperInterface& helper, + quic::QuicAlarmFactory& alarm_factory, const quic::ParsedQuicVersionVector& supported_versions, + Event::Dispatcher& dispatcher, Network::ConnectionSocketPtr&& connection_socket) + : EnvoyQuicClientConnection(server_connection_id, helper, alarm_factory, + new EnvoyQuicPacketWriter(*connection_socket), true, + supported_versions, dispatcher, std::move(connection_socket)) {} + +EnvoyQuicClientConnection::EnvoyQuicClientConnection( + const quic::QuicConnectionId& server_connection_id, quic::QuicConnectionHelperInterface& helper, + quic::QuicAlarmFactory& alarm_factory, quic::QuicPacketWriter* writer, bool owns_writer, + const quic::ParsedQuicVersionVector& supported_versions, Event::Dispatcher& dispatcher, + Network::ConnectionSocketPtr&& connection_socket) + : EnvoyQuicConnection( + server_connection_id, + envoyAddressInstanceToQuicSocketAddress(connection_socket->remoteAddress()), helper, + alarm_factory, writer, owns_writer, quic::Perspective::IS_CLIENT, supported_versions, + std::move(connection_socket)), + dispatcher_(dispatcher) {} + +EnvoyQuicClientConnection::~EnvoyQuicClientConnection() { file_event_->setEnabled(0); } + +void EnvoyQuicClientConnection::processPacket( + Network::Address::InstanceConstSharedPtr local_address, + Network::Address::InstanceConstSharedPtr peer_address, Buffer::InstancePtr buffer, + MonotonicTime receive_time) { + quic::QuicTime timestamp = + quic::QuicTime::Zero() + + quic::QuicTime::Delta::FromMicroseconds( + std::chrono::duration_cast(receive_time.time_since_epoch()) + .count()); + uint64_t num_slice = buffer->getRawSlices(nullptr, 0); + ASSERT(num_slice == 1); + Buffer::RawSlice slice; + buffer->getRawSlices(&slice, 1); + quic::QuicReceivedPacket packet(reinterpret_cast(slice.mem_), slice.len_, timestamp, + /*owns_buffer=*/false, /*ttl=*/0, /*ttl_valid=*/true, + /*packet_headers=*/nullptr, /*headers_length=*/0, + /*owns_header_buffer*/ false); + ProcessUdpPacket(envoyAddressInstanceToQuicSocketAddress(local_address), + envoyAddressInstanceToQuicSocketAddress(peer_address), packet); +} + +uint64_t EnvoyQuicClientConnection::maxPacketSize() const { + // TODO(danzh) make this variable configurable to support jumbo frames. + return Network::MAX_UDP_PACKET_SIZE; +} + +void EnvoyQuicClientConnection::setUpConnectionSocket() { + if (connectionSocket()->ioHandle().isOpen()) { + file_event_ = dispatcher_.createFileEvent( + connectionSocket()->ioHandle().fd(), + [this](uint32_t events) -> void { onFileEvent(events); }, Event::FileTriggerType::Edge, + Event::FileReadyType::Read | Event::FileReadyType::Write); + + if (!Network::Socket::applyOptions(connectionSocket()->options(), *connectionSocket(), + envoy::api::v2::core::SocketOption::STATE_LISTENING)) { + ENVOY_LOG_MISC(error, "Fail to apply listening options"); + connectionSocket()->close(); + } + } + if (!connectionSocket()->ioHandle().isOpen()) { + CloseConnection(quic::QUIC_CONNECTION_CANCELLED, "Fail to setup connection socket.", + quic::ConnectionCloseBehavior::SILENT_CLOSE); + } +} + +void EnvoyQuicClientConnection::onFileEvent(uint32_t events) { + ENVOY_CONN_LOG(trace, "socket event: {}", *this, events); + ASSERT(events & (Event::FileReadyType::Read | Event::FileReadyType::Write)); + + if (events & Event::FileReadyType::Write) { + OnCanWrite(); + } + + // It's possible for a write event callback to close the connection, in such case ignore read + // event processing. + if (connected() && (events & Event::FileReadyType::Read)) { + uint32_t old_packets_dropped = packets_dropped_; + while (connected()) { + // Read till socket is drained. + // TODO(danzh): limit read times here. + MonotonicTime receive_time = dispatcher_.timeSource().monotonicTime(); + Api::IoCallUint64Result result = Network::Utility::readFromSocket( + *connectionSocket(), *this, receive_time, &packets_dropped_); + if (!result.ok()) { + if (result.err_->getErrorCode() != Api::IoError::IoErrorCode::Again) { + ENVOY_CONN_LOG(error, "recvmsg result {}: {}", *this, + static_cast(result.err_->getErrorCode()), + result.err_->getErrorDetails()); + } + // Stop reading. + break; + } + + if (result.rc_ == 0) { + // TODO(conqerAtapple): Is zero length packet interesting? If so add stats + // for it. Otherwise remove the warning log below. + ENVOY_CONN_LOG(trace, "received 0-length packet", *this); + } + + if (packets_dropped_ != old_packets_dropped) { + // The kernel tracks SO_RXQ_OVFL as a uint32 which can overflow to a smaller + // value. So as long as this count differs from previously recorded value, + // more packets are dropped by kernel. + uint32_t delta = (packets_dropped_ > old_packets_dropped) + ? (packets_dropped_ - old_packets_dropped) + : (packets_dropped_ + + (std::numeric_limits::max() - old_packets_dropped) + 1); + // TODO(danzh) add stats for this. + ENVOY_CONN_LOG(debug, + "Kernel dropped {} more packets. Consider increase receive buffer size.", + *this, delta); + } + } + } +} + +Network::ConnectionSocketPtr EnvoyQuicClientConnection::createConnectionSocket( + Network::Address::InstanceConstSharedPtr& peer_addr, + Network::Address::InstanceConstSharedPtr& local_addr, + const Network::ConnectionSocket::OptionsSharedPtr& options) { + Network::IoHandlePtr io_handle = peer_addr->socket(Network::Address::SocketType::Datagram); + auto connection_socket = + std::make_unique(std::move(io_handle), local_addr, peer_addr); + connection_socket->addOptions(Network::SocketOptionFactory::buildIpPacketInfoOptions()); + connection_socket->addOptions(Network::SocketOptionFactory::buildRxQueueOverFlowOptions()); + if (options != nullptr) { + connection_socket->addOptions(options); + } + if (!Network::Socket::applyOptions(connection_socket->options(), *connection_socket, + envoy::api::v2::core::SocketOption::STATE_PREBIND)) { + connection_socket->close(); + ENVOY_LOG_MISC(error, "Fail to apply pre-bind options"); + return connection_socket; + } + local_addr->bind(connection_socket->ioHandle().fd()); + ASSERT(local_addr->ip()); + if (local_addr->ip()->port() == 0) { + // Get ephemeral port number. + local_addr = Network::Address::addressFromFd(connection_socket->ioHandle().fd()); + } + if (!Network::Socket::applyOptions(connection_socket->options(), *connection_socket, + envoy::api::v2::core::SocketOption::STATE_BOUND)) { + ENVOY_LOG_MISC(error, "Fail to apply post-bind options"); + connection_socket->close(); + } + return connection_socket; +} + +} // namespace Quic +} // namespace Envoy diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_client_connection.h b/source/extensions/quic_listeners/quiche/envoy_quic_client_connection.h new file mode 100644 index 0000000000000..f9d79af6acca8 --- /dev/null +++ b/source/extensions/quic_listeners/quiche/envoy_quic_client_connection.h @@ -0,0 +1,66 @@ +#pragma once + +#include "envoy/event/dispatcher.h" + +#include "common/network/utility.h" + +#include "extensions/quic_listeners/quiche/envoy_quic_connection.h" + +namespace Envoy { +namespace Quic { + +// A client QuicConnection instance manages its own I/O events. +class EnvoyQuicClientConnection : public EnvoyQuicConnection, public Network::UdpPacketProcessor { +public: + // A connection socket will be created with given |local_addr|. If binding + // port not provided in |local_addr|, pick up a random port. + EnvoyQuicClientConnection(const quic::QuicConnectionId& server_connection_id, + Network::Address::InstanceConstSharedPtr& initial_peer_address, + quic::QuicConnectionHelperInterface& helper, + quic::QuicAlarmFactory& alarm_factory, + const quic::ParsedQuicVersionVector& supported_versions, + Network::Address::InstanceConstSharedPtr local_addr, + Event::Dispatcher& dispatcher, + const Network::ConnectionSocket::OptionsSharedPtr& options); + + // Overridden to un-register all file events. + ~EnvoyQuicClientConnection() override; + + void processPacket(Network::Address::InstanceConstSharedPtr local_address, + Network::Address::InstanceConstSharedPtr peer_address, + Buffer::InstancePtr buffer, MonotonicTime receive_time) override; + + uint64_t maxPacketSize() const override; + + // Register file event and apply socket options. + void setUpConnectionSocket(); + +private: + EnvoyQuicClientConnection(const quic::QuicConnectionId& server_connection_id, + quic::QuicConnectionHelperInterface& helper, + quic::QuicAlarmFactory& alarm_factory, + const quic::ParsedQuicVersionVector& supported_versions, + Event::Dispatcher& dispatcher, + Network::ConnectionSocketPtr&& connection_socket); + + EnvoyQuicClientConnection(const quic::QuicConnectionId& server_connection_id, + quic::QuicConnectionHelperInterface& helper, + quic::QuicAlarmFactory& alarm_factory, quic::QuicPacketWriter* writer, + bool owns_writer, + const quic::ParsedQuicVersionVector& supported_versions, + Event::Dispatcher& dispatcher, + Network::ConnectionSocketPtr&& connection_socket); + + Network::ConnectionSocketPtr + createConnectionSocket(Network::Address::InstanceConstSharedPtr& peer_addr, + Network::Address::InstanceConstSharedPtr& local_addr, + const Network::ConnectionSocket::OptionsSharedPtr& options); + + void onFileEvent(uint32_t events); + uint32_t packets_dropped_{0}; + Event::Dispatcher& dispatcher_; + Event::FileEventPtr file_event_; +}; + +} // namespace Quic +} // namespace Envoy diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_client_session.cc b/source/extensions/quic_listeners/quiche/envoy_quic_client_session.cc new file mode 100644 index 0000000000000..f9aeb81df3230 --- /dev/null +++ b/source/extensions/quic_listeners/quiche/envoy_quic_client_session.cc @@ -0,0 +1,87 @@ +#include "extensions/quic_listeners/quiche/envoy_quic_client_session.h" + +namespace Envoy { +namespace Quic { + +EnvoyQuicClientSession::EnvoyQuicClientSession( + const quic::QuicConfig& config, const quic::ParsedQuicVersionVector& supported_versions, + std::unique_ptr connection, const quic::QuicServerId& server_id, + quic::QuicCryptoClientConfig* crypto_config, + quic::QuicClientPushPromiseIndex* push_promise_index, Event::Dispatcher& dispatcher, + uint32_t send_buffer_limit) + : QuicFilterManagerConnectionImpl(*connection, dispatcher, send_buffer_limit), + quic::QuicSpdyClientSession(config, supported_versions, connection.release(), server_id, + crypto_config, push_promise_index) { + Initialize(); +} + +EnvoyQuicClientSession::~EnvoyQuicClientSession() { + ASSERT(!connection()->connected()); + QuicFilterManagerConnectionImpl::quic_connection_ = nullptr; +} + +absl::string_view EnvoyQuicClientSession::requestedServerName() const { + return {GetCryptoStream()->crypto_negotiated_params().sni}; +} + +void EnvoyQuicClientSession::connect() { + dynamic_cast(quic_connection_)->setUpConnectionSocket(); +} + +void EnvoyQuicClientSession::OnConnectionClosed(const quic::QuicConnectionCloseFrame& frame, + quic::ConnectionCloseSource source) { + quic::QuicSpdyClientSession::OnConnectionClosed(frame, source); + onConnectionCloseEvent(frame, source); +} + +void EnvoyQuicClientSession::Initialize() { + quic::QuicSpdyClientSession::Initialize(); + quic_connection_->setEnvoyConnection(*this); +} + +void EnvoyQuicClientSession::OnGoAway(const quic::QuicGoAwayFrame& frame) { + ENVOY_CONN_LOG(debug, "GOAWAY received with error {}: {}", *this, + quic::QuicErrorCodeToString(frame.error_code), frame.reason_phrase); + quic::QuicSpdyClientSession::OnGoAway(frame); + if (http_connection_callbacks_ != nullptr) { + http_connection_callbacks_->onGoAway(); + } +} + +void EnvoyQuicClientSession::OnCryptoHandshakeEvent(CryptoHandshakeEvent event) { + quic::QuicSpdyClientSession::OnCryptoHandshakeEvent(event); + if (event == HANDSHAKE_CONFIRMED) { + raiseEvent(Network::ConnectionEvent::Connected); + } +} + +void EnvoyQuicClientSession::cryptoConnect() { + CryptoConnect(); + set_max_allowed_push_id(0u); + // Wait for finishing handshake with server. + dispatcher_.run(Event::Dispatcher::RunType::Block); +} + +std::unique_ptr EnvoyQuicClientSession::CreateClientStream() { + auto stream = std::make_unique(GetNextOutgoingBidirectionalStreamId(), + this, quic::BIDIRECTIONAL); + return stream; +} + +quic::QuicSpdyStream* EnvoyQuicClientSession::CreateIncomingStream(quic::QuicStreamId id) { + if (!ShouldCreateIncomingStream(id)) { + return nullptr; + } + auto stream = new EnvoyQuicClientStream(id, this, quic::READ_UNIDIRECTIONAL); + ActivateStream(std::unique_ptr(stream)); + return stream; +} + +quic::QuicSpdyStream* EnvoyQuicClientSession::CreateIncomingStream(quic::PendingStream* pending) { + auto stream = new EnvoyQuicClientStream(pending, this, quic::READ_UNIDIRECTIONAL); + ActivateStream(std::unique_ptr(stream)); + return stream; +} + +} // namespace Quic +} // namespace Envoy diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_client_session.h b/source/extensions/quic_listeners/quiche/envoy_quic_client_session.h new file mode 100644 index 0000000000000..e5dd9862173db --- /dev/null +++ b/source/extensions/quic_listeners/quiche/envoy_quic_client_session.h @@ -0,0 +1,82 @@ +#pragma once + +#pragma GCC diagnostic push +// QUICHE allows unused parameters. +#pragma GCC diagnostic ignored "-Wunused-parameter" +// QUICHE uses offsetof(). +#pragma GCC diagnostic ignored "-Winvalid-offsetof" +#pragma GCC diagnostic ignored "-Wtype-limits" + +#include "quiche/quic/core/http/quic_spdy_client_session.h" + +#pragma GCC diagnostic pop + +#include "extensions/quic_listeners/quiche/envoy_quic_client_stream.h" +#include "extensions/quic_listeners/quiche/envoy_quic_client_connection.h" +#include "extensions/quic_listeners/quiche/quic_filter_manager_connection_impl.h" + +namespace Envoy { +namespace Quic { + +// Act as a Network::ClientConnection to ClientCodec. +// TODO(danzh) This class doesn't need to inherit Network::FilterManager +// interface but need all other Network::Connection implementation in +// QuicFilterManagerConnectionImpl. Refactor QuicFilterManagerConnectionImpl to +// move FilterManager interface to EnvoyQuicServerSession. +class EnvoyQuicClientSession : public QuicFilterManagerConnectionImpl, + public quic::QuicSpdyClientSession, + public Network::ClientConnection { +public: + EnvoyQuicClientSession(const quic::QuicConfig& config, + const quic::ParsedQuicVersionVector& supported_versions, + std::unique_ptr connection, + const quic::QuicServerId& server_id, + quic::QuicCryptoClientConfig* crypto_config, + quic::QuicClientPushPromiseIndex* push_promise_index, + Event::Dispatcher& dispatcher, uint32_t send_buffer_limit); + + ~EnvoyQuicClientSession() override; + + // Called by QuicHttpClientConnectionImpl before creating data streams. + void setHttpConnectionCallbacks(Http::ConnectionCallbacks& callbacks) { + http_connection_callbacks_ = &callbacks; + } + + // Network::Connection + absl::string_view requestedServerName() const override; + + // Network::ClientConnection + // Only register socket and set socket options. + void connect() override; + + // quic::QuicSession + void OnConnectionClosed(const quic::QuicConnectionCloseFrame& frame, + quic::ConnectionCloseSource source) override; + void Initialize() override; + void OnGoAway(const quic::QuicGoAwayFrame& frame) override; + // quic::QuicSpdyClientSessionBase + void OnCryptoHandshakeEvent(CryptoHandshakeEvent event) override; + + // Do version negotiation and crypto handshake. Fail the connection if server + // doesn't support the one and only supported version. + // This call will block till the handshake finished with either success to + // failure. + void cryptoConnect(); + + using quic::QuicSpdyClientSession::stream_map; + +protected: + // quic::QuicSpdyClientSession + std::unique_ptr CreateClientStream() override; + // quic::QuicSpdySession + quic::QuicSpdyStream* CreateIncomingStream(quic::QuicStreamId id) override; + quic::QuicSpdyStream* CreateIncomingStream(quic::PendingStream* pending) override; + +private: + // These callbacks are owned by network filters and quic session should out live + // them. + Http::ConnectionCallbacks* http_connection_callbacks_{nullptr}; +}; + +} // namespace Quic +} // namespace Envoy diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_client_stream.cc b/source/extensions/quic_listeners/quiche/envoy_quic_client_stream.cc new file mode 100644 index 0000000000000..3fc24dea4e25c --- /dev/null +++ b/source/extensions/quic_listeners/quiche/envoy_quic_client_stream.cc @@ -0,0 +1,219 @@ +#include "extensions/quic_listeners/quiche/envoy_quic_client_stream.h" + +#pragma GCC diagnostic push +// QUICHE allows unused parameters. +#pragma GCC diagnostic ignored "-Wunused-parameter" +// QUICHE uses offsetof(). +#pragma GCC diagnostic ignored "-Winvalid-offsetof" + +#include "quiche/quic/core/quic_session.h" +#include "quiche/quic/core/http/quic_header_list.h" +#include "quiche/quic/core/quic_session.h" +#include "quiche/spdy/core/spdy_header_block.h" +#include "extensions/quic_listeners/quiche/platform/quic_mem_slice_span_impl.h" + +#pragma GCC diagnostic pop + +#include "extensions/quic_listeners/quiche/envoy_quic_utils.h" +#include "extensions/quic_listeners/quiche/envoy_quic_client_session.h" + +#include "common/buffer/buffer_impl.h" +#include "common/http/header_map_impl.h" +#include "common/common/assert.h" + +namespace Envoy { +namespace Quic { + +EnvoyQuicClientStream::EnvoyQuicClientStream(quic::QuicStreamId id, + quic::QuicSpdyClientSession* client_session, + quic::StreamType type) + : quic::QuicSpdyClientStream(id, client_session, type), + EnvoyQuicStream( + session()->config()->GetInitialStreamFlowControlWindowToSend(), + [this]() { runLowWatermarkCallbacks(); }, [this]() { runHighWatermarkCallbacks(); }) {} + +EnvoyQuicClientStream::EnvoyQuicClientStream(quic::PendingStream* pending, + quic::QuicSpdyClientSession* client_session, + quic::StreamType type) + : quic::QuicSpdyClientStream(pending, client_session, type), + EnvoyQuicStream( + session()->config()->GetInitialStreamFlowControlWindowToSend(), + [this]() { runLowWatermarkCallbacks(); }, [this]() { runHighWatermarkCallbacks(); }) {} + +void EnvoyQuicClientStream::encode100ContinueHeaders(const Http::HeaderMap& headers) { + ASSERT(headers.Status()->value() == "100"); + encodeHeaders(headers, false); +} + +void EnvoyQuicClientStream::encodeHeaders(const Http::HeaderMap& headers, bool end_stream) { + ENVOY_STREAM_LOG(debug, "encodeHeaders: (end_stream={}) {}.", *this, end_stream, headers); + WriteHeaders(envoyHeadersToSpdyHeaderBlock(headers), end_stream, nullptr); + local_end_stream_ = end_stream; +} + +void EnvoyQuicClientStream::encodeData(Buffer::Instance& data, bool end_stream) { + ENVOY_STREAM_LOG(debug, "encodeData (end_stream={}) of {} bytes.", *this, end_stream, + data.length()); + local_end_stream_ = end_stream; + // This is counting not serialized bytes in the send buffer. + uint64_t bytes_to_send_old = BufferedDataBytes(); + // QUIC stream must take all. + WriteBodySlices(quic::QuicMemSliceSpan(quic::QuicMemSliceSpanImpl(data)), end_stream); + ASSERT(data.length() == 0); + + uint64_t bytes_to_send_new = BufferedDataBytes(); + ASSERT(bytes_to_send_old <= bytes_to_send_new); + if (bytes_to_send_new > bytes_to_send_old) { + // If buffered bytes changed, update stream and session's watermark book + // keeping. + sendBufferSimulation().checkHighWatermark(bytes_to_send_new); + dynamic_cast(session())->adjustBytesToSend(bytes_to_send_new - + bytes_to_send_old); + } +} + +void EnvoyQuicClientStream::encodeTrailers(const Http::HeaderMap& trailers) { + ASSERT(!local_end_stream_); + local_end_stream_ = true; + ENVOY_STREAM_LOG(debug, "encodeTrailers: {}.", *this, trailers); + WriteTrailers(envoyHeadersToSpdyHeaderBlock(trailers), nullptr); +} + +void EnvoyQuicClientStream::encodeMetadata(const Http::MetadataMapVector& /*metadata_map_vector*/) { + ASSERT(false, "Metadata Frame is not supported in QUIC"); +} + +void EnvoyQuicClientStream::resetStream(Http::StreamResetReason reason) { + // Higher layers expect calling resetStream() to immediately raise reset callbacks. + runResetCallbacks(reason); + + Reset(envoyResetReasonToQuicRstError(reason)); +} + +void EnvoyQuicClientStream::switchStreamBlockState(bool should_block) { + ASSERT(FinishedReadingHeaders(), + "codec buffer limit is reached before response body is delivered."); + if (should_block) { + sequencer()->SetBlockedUntilFlush(); + } else { + sequencer()->SetUnblocked(); + } +} + +void EnvoyQuicClientStream::OnInitialHeadersComplete(bool fin, size_t frame_len, + const quic::QuicHeaderList& header_list) { + quic::QuicSpdyStream::OnInitialHeadersComplete(fin, frame_len, header_list); + if (rst_sent()) { + return; + } + ASSERT(decoder() != nullptr); + ASSERT(headers_decompressed()); + decoder()->decodeHeaders(quicHeadersToEnvoyHeaders(header_list), /*end_stream=*/fin); + if (fin) { + end_stream_decoded_ = true; + } + ConsumeHeaderList(); +} + +void EnvoyQuicClientStream::OnBodyAvailable() { + ASSERT(FinishedReadingHeaders()); + ASSERT(read_disable_counter_ == 0); + ASSERT(!in_encode_data_callstack_); + in_encode_data_callstack_ = true; + + Buffer::InstancePtr buffer = std::make_unique(); + // TODO(danzh): check Envoy per stream buffer limit. + // Currently read out all the data. + while (HasBytesToRead()) { + struct iovec iov; + int num_regions = GetReadableRegions(&iov, 1); + ASSERT(num_regions > 0); + size_t bytes_read = iov.iov_len; + Buffer::RawSlice slice; + buffer->reserve(bytes_read, &slice, 1); + ASSERT(slice.len_ >= bytes_read); + slice.len_ = bytes_read; + memcpy(slice.mem_, iov.iov_base, iov.iov_len); + buffer->commit(&slice, 1); + MarkConsumed(bytes_read); + } + + // True if no trailer and FIN read. + bool finished_reading = IsDoneReading(); + bool empty_payload_with_fin = buffer->length() == 0 && finished_reading; + if (!empty_payload_with_fin || !end_stream_decoded_) { + ASSERT(decoder() != nullptr); + decoder()->decodeData(*buffer, finished_reading); + if (finished_reading) { + end_stream_decoded_ = true; + } + } + + if (!sequencer()->IsClosed()) { + in_encode_data_callstack_ = false; + if (read_disable_counter_ > 0) { + // If readDisable() was ever called during decodeData() and it meant to disable + // reading from downstream, the call must have been deferred. Call it now. + switchStreamBlockState(true); + } + return; + } + + if (!quic::VersionUsesQpack(transport_version()) && !FinishedReadingTrailers()) { + // For Google QUIC implementation, trailers may arrived earlier and wait to + // be consumed after reading all the body. Consume it here. + // IETF QUIC shouldn't reach here because trailers are sent on same stream. + decoder()->decodeTrailers(spdyHeaderBlockToEnvoyHeaders(received_trailers())); + MarkTrailersConsumed(); + } + OnFinRead(); + in_encode_data_callstack_ = false; +} + +void EnvoyQuicClientStream::OnTrailingHeadersComplete(bool fin, size_t frame_len, + const quic::QuicHeaderList& header_list) { + quic::QuicSpdyStream::OnTrailingHeadersComplete(fin, frame_len, header_list); + if (session()->connection()->connected() && + (quic::VersionUsesQpack(transport_version()) || sequencer()->IsClosed()) && + !FinishedReadingTrailers()) { + // Before QPack trailers can arrive before body. Only decode trailers after finishing decoding + // body. + ASSERT(decoder() != nullptr); + decoder()->decodeTrailers(spdyHeaderBlockToEnvoyHeaders(received_trailers())); + MarkTrailersConsumed(); + } +} + +void EnvoyQuicClientStream::OnStreamReset(const quic::QuicRstStreamFrame& frame) { + quic::QuicSpdyClientStream::OnStreamReset(frame); + runResetCallbacks(quicRstErrorToEnvoyResetReason(frame.error_code)); +} + +void EnvoyQuicClientStream::OnConnectionClosed(quic::QuicErrorCode error, + quic::ConnectionCloseSource source) { + quic::QuicSpdyClientStream::OnConnectionClosed(error, source); + runResetCallbacks(quicErrorCodeToEnvoyResetReason(error)); +} + +void EnvoyQuicClientStream::OnCanWrite() { + uint64_t buffered_data_old = BufferedDataBytes(); + quic::QuicSpdyClientStream::OnCanWrite(); + uint64_t buffered_data_new = BufferedDataBytes(); + // As long as OnCanWriteNewData() is no-op, data to sent in buffer shouldn't + // increase. + ASSERT(buffered_data_new <= buffered_data_old); + if (buffered_data_new < buffered_data_old) { + sendBufferSimulation().checkLowWatermark(buffered_data_new); + dynamic_cast(session())->adjustBytesToSend(buffered_data_new - + buffered_data_old); + } +} + +uint32_t EnvoyQuicClientStream::streamId() { return id(); } + +Network::Connection* EnvoyQuicClientStream::connection() { + return dynamic_cast(session()); +} + +} // namespace Quic +} // namespace Envoy diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_client_stream.h b/source/extensions/quic_listeners/quiche/envoy_quic_client_stream.h new file mode 100644 index 0000000000000..6bc88ff8a9e81 --- /dev/null +++ b/source/extensions/quic_listeners/quiche/envoy_quic_client_stream.h @@ -0,0 +1,56 @@ +#pragma once + +#pragma GCC diagnostic push +// QUICHE allows unused parameters. +#pragma GCC diagnostic ignored "-Wunused-parameter" +// QUICHE uses offsetof(). +#pragma GCC diagnostic ignored "-Winvalid-offsetof" +#include "quiche/quic/core/http/quic_spdy_client_stream.h" + +#pragma GCC diagnostic pop + +#include "extensions/quic_listeners/quiche/envoy_quic_stream.h" + +namespace Envoy { +namespace Quic { + +// This class is a quic stream and also a request encoder. +class EnvoyQuicClientStream : public quic::QuicSpdyClientStream, public EnvoyQuicStream { +public: + EnvoyQuicClientStream(quic::QuicStreamId id, quic::QuicSpdyClientSession* client_session, + quic::StreamType type); + EnvoyQuicClientStream(quic::PendingStream* pending, quic::QuicSpdyClientSession* client_session, + quic::StreamType type); + + // Http::StreamEncoder + void encode100ContinueHeaders(const Http::HeaderMap& headers) override; + void encodeHeaders(const Http::HeaderMap& headers, bool end_stream) override; + void encodeData(Buffer::Instance& data, bool end_stream) override; + void encodeTrailers(const Http::HeaderMap& trailers) override; + void encodeMetadata(const Http::MetadataMapVector& metadata_map_vector) override; + + // Http::Stream + void resetStream(Http::StreamResetReason reason) override; + // quic::QuicSpdyStream + void OnBodyAvailable() override; + void OnStreamReset(const quic::QuicRstStreamFrame& frame) override; + void OnCanWrite() override; + // quic::Stream + void OnConnectionClosed(quic::QuicErrorCode error, quic::ConnectionCloseSource source) override; + +protected: + // EnvoyQuicStream + void switchStreamBlockState(bool should_block) override; + uint32_t streamId() override; + Network::Connection* connection() override; + + // quic::QuicSpdyStream + // Overridden to pass headers to decoder. + void OnInitialHeadersComplete(bool fin, size_t frame_len, + const quic::QuicHeaderList& header_list) override; + void OnTrailingHeadersComplete(bool fin, size_t frame_len, + const quic::QuicHeaderList& header_list) override; +}; + +} // namespace Quic +} // namespace Envoy diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_connection.cc b/source/extensions/quic_listeners/quiche/envoy_quic_connection.cc index dcc311a6eaac6..f2459bf79a190 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_connection.cc +++ b/source/extensions/quic_listeners/quiche/envoy_quic_connection.cc @@ -19,7 +19,10 @@ EnvoyQuicConnection::EnvoyQuicConnection(const quic::QuicConnectionId& server_co EnvoyQuicConnection::~EnvoyQuicConnection() { connection_socket_->close(); } -uint64_t EnvoyQuicConnection::id() const { return envoy_connection_->id(); } +uint64_t EnvoyQuicConnection::id() const { + ASSERT(envoy_connection_ != nullptr); + return envoy_connection_->id(); +} } // namespace Quic } // namespace Envoy diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_fake_proof_verifier.h b/source/extensions/quic_listeners/quiche/envoy_quic_fake_proof_verifier.h index 0861e09fb4d9b..c8355717bccb8 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_fake_proof_verifier.h +++ b/source/extensions/quic_listeners/quiche/envoy_quic_fake_proof_verifier.h @@ -48,7 +48,8 @@ class EnvoyQuicFakeProofVerifier : public quic::ProofVerifier { const quic::ProofVerifyContext* /*context*/, std::string* /*error_details*/, std::unique_ptr* /*details*/, std::unique_ptr /*callback*/) override { - if (cert_sct == "Fake timestamp" && certs.size() == 1 && certs[0] == "Fake cert") { + // Cert SCT support is not enabled for fake ProofSource. + if (cert_sct == "" && certs.size() == 1 && certs[0] == "Fake cert") { return quic::QUIC_SUCCESS; } return quic::QUIC_FAILURE; diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_server_session.cc b/source/extensions/quic_listeners/quiche/envoy_quic_server_session.cc index 1d48b93bf3149..456cc77da297e 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_server_session.cc +++ b/source/extensions/quic_listeners/quiche/envoy_quic_server_session.cc @@ -23,9 +23,11 @@ EnvoyQuicServerSession::EnvoyQuicServerSession( uint32_t send_buffer_limit) : quic::QuicServerSessionBase(config, supported_versions, connection.get(), visitor, helper, crypto_config, compressed_certs_cache), - QuicFilterManagerConnectionImpl(std::move(connection), dispatcher, send_buffer_limit) {} + QuicFilterManagerConnectionImpl(*connection, dispatcher, send_buffer_limit), + quic_connection_(std::move(connection)) {} EnvoyQuicServerSession::~EnvoyQuicServerSession() { + ASSERT(!quic_connection_->connected()); QuicFilterManagerConnectionImpl::quic_connection_ = nullptr; } diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_server_session.h b/source/extensions/quic_listeners/quiche/envoy_quic_server_session.h index ff5d254a23c44..5c5561cbb7f52 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_server_session.h +++ b/source/extensions/quic_listeners/quiche/envoy_quic_server_session.h @@ -68,6 +68,7 @@ class EnvoyQuicServerSession : public quic::QuicServerSessionBase, private: void setUpRequestDecoder(EnvoyQuicStream& stream); + std::unique_ptr quic_connection_; // These callbacks are owned by network filters and quic session should out live // them. Http::ServerConnectionCallbacks* http_connection_callbacks_{nullptr}; diff --git a/source/extensions/quic_listeners/quiche/quic_filter_manager_connection_impl.cc b/source/extensions/quic_listeners/quiche/quic_filter_manager_connection_impl.cc index 3ea596f3a4542..4baaed3a2c8dd 100644 --- a/source/extensions/quic_listeners/quiche/quic_filter_manager_connection_impl.cc +++ b/source/extensions/quic_listeners/quiche/quic_filter_manager_connection_impl.cc @@ -5,17 +5,17 @@ namespace Envoy { namespace Quic { -QuicFilterManagerConnectionImpl::QuicFilterManagerConnectionImpl(std::unique_ptr connection, +QuicFilterManagerConnectionImpl::QuicFilterManagerConnectionImpl(EnvoyQuicConnection& connection, Event::Dispatcher& dispatcher, uint32_t send_buffer_limit) - : quic_connection_(std::move(connection)), dispatcher_(dispatcher), filter_manager_(*this), + : quic_connection_(&connection), dispatcher_(dispatcher), filter_manager_(*this), // QUIC connection id can be 18 bytes. It's easier to use hash value instead // of trying to map it into a 64-bit space. stream_info_(dispatcher.timeSource()), id_(quic_connection_->connection_id().Hash()), write_buffer_watermark_simulation_( send_buffer_limit / 2, send_buffer_limit, [this]() { onSendBufferLowWatermark(); }, [this]() { onSendBufferHighWatermark(); }, ENVOY_LOGGER()) { - stream_info_.protocol(Http::Protocol::Http2); + stream_info_.protocol(Http::Protocol::Http3); } void QuicFilterManagerConnectionImpl::addWriteFilter(Network::WriteFilterSharedPtr filter) { diff --git a/source/extensions/quic_listeners/quiche/quic_filter_manager_connection_impl.h b/source/extensions/quic_listeners/quiche/quic_filter_manager_connection_impl.h index ace95927c9e79..a72364d334e53 100644 --- a/source/extensions/quic_listeners/quiche/quic_filter_manager_connection_impl.h +++ b/source/extensions/quic_listeners/quiche/quic_filter_manager_connection_impl.h @@ -18,7 +18,7 @@ namespace Quic { class QuicFilterManagerConnectionImpl : public Network::FilterManagerConnection, protected Logger::Loggable { public: - QuicFilterManagerConnectionImpl(std::unique_ptr connection, Event::Dispatcher& dispatcher, + QuicFilterManagerConnectionImpl(EnvoyQuicConnection& connection, Event::Dispatcher& dispatcher, uint32_t send_buffer_limit); // Network::FilterManager @@ -106,7 +106,7 @@ class QuicFilterManagerConnectionImpl : public Network::FilterManagerConnection, void raiseEvent(Network::ConnectionEvent event); - std::unique_ptr quic_connection_; + EnvoyQuicConnection* quic_connection_{nullptr}; // TODO(danzh): populate stats. std::unique_ptr stats_; Event::Dispatcher& dispatcher_; diff --git a/test/extensions/quic_listeners/quiche/envoy_quic_proof_source_test.cc b/test/extensions/quic_listeners/quiche/envoy_quic_proof_source_test.cc index 4737a532f558b..57bdf94e9e1f0 100644 --- a/test/extensions/quic_listeners/quiche/envoy_quic_proof_source_test.cc +++ b/test/extensions/quic_listeners/quiche/envoy_quic_proof_source_test.cc @@ -62,8 +62,8 @@ TEST_F(EnvoyQuicFakeProofSourceTest, TestGetProof) { TEST_F(EnvoyQuicFakeProofSourceTest, TestVerifyProof) { EXPECT_EQ(quic::QUIC_SUCCESS, proof_verifier_.VerifyProof(hostname_, /*port=*/0, server_config_, version_, chlo_hash_, - expected_certs_, "Fake timestamp", expected_signature_, - nullptr, nullptr, nullptr, nullptr)); + expected_certs_, "", expected_signature_, nullptr, nullptr, + nullptr, nullptr)); std::vector wrong_certs{"wrong cert"}; EXPECT_EQ(quic::QUIC_FAILURE, proof_verifier_.VerifyProof(hostname_, /*port=*/0, server_config_, version_, chlo_hash_, diff --git a/test/extensions/quic_listeners/quiche/envoy_quic_server_session_test.cc b/test/extensions/quic_listeners/quiche/envoy_quic_server_session_test.cc index dc4fbbe642682..419953ca523f8 100644 --- a/test/extensions/quic_listeners/quiche/envoy_quic_server_session_test.cc +++ b/test/extensions/quic_listeners/quiche/envoy_quic_server_session_test.cc @@ -103,7 +103,7 @@ class EnvoyQuicServerSessionTest : public testing::TestWithParam { bool installReadFilter() { // Setup read filter. envoy_quic_session_.addReadFilter(read_filter_); - EXPECT_EQ(Http::Protocol::Http2, + EXPECT_EQ(Http::Protocol::Http3, read_filter_->callbacks_->connection().streamInfo().protocol().value()); EXPECT_EQ(envoy_quic_session_.id(), read_filter_->callbacks_->connection().id()); EXPECT_EQ(&envoy_quic_session_, &read_filter_->callbacks_->connection()); @@ -115,7 +115,7 @@ class EnvoyQuicServerSessionTest : public testing::TestWithParam { // Create ServerConnection instance and setup callbacks for it. http_connection_ = std::make_unique(envoy_quic_session_, http_connection_callbacks_); - EXPECT_EQ(Http::Protocol::Http2, http_connection_->protocol()); + EXPECT_EQ(Http::Protocol::Http3, http_connection_->protocol()); // Stop iteration to avoid calling getRead/WriteBuffer(). return Network::FilterStatus::StopIteration; })); diff --git a/test/extensions/quic_listeners/quiche/integration/BUILD b/test/extensions/quic_listeners/quiche/integration/BUILD new file mode 100644 index 0000000000000..4e2cde2d9bd8d --- /dev/null +++ b/test/extensions/quic_listeners/quiche/integration/BUILD @@ -0,0 +1,29 @@ +licenses(["notice"]) # Apache 2 + +load( + "//bazel:envoy_build_system.bzl", + "envoy_cc_fuzz_test", + "envoy_cc_test", + "envoy_cc_test_binary", + "envoy_cc_test_library", + "envoy_package", + "envoy_proto_library", +) + +envoy_package() + +envoy_cc_test( + name = "quic_http_integration_test", + srcs = ["quic_http_integration_test.cc"], + data = ["//test/config/integration/certs"], + tags = ["nofips"], + deps = [ + "//source/extensions/quic_listeners/quiche:active_quic_listener_config_lib", + "//source/extensions/quic_listeners/quiche:envoy_quic_client_connection_lib", + "//source/extensions/quic_listeners/quiche:envoy_quic_client_session_lib", + "//source/extensions/quic_listeners/quiche:envoy_quic_connection_helper_lib", + "//source/extensions/quic_listeners/quiche:envoy_quic_proof_verifier_lib", + "//source/extensions/quic_listeners/quiche:quic_transport_socket_factory_lib", + "//test/integration:http_integration_lib", + ], +) diff --git a/test/extensions/quic_listeners/quiche/integration/quic_http_integration_test.cc b/test/extensions/quic_listeners/quiche/integration/quic_http_integration_test.cc new file mode 100644 index 0000000000000..86d5f08b48f58 --- /dev/null +++ b/test/extensions/quic_listeners/quiche/integration/quic_http_integration_test.cc @@ -0,0 +1,185 @@ +#include "test/config/utility.h" +#include "test/integration/http_integration.h" +#include "test/test_common/utility.h" + +#pragma GCC diagnostic push +// QUICHE allows unused parameters. +#pragma GCC diagnostic ignored "-Wunused-parameter" +// QUICHE uses offsetof(). +#pragma GCC diagnostic ignored "-Winvalid-offsetof" + +#include "quiche/quic/core/http/quic_client_push_promise_index.h" +#include "quiche/quic/core/quic_utils.h" + +#pragma GCC diagnostic pop + +#include "extensions/quic_listeners/quiche/envoy_quic_client_session.h" +#include "extensions/quic_listeners/quiche/envoy_quic_client_connection.h" +#include "extensions/quic_listeners/quiche/envoy_quic_fake_proof_verifier.h" +#include "extensions/quic_listeners/quiche/envoy_quic_connection_helper.h" +#include "extensions/quic_listeners/quiche/envoy_quic_alarm_factory.h" +#include "extensions/quic_listeners/quiche/envoy_quic_packet_writer.h" + +namespace Envoy { +namespace Quic { + +class CodecClientCallbacksForTest : public Http::CodecClientCallbacks { +public: + void onStreamDestroy() override {} + + void onStreamReset(Http::StreamResetReason reason) override { + last_stream_reset_reason_ = reason; + } + + Http::StreamResetReason last_stream_reset_reason_{Http::StreamResetReason::LocalReset}; +}; + +class QuicHttpIntegrationTest : public testing::TestWithParam, + public HttpIntegrationTest { +public: + QuicHttpIntegrationTest() + : HttpIntegrationTest(Http::CodecClient::Type::HTTP3, GetParam(), + ConfigHelper::QUIC_HTTP_PROXY_CONFIG), + supported_versions_(quic::CurrentSupportedVersions()), + crypto_config_(std::make_unique()), conn_helper_(*dispatcher_), + alarm_factory_(*dispatcher_, *conn_helper_.GetClock()) { + quic::SetVerbosityLogThreshold(1); + } + + Network::ClientConnectionPtr makeClientConnection(uint32_t port) override { + Network::Address::InstanceConstSharedPtr server_addr = Network::Utility::resolveUrl( + fmt::format("udp://{}:{}", Network::Test::getLoopbackAddressUrlString(version_), port)); + Network::Address::InstanceConstSharedPtr local_addr = + Network::Test::getCanonicalLoopbackAddress(version_); + // Initiate a QUIC connection with the highest supported version. If not + // supported by server, this connection will fail. + // TODO(danzh) Implement retry upon version mismatch and modify test frame work to specify a + // different version set on server side to test that. + auto connection = std::make_unique( + getNextServerDesignatedConnectionId(), server_addr, conn_helper_, alarm_factory_, + quic::ParsedQuicVersionVector{supported_versions_[0]}, local_addr, *dispatcher_, nullptr); + auto session = std::make_unique( + quic_config_, supported_versions_, std::move(connection), server_id_, &crypto_config_, + &push_promise_index_, *dispatcher_, 0); + return session; + } + + // This call may fail because of INVALID_VERSION, because QUIC connection doesn't support + // in-connection version negotiation. + // TODO(#8479) Popagate INVALID_VERSION error to caller and let caller to use server advertised + // version list to create a new connection with mutually supported version and make client codec + // again. + IntegrationCodecClientPtr makeRawHttpConnection(Network::ClientConnectionPtr&& conn) override { + IntegrationCodecClientPtr codec = HttpIntegrationTest::makeRawHttpConnection(std::move(conn)); + ASSERT(!codec->connected()); + dynamic_cast(codec->connection())->cryptoConnect(); + if (codec->disconnected()) { + // Connection may get closed during version negotiation or handshake. + ENVOY_LOG(error, "Fail to connect to server with error: {}", + codec->connection()->transportFailureReason()); + } else { + codec->setCodecClientCallbacks(client_codec_callback_); + } + return codec; + } + + quic::QuicConnectionId getNextServerDesignatedConnectionId() { + quic::QuicCryptoClientConfig::CachedState* cached = crypto_config_.LookupOrCreate(server_id_); + // If the cached state indicates that we should use a server-designated + // connection ID, then return that connection ID. + quic::QuicConnectionId conn_id = cached->has_server_designated_connection_id() + ? cached->GetNextServerDesignatedConnectionId() + : quic::EmptyQuicConnectionId(); + return conn_id.IsEmpty() ? quic::QuicUtils::CreateRandomConnectionId() : conn_id; + } + + void initialize() override { + config_helper_.addConfigModifier([](envoy::config::bootstrap::v2::Bootstrap& bootstrap) { + envoy::api::v2::auth::DownstreamTlsContext tls_context; + ConfigHelper::initializeTls({}, *tls_context.mutable_common_tls_context()); + auto* filter_chain = + bootstrap.mutable_static_resources()->mutable_listeners(0)->mutable_filter_chains(0); + auto* transport_socket = filter_chain->mutable_transport_socket(); + TestUtility::jsonConvert(tls_context, *transport_socket->mutable_config()); + }); + config_helper_.addConfigModifier( + [](envoy::config::filter::network::http_connection_manager::v2::HttpConnectionManager& + hcm) { + hcm.mutable_delayed_close_timeout()->set_nanos(0); + EXPECT_EQ(hcm.codec_type(), envoy::config::filter::network::http_connection_manager::v2:: + HttpConnectionManager::HTTP3); + }); + + HttpIntegrationTest::initialize(); + registerTestServerPorts({"http"}); + } + +protected: + quic::QuicConfig quic_config_; + quic::QuicServerId server_id_; + quic::QuicClientPushPromiseIndex push_promise_index_; + quic::ParsedQuicVersionVector supported_versions_; + quic::QuicCryptoClientConfig crypto_config_; + EnvoyQuicConnectionHelper conn_helper_; + EnvoyQuicAlarmFactory alarm_factory_; + CodecClientCallbacksForTest client_codec_callback_; +}; + +INSTANTIATE_TEST_SUITE_P(IpVersions, QuicHttpIntegrationTest, + testing::ValuesIn(TestEnvironment::getIpVersionsForTest()), + TestUtility::ipTestParamsToString); + +TEST_P(QuicHttpIntegrationTest, GetRequestAndEmptyResponse) { + testRouterHeaderOnlyRequestAndResponse(); +} + +TEST_P(QuicHttpIntegrationTest, GetRequestAndResponseWithBody) { + initialize(); + sendRequestAndVerifyResponse(default_request_headers_, /*request_size=*/0, + default_response_headers_, /*response_size=*/1024, + /*backend_index*/ 0); +} + +TEST_P(QuicHttpIntegrationTest, PostRequestAndResponseWithBody) { + testRouterRequestAndResponseWithBody(1024, 512, false); +} + +TEST_P(QuicHttpIntegrationTest, PostRequestWithBigHeadersAndResponseWithBody) { + testRouterRequestAndResponseWithBody(1024, 512, true); +} + +TEST_P(QuicHttpIntegrationTest, RouterUpstreamDisconnectBeforeRequestcomplete) { + testRouterUpstreamDisconnectBeforeRequestComplete(); +} + +TEST_P(QuicHttpIntegrationTest, RouterUpstreamDisconnectBeforeResponseComplete) { + testRouterUpstreamDisconnectBeforeResponseComplete(); + EXPECT_EQ(Http::StreamResetReason::RemoteReset, client_codec_callback_.last_stream_reset_reason_); +} + +TEST_P(QuicHttpIntegrationTest, RouterDownstreamDisconnectBeforeRequestComplete) { + testRouterDownstreamDisconnectBeforeRequestComplete(); +} + +TEST_P(QuicHttpIntegrationTest, RouterDownstreamDisconnectBeforeResponseComplete) { + testRouterDownstreamDisconnectBeforeResponseComplete(); +} + +TEST_P(QuicHttpIntegrationTest, RouterUpstreamResponseBeforeRequestComplete) { + testRouterUpstreamResponseBeforeRequestComplete(); +} + +TEST_P(QuicHttpIntegrationTest, Retry) { testRetry(); } + +TEST_P(QuicHttpIntegrationTest, UpstreamThrottlingOnGiantResponseBody) { + config_helper_.setBufferLimits(/*upstream_buffer_limit=*/1024, /*downstream_buffer_limit=*/1024); + testRouterRequestAndResponseWithBody(/*request_size=*/512, /*response_size=*/1024 * 1024, false); +} + +TEST_P(QuicHttpIntegrationTest, DownstreamThrottlingOnGiantPost) { + config_helper_.setBufferLimits(/*upstream_buffer_limit=*/1024, /*downstream_buffer_limit=*/1024); + testRouterRequestAndResponseWithBody(/*request_size=*/1024 * 1024, /*response_size=*/1024, false); +} + +} // namespace Quic +} // namespace Envoy diff --git a/test/integration/http_integration.cc b/test/integration/http_integration.cc index eaa88e0b8bd27..8bc0f9d76ed4f 100644 --- a/test/integration/http_integration.cc +++ b/test/integration/http_integration.cc @@ -46,6 +46,9 @@ typeToCodecType(Http::CodecClient::Type type) { case Http::CodecClient::Type::HTTP2: return envoy::config::filter::network::http_connection_manager::v2::HttpConnectionManager:: HTTP2; + case Http::CodecClient::Type::HTTP3: + return envoy::config::filter::network::http_connection_manager::v2::HttpConnectionManager:: + HTTP3; default: RELEASE_ASSERT(0, ""); } @@ -60,7 +63,12 @@ IntegrationCodecClient::IntegrationCodecClient( callbacks_(*this), codec_callbacks_(*this) { connection_->addConnectionCallbacks(callbacks_); setCodecConnectionCallbacks(codec_callbacks_); - dispatcher.run(Event::Dispatcher::RunType::Block); + if (type != CodecClient::Type::HTTP3) { + // Only expect to have IO event if it's not QUIC. Because call to connect() in CodecClientProd + // for QUIC doesn't send anything to server, but just register file event. QUIC connection won't + // have any IO event till cryptoConnect() is called later. + dispatcher.run(Event::Dispatcher::RunType::Block); + } } void IntegrationCodecClient::flushWrite() { @@ -573,7 +581,9 @@ void HttpIntegrationTest::testRouterUpstreamResponseBeforeRequestComplete() { ASSERT_TRUE(fake_upstreams_[0]->waitForHttpConnection(*dispatcher_, fake_upstream_connection_)); ASSERT_TRUE(fake_upstream_connection_->waitForNewStream(*dispatcher_, upstream_request_)); ASSERT_TRUE(upstream_request_->waitForHeadersComplete()); + upstream_request_->encodeHeaders(default_response_headers_, false); + upstream_request_->encodeData(512, true); response->waitForEndStream(); diff --git a/test/integration/http_integration.h b/test/integration/http_integration.h index 8bbae101304b0..1a8f860b87d96 100644 --- a/test/integration/http_integration.h +++ b/test/integration/http_integration.h @@ -108,7 +108,7 @@ class HttpIntegrationTest : public BaseIntegrationTest { IntegrationCodecClientPtr makeHttpConnection(uint32_t port); // Makes a http connection object without checking its connected state. - IntegrationCodecClientPtr makeRawHttpConnection(Network::ClientConnectionPtr&& conn); + virtual IntegrationCodecClientPtr makeRawHttpConnection(Network::ClientConnectionPtr&& conn); // Makes a http connection object with asserting a connected state. IntegrationCodecClientPtr makeHttpConnection(Network::ClientConnectionPtr&& conn); diff --git a/test/integration/integration.h b/test/integration/integration.h index 7cd2109cc8593..0e2b7d48c6fe1 100644 --- a/test/integration/integration.h +++ b/test/integration/integration.h @@ -138,7 +138,7 @@ struct ApiFilesystemConfig { /** * Test fixture for all integration tests. */ -class BaseIntegrationTest : Logger::Loggable { +class BaseIntegrationTest : protected Logger::Loggable { public: using TestTimeSystemPtr = std::unique_ptr; using InstanceConstSharedPtrFn = std::function; @@ -191,7 +191,7 @@ class BaseIntegrationTest : Logger::Loggable { void setUpstreamAddress(uint32_t upstream_index, envoy::api::v2::endpoint::LbEndpoint& endpoint) const; - Network::ClientConnectionPtr makeClientConnection(uint32_t port); + virtual Network::ClientConnectionPtr makeClientConnection(uint32_t port); void registerTestServerPorts(const std::vector& port_names); void createTestServer(const std::string& json_path, const std::vector& port_names); diff --git a/test/integration/xfcc_integration_test.cc b/test/integration/xfcc_integration_test.cc index 4e2bed984cfbd..0b62da4439de2 100644 --- a/test/integration/xfcc_integration_test.cc +++ b/test/integration/xfcc_integration_test.cc @@ -92,7 +92,7 @@ Network::TransportSocketFactoryPtr XfccIntegrationTest::createUpstreamSslContext std::move(cfg), *context_manager_, *upstream_stats_store, std::vector{}); } -Network::ClientConnectionPtr XfccIntegrationTest::makeClientConnection() { +Network::ClientConnectionPtr XfccIntegrationTest::makeTcpClientConnection() { Network::Address::InstanceConstSharedPtr address = Network::Utility::resolveUrl("tcp://" + Network::Test::getLoopbackAddressUrlString(version_) + ":" + std::to_string(lookupPort("http"))); @@ -143,7 +143,7 @@ void XfccIntegrationTest::initialize() { void XfccIntegrationTest::testRequestAndResponseWithXfccHeader(std::string previous_xfcc, std::string expected_xfcc) { - Network::ClientConnectionPtr conn = tls_ ? makeMtlsClientConnection() : makeClientConnection(); + Network::ClientConnectionPtr conn = tls_ ? makeMtlsClientConnection() : makeTcpClientConnection(); Http::TestHeaderMapImpl header_map; if (previous_xfcc.empty()) { header_map = Http::TestHeaderMapImpl{{":method", "GET"}, diff --git a/test/integration/xfcc_integration_test.h b/test/integration/xfcc_integration_test.h index 488ff98aad65d..5e1ad2082890d 100644 --- a/test/integration/xfcc_integration_test.h +++ b/test/integration/xfcc_integration_test.h @@ -46,7 +46,7 @@ class XfccIntegrationTest : public testing::TestWithParam Date: Wed, 9 Oct 2019 18:53:13 -0400 Subject: [PATCH 12/76] remove unrelated change Signed-off-by: Dan Zhang --- .../quic_listeners/quiche/codec_impl.h | 7 ++++- test/config/utility.cc | 31 ------------------- test/config/utility.h | 3 +- 3 files changed, 7 insertions(+), 34 deletions(-) diff --git a/source/extensions/quic_listeners/quiche/codec_impl.h b/source/extensions/quic_listeners/quiche/codec_impl.h index 9b3d5e08a62b8..07b2f2042ac14 100644 --- a/source/extensions/quic_listeners/quiche/codec_impl.h +++ b/source/extensions/quic_listeners/quiche/codec_impl.h @@ -21,7 +21,12 @@ class QuicHttpConnectionImplBase : public virtual Http::Connection, // Bypassed. QUIC connection already hands all data to streams. NOT_REACHED_GCOVR_EXCL_LINE; } - Http::Protocol protocol() override { return Http::Protocol::Http2; } + Http::Protocol protocol() override { + // From HCM's view, QUIC should behave the same as Http2, only the stats + // should be different. + // TODO(danzh) add Http3 enum value for QUIC. + return Http::Protocol::Http2; + } // Returns true if the session has data to send but queued in connection or // stream send buffer. diff --git a/test/config/utility.cc b/test/config/utility.cc index 50a40990a0e72..8f994611ab285 100644 --- a/test/config/utility.cc +++ b/test/config/utility.cc @@ -113,37 +113,6 @@ const std::string ConfigHelper::HTTP_PROXY_CONFIG = BASE_CONFIG + R"EOF( name: route_config_0 )EOF"; -const std::string ConfigHelper::QUIC_HTTP_PROXY_CONFIG = BASE_UDP_LISTENER_CONFIG + R"EOF( - filter_chains: - transport_socket: - name: quic - filters: - name: envoy.http_connection_manager - config: - stat_prefix: config_test - http_filters: - name: envoy.router - codec_type: HTTP3 - access_log: - name: envoy.file_access_log - filter: - not_health_check_filter: {} - config: - path: /dev/null - route_config: - virtual_hosts: - name: integration - routes: - route: - cluster: cluster_0 - match: - prefix: "/" - domains: "*" - name: route_config_0 - udp_listener_config: - udp_listener_name: "quiche_quic_listener" -)EOF"; - const std::string ConfigHelper::DEFAULT_BUFFER_FILTER = R"EOF( name: envoy.buffer diff --git a/test/config/utility.h b/test/config/utility.h index ada7f71906f51..aa03d8583a6f3 100644 --- a/test/config/utility.h +++ b/test/config/utility.h @@ -78,8 +78,7 @@ class ConfigHelper { static const std::string TCP_PROXY_CONFIG; // A basic configuration for L7 proxying. static const std::string HTTP_PROXY_CONFIG; - // A basic configuration for L7 proxying with QUIC transport. - static const std::string QUIC_HTTP_PROXY_CONFIG; + // A string for a basic buffer filter, which can be used with addFilter() static const std::string DEFAULT_BUFFER_FILTER; // A string for a small buffer filter, which can be used with addFilter() From 29128aa484f76bf23fffbc0d577f9240a5a90a5a Mon Sep 17 00:00:00 2001 From: Dan Zhang Date: Thu, 10 Oct 2019 11:49:39 -0400 Subject: [PATCH 13/76] fix asan Signed-off-by: Dan Zhang --- .../quic_listeners/quiche/envoy_quic_server_session.cc | 3 ++- .../quic_listeners/quiche/envoy_quic_server_session.h | 1 + .../quiche/quic_filter_manager_connection_impl.cc | 4 ++-- .../quiche/quic_filter_manager_connection_impl.h | 4 ++-- 4 files changed, 7 insertions(+), 5 deletions(-) diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_server_session.cc b/source/extensions/quic_listeners/quiche/envoy_quic_server_session.cc index 1d48b93bf3149..8a6eba95daa0e 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_server_session.cc +++ b/source/extensions/quic_listeners/quiche/envoy_quic_server_session.cc @@ -23,7 +23,8 @@ EnvoyQuicServerSession::EnvoyQuicServerSession( uint32_t send_buffer_limit) : quic::QuicServerSessionBase(config, supported_versions, connection.get(), visitor, helper, crypto_config, compressed_certs_cache), - QuicFilterManagerConnectionImpl(std::move(connection), dispatcher, send_buffer_limit) {} + QuicFilterManagerConnectionImpl(connection.get(), dispatcher, send_buffer_limit), + quic_connection_(std::move(connection)) {} EnvoyQuicServerSession::~EnvoyQuicServerSession() { QuicFilterManagerConnectionImpl::quic_connection_ = nullptr; diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_server_session.h b/source/extensions/quic_listeners/quiche/envoy_quic_server_session.h index ff5d254a23c44..5c5561cbb7f52 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_server_session.h +++ b/source/extensions/quic_listeners/quiche/envoy_quic_server_session.h @@ -68,6 +68,7 @@ class EnvoyQuicServerSession : public quic::QuicServerSessionBase, private: void setUpRequestDecoder(EnvoyQuicStream& stream); + std::unique_ptr quic_connection_; // These callbacks are owned by network filters and quic session should out live // them. Http::ServerConnectionCallbacks* http_connection_callbacks_{nullptr}; diff --git a/source/extensions/quic_listeners/quiche/quic_filter_manager_connection_impl.cc b/source/extensions/quic_listeners/quiche/quic_filter_manager_connection_impl.cc index 3ea596f3a4542..a24b9802c0cb0 100644 --- a/source/extensions/quic_listeners/quiche/quic_filter_manager_connection_impl.cc +++ b/source/extensions/quic_listeners/quiche/quic_filter_manager_connection_impl.cc @@ -5,10 +5,10 @@ namespace Envoy { namespace Quic { -QuicFilterManagerConnectionImpl::QuicFilterManagerConnectionImpl(std::unique_ptr connection, +QuicFilterManagerConnectionImpl::QuicFilterManagerConnectionImpl(EnvoyQuicConnection* connection, Event::Dispatcher& dispatcher, uint32_t send_buffer_limit) - : quic_connection_(std::move(connection)), dispatcher_(dispatcher), filter_manager_(*this), + : quic_connection_(connection), dispatcher_(dispatcher), filter_manager_(*this), // QUIC connection id can be 18 bytes. It's easier to use hash value instead // of trying to map it into a 64-bit space. stream_info_(dispatcher.timeSource()), id_(quic_connection_->connection_id().Hash()), diff --git a/source/extensions/quic_listeners/quiche/quic_filter_manager_connection_impl.h b/source/extensions/quic_listeners/quiche/quic_filter_manager_connection_impl.h index ace95927c9e79..fe4ab76ee86ef 100644 --- a/source/extensions/quic_listeners/quiche/quic_filter_manager_connection_impl.h +++ b/source/extensions/quic_listeners/quiche/quic_filter_manager_connection_impl.h @@ -18,7 +18,7 @@ namespace Quic { class QuicFilterManagerConnectionImpl : public Network::FilterManagerConnection, protected Logger::Loggable { public: - QuicFilterManagerConnectionImpl(std::unique_ptr connection, Event::Dispatcher& dispatcher, + QuicFilterManagerConnectionImpl(EnvoyQuicConnection* connection, Event::Dispatcher& dispatcher, uint32_t send_buffer_limit); // Network::FilterManager @@ -106,7 +106,7 @@ class QuicFilterManagerConnectionImpl : public Network::FilterManagerConnection, void raiseEvent(Network::ConnectionEvent event); - std::unique_ptr quic_connection_; + EnvoyQuicConnection* quic_connection_; // TODO(danzh): populate stats. std::unique_ptr stats_; Event::Dispatcher& dispatcher_; From 31340f2711c822ab9232e5b69c6e791fc0e22446 Mon Sep 17 00:00:00 2001 From: Dan Zhang Date: Thu, 10 Oct 2019 12:32:54 -0400 Subject: [PATCH 14/76] typo Signed-off-by: Dan Zhang --- .../quic_listeners/quiche/envoy_quic_server_stream_test.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/extensions/quic_listeners/quiche/envoy_quic_server_stream_test.cc b/test/extensions/quic_listeners/quiche/envoy_quic_server_stream_test.cc index 290096b9c8408..4119445f528ae 100644 --- a/test/extensions/quic_listeners/quiche/envoy_quic_server_stream_test.cc +++ b/test/extensions/quic_listeners/quiche/envoy_quic_server_stream_test.cc @@ -336,7 +336,7 @@ TEST_P(EnvoyQuicServerStreamTest, WatermarkSendBuffer) { response_headers_.addCopy(":content-length", "32770"); // 32KB + 2 byte quic_stream_->encodeHeaders(response_headers_, /*end_stream=*/false); - // encode 32kB response body. first 16KB shoudl be written out right away. The + // encode 32kB response body. first 16KB should be written out right away. The // rest should be buffered. The high watermark is 16KB, so this call should // make the send buffer reach its high watermark. std::string response(32 * 1024 + 1, 'a'); From 88ebd06d3e1bd2c89473f1a3ed3487b90e3774bd Mon Sep 17 00:00:00 2001 From: Dan Zhang Date: Mon, 14 Oct 2019 12:06:37 -0400 Subject: [PATCH 15/76] add test for readDisable Signed-off-by: Dan Zhang --- bazel/external/quiche.BUILD | 14 ++ source/extensions/quic_listeners/quiche/BUILD | 2 + .../quic_listeners/quiche/codec_impl.cc | 2 + .../quiche/envoy_quic_server_stream.cc | 4 +- .../envoy_quic_simulated_watermark_buffer.h | 2 + test/extensions/quic_listeners/quiche/BUILD | 2 + .../quiche/envoy_quic_server_session_test.cc | 208 +++++++++++++++++- .../quiche/envoy_quic_server_stream_test.cc | 179 ++++++++++----- 8 files changed, 358 insertions(+), 55 deletions(-) diff --git a/bazel/external/quiche.BUILD b/bazel/external/quiche.BUILD index 67d7d4207ce90..9b39ee83f5351 100644 --- a/bazel/external/quiche.BUILD +++ b/bazel/external/quiche.BUILD @@ -3112,6 +3112,20 @@ envoy_cc_test_library( ], ) +envoy_cc_test_library( + name = "quic_test_tools_server_session_base_peer", + hdrs = [ + "quiche/quic/test_tools/quic_server_session_base_peer.h", + ], + copts = quiche_copt, + repository = "@envoy", + tags = ["nofips"], + deps = [ + ":quic_core_http_spdy_session_lib", + ":quic_core_utils_lib", + ], +) + envoy_cc_test_library( name = "quic_test_tools_simple_quic_framer_lib", srcs = ["quiche/quic/test_tools/simple_quic_framer.cc"], diff --git a/source/extensions/quic_listeners/quiche/BUILD b/source/extensions/quic_listeners/quiche/BUILD index 656ee91314eec..a713809722541 100644 --- a/source/extensions/quic_listeners/quiche/BUILD +++ b/source/extensions/quic_listeners/quiche/BUILD @@ -98,6 +98,7 @@ envoy_cc_library( tags = ["nofips"], deps = [ ":envoy_quic_simulated_watermark_buffer_lib", + "//include/envoy/event:dispatcher_interface", "//include/envoy/http:codec_interface", "//source/common/http:codec_helper_lib", ], @@ -211,6 +212,7 @@ envoy_cc_library( envoy_cc_library( name = "envoy_quic_simulated_watermark_buffer_lib", hdrs = ["envoy_quic_simulated_watermark_buffer.h"], + deps = ["//source/common/common:assert_lib"], ) envoy_cc_library( diff --git a/source/extensions/quic_listeners/quiche/codec_impl.cc b/source/extensions/quic_listeners/quiche/codec_impl.cc index bad608ed8a281..6c9ae799b51f3 100644 --- a/source/extensions/quic_listeners/quiche/codec_impl.cc +++ b/source/extensions/quic_listeners/quiche/codec_impl.cc @@ -16,6 +16,7 @@ QuicHttpServerConnectionImpl::QuicHttpServerConnectionImpl( void QuicHttpServerConnectionImpl::onUnderlyingConnectionAboveWriteBufferHighWatermark() { for (auto& it : quic_server_session_.stream_map()) { if (!it.second->is_static()) { + ENVOY_LOG(debug, "runHighWatermarkCallbacks on stream {}", it.first); dynamic_cast(it.second.get())->runHighWatermarkCallbacks(); } } @@ -24,6 +25,7 @@ void QuicHttpServerConnectionImpl::onUnderlyingConnectionAboveWriteBufferHighWat void QuicHttpServerConnectionImpl::onUnderlyingConnectionBelowWriteBufferLowWatermark() { for (const auto& it : quic_server_session_.stream_map()) { if (!it.second->is_static()) { + ENVOY_LOG(debug, "runLowWatermarkCallbacks on stream {}", it.first); dynamic_cast(it.second.get())->runLowWatermarkCallbacks(); } } diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_server_stream.cc b/source/extensions/quic_listeners/quiche/envoy_quic_server_stream.cc index 9f05182af88af..8e743a7adaac8 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_server_stream.cc +++ b/source/extensions/quic_listeners/quiche/envoy_quic_server_stream.cc @@ -31,14 +31,14 @@ EnvoyQuicServerStream::EnvoyQuicServerStream(quic::QuicStreamId id, quic::QuicSp quic::StreamType type) : quic::QuicSpdyServerStreamBase(id, session, type), EnvoyQuicStream( - session->config()->GetInitialStreamFlowControlWindowToSend(), + 2 * GetQuicFlag(FLAGS_quic_buffered_data_threshold), [this]() { runLowWatermarkCallbacks(); }, [this]() { runHighWatermarkCallbacks(); }) {} EnvoyQuicServerStream::EnvoyQuicServerStream(quic::PendingStream* pending, quic::QuicSpdySession* session, quic::StreamType type) : quic::QuicSpdyServerStreamBase(pending, session, type), EnvoyQuicStream( - session->config()->GetInitialStreamFlowControlWindowToSend(), + session->config()->ReceivedInitialStreamFlowControlWindowBytes(), [this]() { runLowWatermarkCallbacks(); }, [this]() { runHighWatermarkCallbacks(); }) {} void EnvoyQuicServerStream::encode100ContinueHeaders(const Http::HeaderMap& headers) { diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_simulated_watermark_buffer.h b/source/extensions/quic_listeners/quiche/envoy_quic_simulated_watermark_buffer.h index 0602bd39477f5..025f9e1e576b8 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_simulated_watermark_buffer.h +++ b/source/extensions/quic_listeners/quiche/envoy_quic_simulated_watermark_buffer.h @@ -2,6 +2,8 @@ #include +#include "common/common/assert.h" + #include "spdlog/spdlog.h" namespace Envoy { diff --git a/test/extensions/quic_listeners/quiche/BUILD b/test/extensions/quic_listeners/quiche/BUILD index a1f9e7b2986a7..fd0e631a8d738 100644 --- a/test/extensions/quic_listeners/quiche/BUILD +++ b/test/extensions/quic_listeners/quiche/BUILD @@ -93,6 +93,8 @@ envoy_cc_test( "//test/test_common:global_lib", "//test/test_common:logging_lib", "//test/test_common:simulated_time_system_lib", + "@com_googlesource_quiche//:quic_test_tools_server_session_base_peer", + "@com_googlesource_quiche//:quic_test_tools_test_utils_interface_lib", ], ) diff --git a/test/extensions/quic_listeners/quiche/envoy_quic_server_session_test.cc b/test/extensions/quic_listeners/quiche/envoy_quic_server_session_test.cc index dc4fbbe642682..8a9d9fd7ca23b 100644 --- a/test/extensions/quic_listeners/quiche/envoy_quic_server_session_test.cc +++ b/test/extensions/quic_listeners/quiche/envoy_quic_server_session_test.cc @@ -4,8 +4,13 @@ // QUICHE uses offsetof(). #pragma GCC diagnostic ignored "-Winvalid-offsetof" +#include "quiche/quic/core/crypto/null_encrypter.h" +#include "quiche/quic/core/quic_utils.h" #include "quiche/quic/core/quic_versions.h" #include "quiche/quic/test_tools/crypto_test_utils.h" +#include "quiche/quic/test_tools/quic_config_peer.h" +#include "quiche/quic/test_tools/quic_connection_peer.h" +#include "quiche/quic/test_tools/quic_server_session_base_peer.h" #include "quiche/quic/test_tools/quic_test_utils.h" #pragma GCC diagnostic pop @@ -13,6 +18,7 @@ #include #include "extensions/quic_listeners/quiche/envoy_quic_server_session.h" +#include "extensions/quic_listeners/quiche/envoy_quic_server_stream.h" #include "extensions/quic_listeners/quiche/envoy_quic_server_connection.h" #include "extensions/quic_listeners/quiche/codec_impl.h" #include "extensions/quic_listeners/quiche/envoy_quic_connection_helper.h" @@ -36,6 +42,7 @@ #include "gtest/gtest.h" using testing::_; +using testing::AnyNumber; using testing::Invoke; using testing::Return; using testing::ReturnRef; @@ -66,6 +73,28 @@ class TestEnvoyQuicServerConnection : public EnvoyQuicServerConnection { MOCK_METHOD1(SendControlFrame, bool(const quic::QuicFrame& frame)); }; +// Derive to have simpler priority mechanism. +class TestEnvoyQuicServerSession : public EnvoyQuicServerSession { +public: + TestEnvoyQuicServerSession(const quic::QuicConfig& config, + const quic::ParsedQuicVersionVector& supported_versions, + std::unique_ptr connection, + quic::QuicSession::Visitor* visitor, + quic::QuicCryptoServerStream::Helper* helper, + const quic::QuicCryptoServerConfig* crypto_config, + quic::QuicCompressedCertsCache* compressed_certs_cache, + Event::Dispatcher& dispatcher, uint32_t send_buffer_limit) + : EnvoyQuicServerSession(config, supported_versions, std::move(connection), visitor, helper, + crypto_config, compressed_certs_cache, dispatcher, + send_buffer_limit) {} + + bool ShouldYield(quic::QuicStreamId /*stream_id*/) override { + // Never yield to other stream so that it's easier to predict stream write + // behavior. + return false; + } +}; + class EnvoyQuicServerSessionTest : public testing::TestWithParam { public: EnvoyQuicServerSessionTest() @@ -88,13 +117,17 @@ class EnvoyQuicServerSessionTest : public testing::TestWithParam { std::unique_ptr(quic_connection_), /*visitor=*/nullptr, &crypto_stream_helper_, &crypto_config_, &compressed_certs_cache_, *dispatcher_, - /*send_buffer_limit*/ 1024 * 1024), + /*send_buffer_limit*/ quic::kDefaultFlowControlSendWindow * 1.5), read_filter_(new Network::MockReadFilter()) { + EXPECT_EQ(time_system_.systemTime(), envoy_quic_session_.streamInfo().startTime()); EXPECT_EQ(EMPTY_STRING, envoy_quic_session_.nextProtocol()); time_system_.sleep(std::chrono::milliseconds(1)); ON_CALL(writer_, WritePacket(_, _, _, _, _)) - .WillByDefault(testing::Return(quic::WriteResult(quic::WRITE_STATUS_OK, 1))); + .WillByDefault(Invoke([](const char*, size_t buf_len, const quic::QuicIpAddress&, + const quic::QuicSocketAddress&, quic::PerPacketOptions*) { + return quic::WriteResult{quic::WRITE_STATUS_OK, static_cast(buf_len)}; + })); ON_CALL(crypto_stream_helper_, CanAcceptClientHello(_, _, _, _, _)).WillByDefault(Return(true)); } @@ -145,7 +178,7 @@ class EnvoyQuicServerSessionTest : public testing::TestWithParam { quic::QuicConfig quic_config_; quic::QuicCryptoServerConfig crypto_config_; testing::NiceMock crypto_stream_helper_; - EnvoyQuicServerSession envoy_quic_session_; + TestEnvoyQuicServerSession envoy_quic_session_; quic::QuicCompressedCertsCache compressed_certs_cache_{100}; std::shared_ptr read_filter_; Network::MockConnectionCallbacks network_connection_callbacks_; @@ -394,5 +427,174 @@ TEST_P(EnvoyQuicServerSessionTest, NetworkConnectionInterface) { EXPECT_TRUE(envoy_quic_session_.readEnabled()); } +class TestQuicCryptoServerStream : public quic::QuicCryptoServerStream { +public: + explicit TestQuicCryptoServerStream(const quic::QuicCryptoServerConfig* crypto_config, + quic::QuicCompressedCertsCache* compressed_certs_cache, + quic::QuicServerSessionBase* session, + quic::QuicCryptoServerStream::Helper* helper) + : quic::QuicCryptoServerStream(crypto_config, compressed_certs_cache, session, helper) {} + bool encryption_established() const override { return true; } +}; + +TEST_P(EnvoyQuicServerSessionTest, SendBufferWatermark) { + // Switch to a encryption forward secure crypto stream. + quic::test::QuicServerSessionBasePeer::SetCryptoStream(&envoy_quic_session_, nullptr); + quic::test::QuicServerSessionBasePeer::SetCryptoStream( + &envoy_quic_session_, + new TestQuicCryptoServerStream(&crypto_config_, &compressed_certs_cache_, + &envoy_quic_session_, &crypto_stream_helper_)); + quic_connection_->SetDefaultEncryptionLevel(quic::ENCRYPTION_FORWARD_SECURE); + quic_connection_->SetEncrypter( + quic::ENCRYPTION_FORWARD_SECURE, + std::make_unique(quic::Perspective::IS_SERVER)); + // Drive congestion control manually. + auto send_algorithm = new testing::StrictMock; + quic::test::QuicConnectionPeer::SetSendAlgorithm(quic_connection_, send_algorithm); + EXPECT_CALL(*send_algorithm, CanSend(_)).WillRepeatedly(Return(true)); + EXPECT_CALL(*send_algorithm, OnPacketSent(_, _, _, _, _)).Times(AnyNumber()); + EXPECT_CALL(*send_algorithm, GetCongestionWindow()).WillRepeatedly(Return(quic::kDefaultTCPMSS)); + EXPECT_CALL(*send_algorithm, PacingRate(_)).WillRepeatedly(Return(quic::QuicBandwidth::Zero())); + EXPECT_CALL(*send_algorithm, HasReliableBandwidthEstimate()).Times(AnyNumber()); + EXPECT_CALL(*send_algorithm, BandwidthEstimate()) + .WillRepeatedly(Return(quic::QuicBandwidth::Zero())); + EXPECT_CALL(*send_algorithm, InSlowStart()).Times(AnyNumber()); + EXPECT_CALL(*send_algorithm, InRecovery()).Times(AnyNumber()); + EXPECT_CALL(*send_algorithm, OnApplicationLimited(_)).Times(AnyNumber()); + EXPECT_CALL(*quic_connection_, SendControlFrame(_)).Times(AnyNumber()); + + // Bump connection flow control window large enough not to interfere + // stream writing. + envoy_quic_session_.flow_controller()->UpdateSendWindowOffset( + 10 * quic::kDefaultFlowControlSendWindow); + installReadFilter(); + Http::MockStreamDecoder request_decoder; + Http::MockStreamCallbacks stream_callbacks; + EXPECT_CALL(http_connection_callbacks_, newStream(_, false)) + .WillOnce(Invoke([&request_decoder, &stream_callbacks](Http::StreamEncoder& encoder, + bool) -> Http::StreamDecoder& { + encoder.getStream().addCallbacks(stream_callbacks); + return request_decoder; + })); + quic::QuicStreamId stream_id = + quic_version_[0].transport_version == quic::QUIC_VERSION_99 ? 4u : 5u; + auto stream1 = + dynamic_cast(envoy_quic_session_.GetOrCreateStream(stream_id)); + + // Receive a GET request on created stream. + quic::QuicHeaderList request_headers; + request_headers.OnHeaderBlockStart(); + std::string host("www.abc.com"); + request_headers.OnHeader(":authority", host); + request_headers.OnHeader(":method", "GET"); + request_headers.OnHeader(":path", "/"); + request_headers.OnHeaderBlockEnd(/*uncompressed_header_bytes=*/0, /*compressed_header_bytes=*/0); + // Request headers should be propagated to decoder. + EXPECT_CALL(request_decoder, decodeHeaders_(_, /*end_stream=*/true)) + .WillOnce(Invoke([&host](const Http::HeaderMapPtr& decoded_headers, bool) { + EXPECT_EQ(host, decoded_headers->Host()->value().getStringView()); + EXPECT_EQ("/", decoded_headers->Path()->value().getStringView()); + EXPECT_EQ(Http::Headers::get().MethodValues.Get, + decoded_headers->Method()->value().getStringView()); + })); + EXPECT_CALL(request_decoder, decodeData(_, true)) + .Times(testing::AtMost(1)) + .WillOnce(Invoke([](Buffer::Instance& buffer, bool) { EXPECT_EQ(0, buffer.length()); })); + stream1->OnStreamHeaderList(/*fin=*/true, request_headers.uncompressed_header_bytes(), + request_headers); + + Http::TestHeaderMapImpl response_headers{{":status", "200"}, + {":content-length", "32770"}}; // 32KB + 2 bytes + + stream1->encodeHeaders(response_headers, false); + std::string response(32 * 1024 + 1, 'a'); + Buffer::OwnedImpl buffer(response); + EXPECT_CALL(stream_callbacks, onAboveWriteBufferHighWatermark()); + stream1->encodeData(buffer, false); + EXPECT_TRUE(stream1->flow_controller()->IsBlocked()); + EXPECT_FALSE(envoy_quic_session_.IsConnectionFlowControlBlocked()); + + // Receive another request and send back response to trigger connection level + // send buffer watermark. + Http::MockStreamDecoder request_decoder2; + Http::MockStreamCallbacks stream_callbacks2; + EXPECT_CALL(http_connection_callbacks_, newStream(_, false)) + .WillOnce(Invoke([&request_decoder2, &stream_callbacks2](Http::StreamEncoder& encoder, + bool) -> Http::StreamDecoder& { + encoder.getStream().addCallbacks(stream_callbacks2); + return request_decoder2; + })); + auto stream2 = + dynamic_cast(envoy_quic_session_.GetOrCreateStream(stream_id + 4)); + EXPECT_CALL(request_decoder2, decodeHeaders_(_, /*end_stream=*/true)) + .WillOnce(Invoke([&host](const Http::HeaderMapPtr& decoded_headers, bool) { + EXPECT_EQ(host, decoded_headers->Host()->value().getStringView()); + EXPECT_EQ("/", decoded_headers->Path()->value().getStringView()); + EXPECT_EQ(Http::Headers::get().MethodValues.Get, + decoded_headers->Method()->value().getStringView()); + })); + EXPECT_CALL(request_decoder2, decodeData(_, true)) + .Times(testing::AtMost(1)) + .WillOnce(Invoke([](Buffer::Instance& buffer, bool) { EXPECT_EQ(0, buffer.length()); })); + stream2->OnStreamHeaderList(/*fin=*/true, request_headers.uncompressed_header_bytes(), + request_headers); + stream2->encodeHeaders(response_headers, false); + // This reponse will trigger both stream and connection's send buffer watermark upper limits. + Buffer::OwnedImpl buffer2(response); + EXPECT_CALL(network_connection_callbacks_, onAboveWriteBufferHighWatermark) + .WillOnce(Invoke( + [this]() { http_connection_->onUnderlyingConnectionAboveWriteBufferHighWatermark(); })); + EXPECT_CALL(stream_callbacks2, onAboveWriteBufferHighWatermark()).Times(2); + EXPECT_CALL(stream_callbacks, onAboveWriteBufferHighWatermark()); + stream2->encodeData(buffer2, false); + + // Update flow control window for stream1. + quic::QuicWindowUpdateFrame window_update1(quic::kInvalidControlFrameId, stream1->id(), + 32 * 1024); + stream1->OnWindowUpdateFrame(window_update1); + EXPECT_CALL(stream_callbacks, onBelowWriteBufferLowWatermark()).WillOnce(Invoke([stream1]() { + // Write rest response to stream1. + std::string rest_response(1, 'a'); + Buffer::OwnedImpl buffer(rest_response); + stream1->encodeData(buffer, true); + })); + envoy_quic_session_.OnCanWrite(); + EXPECT_TRUE(stream1->flow_controller()->IsBlocked()); + + // Update flow control window for stream2. + quic::QuicWindowUpdateFrame window_update2(quic::kInvalidControlFrameId, stream2->id(), + 32 * 1024); + stream2->OnWindowUpdateFrame(window_update2); + EXPECT_CALL(stream_callbacks2, onBelowWriteBufferLowWatermark()).WillOnce(Invoke([stream2]() { + // Write rest response to stream2. + std::string rest_response(1, 'a'); + Buffer::OwnedImpl buffer(rest_response); + stream2->encodeData(buffer, true); + })); + // Writing out another 16k on stream2 will trigger connection's send buffer + // come down below low watermark. + EXPECT_CALL(network_connection_callbacks_, onBelowWriteBufferLowWatermark) + .WillOnce(Invoke([this]() { + // This call shouldn't be propagate to streams because they both wrote to end + // of stream. + http_connection_->onUnderlyingConnectionBelowWriteBufferLowWatermark(); + })); + envoy_quic_session_.OnCanWrite(); + EXPECT_TRUE(stream2->flow_controller()->IsBlocked()); + + // Update flow control window for stream1. + quic::QuicWindowUpdateFrame window_update3(quic::kInvalidControlFrameId, stream1->id(), + 48 * 1024); + stream1->OnWindowUpdateFrame(window_update3); + // Update flow control window for stream2. + quic::QuicWindowUpdateFrame window_update4(quic::kInvalidControlFrameId, stream2->id(), + 48 * 1024); + stream2->OnWindowUpdateFrame(window_update4); + envoy_quic_session_.OnCanWrite(); + + EXPECT_TRUE(stream1->write_side_closed()); + EXPECT_TRUE(stream2->write_side_closed()); +} + } // namespace Quic } // namespace Envoy diff --git a/test/extensions/quic_listeners/quiche/envoy_quic_server_stream_test.cc b/test/extensions/quic_listeners/quiche/envoy_quic_server_stream_test.cc index 4119445f528ae..a2b218c6f255b 100644 --- a/test/extensions/quic_listeners/quiche/envoy_quic_server_stream_test.cc +++ b/test/extensions/quic_listeners/quiche/envoy_quic_server_stream_test.cc @@ -1,3 +1,5 @@ +#include + #include "extensions/quic_listeners/quiche/envoy_quic_server_stream.h" #pragma GCC diagnostic push @@ -63,13 +65,6 @@ class MockQuicServerSession : public EnvoyQuicServerSession { size_t write_length, quic::QuicStreamOffset offset, quic::StreamSendingState state)); - quic::QuicCryptoServerStream* - CreateQuicCryptoServerStream(const quic::QuicCryptoServerConfig* crypto_config, - quic::QuicCompressedCertsCache* compressed_certs_cache) override { - return new quic::QuicCryptoServerStream(crypto_config, compressed_certs_cache, this, - stream_helper()); - } - using quic::QuicServerSessionBase::ActivateStream; }; @@ -95,11 +90,11 @@ class EnvoyQuicServerStreamTest : public testing::TestWithParam { /*visitor=*/nullptr, /*helper=*/nullptr, /*crypto_config=*/nullptr, /*compressed_certs_cache=*/nullptr, *dispatcher_, - quic_config_.GetInitialStreamFlowControlWindowToSend()), + quic_config_.GetInitialSessionFlowControlWindowToSend()), stream_id_(quic_version_.transport_version == quic::QUIC_VERSION_99 ? 4u : 5u), quic_stream_(new EnvoyQuicServerStream(stream_id_, &quic_session_, quic::BIDIRECTIONAL)), response_headers_{{":status", "200"}} { - quic::SetVerbosityLogThreshold(3); + quic::SetVerbosityLogThreshold(2); quic_stream_->setDecoder(stream_decoder_); quic_stream_->addCallbacks(stream_callbacks_); quic_session_.ActivateStream(std::unique_ptr(quic_stream_)); @@ -140,6 +135,44 @@ class EnvoyQuicServerStreamTest : public testing::TestWithParam { } } + size_t sendRequest(const std::string& payload, bool fin, size_t decoder_buffer_high_watermark) { + EXPECT_CALL(stream_decoder_, decodeHeaders_(_, /*end_stream=*/false)) + .WillOnce(Invoke([this](const Http::HeaderMapPtr& headers, bool) { + EXPECT_EQ(host_, headers->Host()->value().getStringView()); + EXPECT_EQ("/", headers->Path()->value().getStringView()); + EXPECT_EQ(Http::Headers::get().MethodValues.Get, + headers->Method()->value().getStringView()); + })); + if (quic_version_.transport_version == quic::QUIC_VERSION_99) { + quic_stream_->OnHeadersDecoded(request_headers_); + } else { + quic_stream_->OnStreamHeaderList(/*fin=*/false, request_headers_.uncompressed_header_bytes(), + request_headers_); + } + EXPECT_TRUE(quic_stream_->FinishedReadingHeaders()); + + EXPECT_CALL(stream_decoder_, decodeData(_, _)) + .WillOnce(Invoke([&](Buffer::Instance& buffer, bool finished_reading) { + EXPECT_EQ(payload, buffer.toString()); + EXPECT_EQ(fin, finished_reading); + if (!finished_reading && buffer.length() > decoder_buffer_high_watermark) { + quic_stream_->readDisable(true); + } + })); + std::string data = payload; + if (quic_version_.transport_version == quic::QUIC_VERSION_99) { + std::unique_ptr data_buffer; + quic::HttpEncoder encoder; + quic::QuicByteCount data_frame_header_length = + encoder.SerializeDataFrameHeader(payload.length(), &data_buffer); + quic::QuicStringPiece data_frame_header(data_buffer.get(), data_frame_header_length); + data = absl::StrCat(data_frame_header, payload); + } + quic::QuicStreamFrame frame(stream_id_, fin, 0, data); + quic_stream_->OnStreamFrame(frame); + return data.length(); + } + protected: Api::ApiPtr api_; Event::DispatcherPtr dispatcher_; @@ -167,38 +200,7 @@ INSTANTIATE_TEST_SUITE_P(EnvoyQuicServerStreamTests, EnvoyQuicServerStreamTest, testing::ValuesIn({true, false})); TEST_P(EnvoyQuicServerStreamTest, PostRequestAndResponse) { - EXPECT_CALL(stream_decoder_, decodeHeaders_(_, /*end_stream=*/false)) - .WillOnce(Invoke([this](const Http::HeaderMapPtr& headers, bool) { - EXPECT_EQ(host_, headers->Host()->value().getStringView()); - EXPECT_EQ("/", headers->Path()->value().getStringView()); - EXPECT_EQ(Http::Headers::get().MethodValues.Get, - headers->Method()->value().getStringView()); - })); - if (quic_version_.transport_version == quic::QUIC_VERSION_99) { - quic_stream_->OnHeadersDecoded(request_headers_); - } else { - quic_stream_->OnStreamHeaderList(/*fin=*/false, request_headers_.uncompressed_header_bytes(), - request_headers_); - } - EXPECT_TRUE(quic_stream_->FinishedReadingHeaders()); - - EXPECT_CALL(stream_decoder_, decodeData(_, _)) - .WillOnce(Invoke([this](Buffer::Instance& buffer, bool finished_reading) { - EXPECT_EQ(request_body_, buffer.toString()); - EXPECT_TRUE(finished_reading); - })); - std::string data = request_body_; - if (quic_version_.transport_version == quic::QUIC_VERSION_99) { - std::unique_ptr data_buffer; - quic::HttpEncoder encoder; - quic::QuicByteCount data_frame_header_length = - encoder.SerializeDataFrameHeader(request_body_.length(), &data_buffer); - quic::QuicStringPiece data_frame_header(data_buffer.get(), data_frame_header_length); - data = absl::StrCat(data_frame_header, request_body_); - } - quic::QuicStreamFrame frame(stream_id_, true, 0, data); - quic_stream_->OnStreamFrame(frame); - + sendRequest(request_body_, true, request_body_.size() * 2); quic_stream_->encodeHeaders(response_headers_, /*end_stream=*/true); } @@ -301,7 +303,54 @@ TEST_P(EnvoyQuicServerStreamTest, OutOfOrderTrailers) { quic_stream_->OnStreamFrame(frame); } -TEST_P(EnvoyQuicServerStreamTest, WatermarkSendBuffer) { +TEST_P(EnvoyQuicServerStreamTest, ReadDisableUponLargePost) { + std::string large_request(1024, 'a'); + size_t payload_offset = sendRequest(large_request, false, 512); + EXPECT_FALSE(quic_stream_->HasBytesToRead()); + std::string second_part_request("bbb"); + if (quic_version_.transport_version == quic::QUIC_VERSION_99) { + std::unique_ptr data_buffer; + quic::HttpEncoder encoder; + quic::QuicByteCount data_frame_header_length = + encoder.SerializeDataFrameHeader(second_part_request.length(), &data_buffer); + quic::QuicStringPiece data_frame_header(data_buffer.get(), data_frame_header_length); + second_part_request = absl::StrCat(data_frame_header, second_part_request); + } + // Receiving more data shouldn't push the receiving pipe line as the stream + // should have been marked blocked. + quic::QuicStreamFrame frame(stream_id_, false, payload_offset, second_part_request); + EXPECT_CALL(stream_decoder_, decodeData(_, _)).Times(0); + quic_stream_->OnStreamFrame(frame); + + // This data frame should also be buffered. + std::string last_part_request("ccc"); + if (quic_version_.transport_version == quic::QUIC_VERSION_99) { + std::unique_ptr data_buffer; + quic::HttpEncoder encoder; + quic::QuicByteCount data_frame_header_length = + encoder.SerializeDataFrameHeader(last_part_request.length(), &data_buffer); + quic::QuicStringPiece data_frame_header(data_buffer.get(), data_frame_header_length); + last_part_request = absl::StrCat(data_frame_header, last_part_request); + } + quic::QuicStreamFrame frame2(stream_id_, true, payload_offset + second_part_request.length(), + last_part_request); + quic_stream_->OnStreamFrame(frame2); + + // Unblock stream now. The remaining data in the receiving buffer should be + // pushed to upstream. + EXPECT_CALL(stream_decoder_, decodeData(_, _)) + .WillOnce(Invoke([](Buffer::Instance& buffer, bool finished_reading) { + std::string rest_request = "bbbccc"; + EXPECT_EQ(rest_request.size(), buffer.length()); + EXPECT_EQ(rest_request, buffer.toString()); + EXPECT_TRUE(finished_reading); + })); + quic_stream_->readDisable(false); + EXPECT_CALL(stream_callbacks_, onResetStream(_, _)); +} + +// Tests that ReadDisable() doesn't cause re-entry of OnBodyAvailable(). +TEST_P(EnvoyQuicServerStreamTest, ReadDisableAndReEnableImmediately) { EXPECT_CALL(stream_decoder_, decodeHeaders_(_, /*end_stream=*/false)) .WillOnce(Invoke([this](const Http::HeaderMapPtr& headers, bool) { EXPECT_EQ(host_, headers->Host()->value().getStringView()); @@ -317,24 +366,54 @@ TEST_P(EnvoyQuicServerStreamTest, WatermarkSendBuffer) { } EXPECT_TRUE(quic_stream_->FinishedReadingHeaders()); + std::string payload(1024, 'a'); EXPECT_CALL(stream_decoder_, decodeData(_, _)) - .WillOnce(Invoke([this](Buffer::Instance& buffer, bool finished_reading) { - EXPECT_EQ(request_body_, buffer.toString()); - EXPECT_TRUE(finished_reading); + .WillOnce(Invoke([&](Buffer::Instance& buffer, bool finished_reading) { + EXPECT_EQ(payload, buffer.toString()); + EXPECT_FALSE(finished_reading); + quic_stream_->readDisable(true); + // Re-enable reading should not trigger another decodeData. + quic_stream_->readDisable(false); })); - std::string data = request_body_; + std::string data = payload; if (quic_version_.transport_version == quic::QUIC_VERSION_99) { std::unique_ptr data_buffer; quic::HttpEncoder encoder; quic::QuicByteCount data_frame_header_length = - encoder.SerializeDataFrameHeader(request_body_.length(), &data_buffer); + encoder.SerializeDataFrameHeader(payload.length(), &data_buffer); quic::QuicStringPiece data_frame_header(data_buffer.get(), data_frame_header_length); - data = absl::StrCat(data_frame_header, request_body_); + data = absl::StrCat(data_frame_header, payload); } - quic::QuicStreamFrame frame(stream_id_, true, 0, data); + quic::QuicStreamFrame frame(stream_id_, false, 0, data); quic_stream_->OnStreamFrame(frame); - response_headers_.addCopy(":content-length", "32770"); // 32KB + 2 byte + std::string last_part_request("bbb"); + if (quic_version_.transport_version == quic::QUIC_VERSION_99) { + std::unique_ptr data_buffer; + quic::HttpEncoder encoder; + quic::QuicByteCount data_frame_header_length = + encoder.SerializeDataFrameHeader(last_part_request.length(), &data_buffer); + quic::QuicStringPiece data_frame_header(data_buffer.get(), data_frame_header_length); + last_part_request = absl::StrCat(data_frame_header, last_part_request); + } + quic::QuicStreamFrame frame2(stream_id_, true, data.length(), last_part_request); + EXPECT_CALL(stream_decoder_, decodeData(_, _)) + .WillOnce(Invoke([&](Buffer::Instance& buffer, bool finished_reading) { + EXPECT_EQ("bbb", buffer.toString()); + EXPECT_TRUE(finished_reading); + })); + + quic_stream_->OnStreamFrame(frame2); + EXPECT_CALL(stream_callbacks_, onResetStream(_, _)); +} + +// Tests that the stream with a send buffer whose high limit is 16k and low +// limit is 8k sends over 32kB response. +TEST_P(EnvoyQuicServerStreamTest, WatermarkSendBuffer) { + sendRequest(request_body_, true, request_body_.size() * 2); + + // 32KB + 2 byte. The initial stream flow control window is 16k. + response_headers_.addCopy(":content-length", "32770"); quic_stream_->encodeHeaders(response_headers_, /*end_stream=*/false); // encode 32kB response body. first 16KB should be written out right away. The // rest should be buffered. The high watermark is 16KB, so this call should From 8a2740d90988c1e07f5bd51fc409f142d7e592db Mon Sep 17 00:00:00 2001 From: Dan Zhang Date: Mon, 14 Oct 2019 12:08:56 -0400 Subject: [PATCH 16/76] rename var Signed-off-by: Dan Zhang --- .../quic_listeners/quiche/envoy_quic_server_stream.cc | 8 ++++---- .../extensions/quic_listeners/quiche/envoy_quic_stream.h | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_server_stream.cc b/source/extensions/quic_listeners/quiche/envoy_quic_server_stream.cc index 8e743a7adaac8..39c2e16c22ed6 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_server_stream.cc +++ b/source/extensions/quic_listeners/quiche/envoy_quic_server_stream.cc @@ -123,8 +123,8 @@ void EnvoyQuicServerStream::OnInitialHeadersComplete(bool fin, size_t frame_len, void EnvoyQuicServerStream::OnBodyAvailable() { ASSERT(FinishedReadingHeaders()); ASSERT(read_disable_counter_ == 0); - ASSERT(!in_encode_data_callstack_); - in_encode_data_callstack_ = true; + ASSERT(!in_decode_data_callstack_); + in_decode_data_callstack_ = true; Buffer::InstancePtr buffer = std::make_unique(); // TODO(danzh): check Envoy per stream buffer limit. @@ -155,7 +155,7 @@ void EnvoyQuicServerStream::OnBodyAvailable() { } if (!sequencer()->IsClosed()) { - in_encode_data_callstack_ = false; + in_decode_data_callstack_ = false; if (read_disable_counter_ > 0) { // If readDisable() was ever called during decodeData() and it meant to disable // reading from downstream, the call must have been deferred. Call it now. @@ -172,7 +172,7 @@ void EnvoyQuicServerStream::OnBodyAvailable() { MarkTrailersConsumed(); } OnFinRead(); - in_encode_data_callstack_ = false; + in_decode_data_callstack_ = false; } void EnvoyQuicServerStream::OnTrailingHeadersComplete(bool fin, size_t frame_len, diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_stream.h b/source/extensions/quic_listeners/quiche/envoy_quic_stream.h index 0ff2768386251..977d9ea46ffc1 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_stream.h +++ b/source/extensions/quic_listeners/quiche/envoy_quic_stream.h @@ -41,7 +41,7 @@ class EnvoyQuicStream : public Http::StreamEncoder, } } - if (status_changed && !in_encode_data_callstack_) { + if (status_changed && !in_decode_data_callstack_) { switchStreamBlockState(disable); } } @@ -77,7 +77,7 @@ class EnvoyQuicStream : public Http::StreamEncoder, int32_t read_disable_counter_{0}; // If true, switchStreamBlockState() should be deferred till this variable // becomes false. - bool in_encode_data_callstack_{false}; + bool in_decode_data_callstack_{false}; private: // Not owned. From e43a1fe82fe3ae178103592ccce62b4f0951f526 Mon Sep 17 00:00:00 2001 From: Dan Zhang Date: Mon, 14 Oct 2019 12:31:11 -0400 Subject: [PATCH 17/76] typo Signed-off-by: Dan Zhang --- .../quic_listeners/quiche/envoy_quic_server_session_test.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/extensions/quic_listeners/quiche/envoy_quic_server_session_test.cc b/test/extensions/quic_listeners/quiche/envoy_quic_server_session_test.cc index 8a9d9fd7ca23b..0818d5daa21a6 100644 --- a/test/extensions/quic_listeners/quiche/envoy_quic_server_session_test.cc +++ b/test/extensions/quic_listeners/quiche/envoy_quic_server_session_test.cc @@ -539,7 +539,7 @@ TEST_P(EnvoyQuicServerSessionTest, SendBufferWatermark) { stream2->OnStreamHeaderList(/*fin=*/true, request_headers.uncompressed_header_bytes(), request_headers); stream2->encodeHeaders(response_headers, false); - // This reponse will trigger both stream and connection's send buffer watermark upper limits. + // This response will trigger both stream and connection's send buffer watermark upper limits. Buffer::OwnedImpl buffer2(response); EXPECT_CALL(network_connection_callbacks_, onAboveWriteBufferHighWatermark) .WillOnce(Invoke( From 438a8d7a122cd6a9c49961d6cde246d4fcb49fe0 Mon Sep 17 00:00:00 2001 From: Dan Zhang Date: Mon, 14 Oct 2019 16:22:12 -0400 Subject: [PATCH 18/76] bufferLimit Signed-off-by: Dan Zhang --- .../quiche/envoy_quic_simulated_watermark_buffer.h | 2 ++ .../quic_listeners/quiche/envoy_quic_stream.h | 13 ++++++++----- .../quiche/quic_filter_manager_connection_impl.h | 4 ++++ 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_simulated_watermark_buffer.h b/source/extensions/quic_listeners/quiche/envoy_quic_simulated_watermark_buffer.h index 025f9e1e576b8..92e8378e35f13 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_simulated_watermark_buffer.h +++ b/source/extensions/quic_listeners/quiche/envoy_quic_simulated_watermark_buffer.h @@ -24,6 +24,8 @@ class EnvoyQuicSimulatedWatermarkBuffer { ASSERT((high_watermark == 0 && low_watermark == 0) || (high_watermark_ > low_watermark_)); } + uint32_t highWatermark() const { return high_watermark_; } + void checkHighWatermark(uint32_t bytes_buffered) { if (high_watermark_ > 0 && !is_above_high_watermark_ && bytes_buffered > high_watermark_) { // Just exceeds high watermark. diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_stream.h b/source/extensions/quic_listeners/quiche/envoy_quic_stream.h index 977d9ea46ffc1..2bd7b910120d7 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_stream.h +++ b/source/extensions/quic_listeners/quiche/envoy_quic_stream.h @@ -51,7 +51,7 @@ class EnvoyQuicStream : public Http::StreamEncoder, addCallbacks_(callbacks); } void removeCallbacks(Http::StreamCallbacks& callbacks) override { removeCallbacks_(callbacks); } - uint32_t bufferLimit() override { return quic::kStreamReceiveWindowLimit; } + uint32_t bufferLimit() override { return send_buffer_simulation_.highWatermark(); } // Needs to be called during quic stream creation before the stream receives // any headers and data. @@ -82,10 +82,13 @@ class EnvoyQuicStream : public Http::StreamEncoder, private: // Not owned. Http::StreamDecoder* decoder_{nullptr}; - // Keeps the buffer state of the connection, and react upon the changes of how many bytes are - // buffered cross all streams' send buffer. The state is evaluated and may be changed upon each - // stream write. QUICHE doesn't buffer data in connection, all the data is buffered in stream's - // send buffer. + // Keeps track of bytes buffered in the stream send buffer in QUICHE and reacts + // upon crossing high and low wathermarks. + // Its high watermark is also the buffer limit of stream read/write filters in + // HCM. + // There is no receiv buffer simulation because Quic stream's + // OnBodyDataAvailable() hands all the ready-to-use request data from stream sequencer to HCM + // directly and buffers them in filters if needed. Itself doesn't buffer request data. EnvoyQuicSimulatedWatermarkBuffer send_buffer_simulation_; }; diff --git a/source/extensions/quic_listeners/quiche/quic_filter_manager_connection_impl.h b/source/extensions/quic_listeners/quiche/quic_filter_manager_connection_impl.h index fe4ab76ee86ef..ad4d5c2629c40 100644 --- a/source/extensions/quic_listeners/quiche/quic_filter_manager_connection_impl.h +++ b/source/extensions/quic_listeners/quiche/quic_filter_manager_connection_impl.h @@ -130,6 +130,10 @@ class QuicFilterManagerConnectionImpl : public Network::FilterManagerConnection, std::string transport_failure_reason_; const uint64_t id_; uint32_t bytes_to_send_{0}; + // Keeps the buffer state of the connection, and react upon the changes of how many bytes are + // buffered cross all streams' send buffer. The state is evaluated and may be changed upon each + // stream write. QUICHE doesn't buffer data in connection, all the data is buffered in stream's + // send buffer. EnvoyQuicSimulatedWatermarkBuffer write_buffer_watermark_simulation_; }; From 4056e858cbcc4e01c3ddab298c759d8ef6ebcec6 Mon Sep 17 00:00:00 2001 From: Dan Zhang Date: Mon, 14 Oct 2019 17:47:59 -0400 Subject: [PATCH 19/76] typo Signed-off-by: Dan Zhang --- source/extensions/quic_listeners/quiche/envoy_quic_stream.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_stream.h b/source/extensions/quic_listeners/quiche/envoy_quic_stream.h index 2bd7b910120d7..13349fd87b0fa 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_stream.h +++ b/source/extensions/quic_listeners/quiche/envoy_quic_stream.h @@ -83,10 +83,10 @@ class EnvoyQuicStream : public Http::StreamEncoder, // Not owned. Http::StreamDecoder* decoder_{nullptr}; // Keeps track of bytes buffered in the stream send buffer in QUICHE and reacts - // upon crossing high and low wathermarks. + // upon crossing high and low watermarks. // Its high watermark is also the buffer limit of stream read/write filters in // HCM. - // There is no receiv buffer simulation because Quic stream's + // There is no receive buffer simulation because Quic stream's // OnBodyDataAvailable() hands all the ready-to-use request data from stream sequencer to HCM // directly and buffers them in filters if needed. Itself doesn't buffer request data. EnvoyQuicSimulatedWatermarkBuffer send_buffer_simulation_; From b6b10aca1d2e410c8a92c9856ba197d4075b377d Mon Sep 17 00:00:00 2001 From: Dan Zhang Date: Tue, 15 Oct 2019 19:31:31 -0400 Subject: [PATCH 20/76] add more Signed-off-by: Dan Zhang --- docs/quiche_integration.md | 49 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 docs/quiche_integration.md diff --git a/docs/quiche_integration.md b/docs/quiche_integration.md new file mode 100644 index 0000000000000..beb98afd51c53 --- /dev/null +++ b/docs/quiche_integration.md @@ -0,0 +1,49 @@ +### Overview + +QUICHE is integrated in the way described below: + +A combination of QUIC session and connection serves as a Network::Connection instance. More than that, QUIC session manages all the QUIC streams. QUIC codec is a very thin layer between QUIC session and HCM. It doesn't do de-multiplexing stream management, but only provides interfaces for HCM to communicate with QUIC session. + +QUIC's Http::StreamEncoder and Http::StreamDecoder implementation is decoupled, encoder is implemented by EnvoyQuicStream which is a QUIC stream and owned by session. HCM owns the decoder which can be accessed by QUIC stream instances. And decoder also knows about stream encoder. + +### Request piepline + +QUIC stream calls decodeHeaders() to deliver request headers. Request body needs +to be delivered after headers are delieverd as some QUICHE implementation allows +body to arrive earlier than headers. If not read disabled, always deliever available data via decodeData(). Stream +doesn't buffer any readable data in QUICHE stream buffers. + +### Response pipeline + +HCM will call encoder's encodeHeaders() to write response headers, and then encodeData() and encodeTrailers(). encodeData() calls WriteBodySlices() to write response body. Quic stream in QUICHE can config it's send buffer threshold QuicStream::buffered_data_threshold_ to be high enough to take all the data passed in, so stream can have unlimited buffer. + +### Flow Control + +## Receive buffer + +All the arrived out-of-order data is buffered in QUICH stream. And this buffered +in capped by max stream flow control window in QUICHE which is 64MB. Once they are put +in sequence and ready to be used, OnBodyDataAvailable() is called. Our stream +implementation overrides this call and call StreamDecoder::decodeData() in it. +Request and response body is buffered in each L7 filter if desired, and the +stream itself doesn't buffer any of them if not set to be blocked. + +When upstream or any L7 filter reaches its buffer limit, it call +Http::Stream::readDisable() with false to set QUIC stream in block state. In +this state, even more request/reponse body is available to be delivered, +OnBodyDataAvailable() will not be called. As a result, downstream flow contol +will not shift as no data will be consumed. As both filters and upstream buffer +can call readDisable(), each stream has a counter of how many +times the HCM wants to block the stream. Until the counter is cleared, stream +will resume its state to be unblocked and thus delivering any new and existing +available data in QUICHE stream. + +## Send buffer + +We use the unlimited stream send buffer in QUICHE along with a book keeping data structure `EnvoyQuicSimulatedWatermarkBffer` to server the purpose of WatermarkBuffer in Envoy to prevent buffering too much in QUICHE. + +When the bytes buffered in a stream's send buffer exceeds its high watermark, its inherited method StreamCallbackHelper::runHighWatermarkCallbacks() is called. The buffered bytes will go below stream's low watermark as the stream writes out data gradually via QuicStream::OnCanWrite(). And in this case StreamCallbackHelper::runLowWatermarkCallbacks() will be called. QUICHE buffers all the data upon QuicSpdyStream::WriteBodySlices(), assuming `buffered_data_threshold_` is set high enough, and then writes all or part of them according to flow control window and congestion control window. To prevent transient changes in buffered bytes from triggering these two watermark callbacks one immediately after the other, encodeData() and OnCanWrite() only update the watermark book keeping once at the end if buffered bytes are changed. + +QUICHE doesn't buffer data in connection. And all the data is buffered in stream.To prevent the case where all streams collectively buffers a lot of data, there is also a simulated watermark buffer for each Quic connection which changes upon each stream write. + +When the aggregated buffered bytes goes above high watermark, its registered network callbacks will call Network::ConnectionCallbacks::onAboveWriteBufferHighWatermark(). HCM as a such callback will notify each stream via QUIC codec Http::Connection::onUnderlyingConnectionAboveWriteBufferHighWatermark() which will call each stream's StreamCallbackHelper::runHighWatermarkCallbacks(). There might be a way to simply the call stack as Quic connection already knows about all the stream, there is no need to call to HCM and notify each stream via codec. But here we just follow the same logic as HTTP2 codec does. Same way, any QuicStream::OnCanWrite() may change the aggregated buffered bytes in the connection level book keeping as well. If the buffered bytes goes down below low watermark, same calls will be triggered to propergate onBelowWriteBufferLowWatermark() to each stream callbacks. From f7eb6e344af54beb524f7c32f612719fee037d0f Mon Sep 17 00:00:00 2001 From: Dan Zhang Date: Wed, 16 Oct 2019 15:14:28 -0400 Subject: [PATCH 21/76] adjust send buffer threshold, modify doc Signed-off-by: Dan Zhang --- docs/quiche_integration.md | 40 ++++++------------- .../quiche/envoy_quic_dispatcher.cc | 7 ++++ .../quiche/envoy_quic_server_stream.cc | 32 +++++++-------- .../envoy_quic_simulated_watermark_buffer.h | 4 +- .../quic_listeners/quiche/envoy_quic_stream.h | 18 ++++++++- .../quic_filter_manager_connection_impl.cc | 7 ++-- .../quic_filter_manager_connection_impl.h | 2 + .../quiche/envoy_quic_server_stream_test.cc | 7 +++- 8 files changed, 64 insertions(+), 53 deletions(-) diff --git a/docs/quiche_integration.md b/docs/quiche_integration.md index beb98afd51c53..4a60403cc50ca 100644 --- a/docs/quiche_integration.md +++ b/docs/quiche_integration.md @@ -2,48 +2,32 @@ QUICHE is integrated in the way described below: -A combination of QUIC session and connection serves as a Network::Connection instance. More than that, QUIC session manages all the QUIC streams. QUIC codec is a very thin layer between QUIC session and HCM. It doesn't do de-multiplexing stream management, but only provides interfaces for HCM to communicate with QUIC session. +A combination of QUIC session and connection serves as a Network::Connection instance. More than that, QUIC session manages all the QUIC streams. The QUIC codec is a very thin layer between QUIC session and HCM. It doesn't do de-multiplexing or stream management, but only provides interfaces for the HCM to communicate with the QUIC session. -QUIC's Http::StreamEncoder and Http::StreamDecoder implementation is decoupled, encoder is implemented by EnvoyQuicStream which is a QUIC stream and owned by session. HCM owns the decoder which can be accessed by QUIC stream instances. And decoder also knows about stream encoder. +QUIC's Http::StreamEncoder and Http::StreamDecoder implementation is decoupled. The encoder is implemented by EnvoyQuicStream which is a QUIC stream and owned by session. The HCM owns the decoder which can be accessed by QUIC stream instances. And the decoder also knows about stream encoder. -### Request piepline +### Request pipeline -QUIC stream calls decodeHeaders() to deliver request headers. Request body needs -to be delivered after headers are delieverd as some QUICHE implementation allows -body to arrive earlier than headers. If not read disabled, always deliever available data via decodeData(). Stream -doesn't buffer any readable data in QUICHE stream buffers. +The QUIC stream calls decodeHeaders() to deliver request headers. The request body needs to be delivered after headers are delivered as some QUICHE implementations allow the body to arrive earlier than headers. If not read disabled, it always deliver available data via decodeData(). The stream doesn't buffer any readable data in QUICHE stream buffers. ### Response pipeline -HCM will call encoder's encodeHeaders() to write response headers, and then encodeData() and encodeTrailers(). encodeData() calls WriteBodySlices() to write response body. Quic stream in QUICHE can config it's send buffer threshold QuicStream::buffered_data_threshold_ to be high enough to take all the data passed in, so stream can have unlimited buffer. +The HCM will call encoder's encodeHeaders() to write response headers, and then encodeData() and encodeTrailers(). encodeData() calls WriteBodySlices() to write out response body. The quic stream in QUICHE configures its send buffer threshold QuicStream::buffered_data_threshold_ to be high enough to take all the data passed in, so stream functionally has unlimited buffer. ### Flow Control ## Receive buffer -All the arrived out-of-order data is buffered in QUICH stream. And this buffered -in capped by max stream flow control window in QUICHE which is 64MB. Once they are put -in sequence and ready to be used, OnBodyDataAvailable() is called. Our stream -implementation overrides this call and call StreamDecoder::decodeData() in it. -Request and response body is buffered in each L7 filter if desired, and the -stream itself doesn't buffer any of them if not set to be blocked. - -When upstream or any L7 filter reaches its buffer limit, it call -Http::Stream::readDisable() with false to set QUIC stream in block state. In -this state, even more request/reponse body is available to be delivered, -OnBodyDataAvailable() will not be called. As a result, downstream flow contol -will not shift as no data will be consumed. As both filters and upstream buffer -can call readDisable(), each stream has a counter of how many -times the HCM wants to block the stream. Until the counter is cleared, stream -will resume its state to be unblocked and thus delivering any new and existing -available data in QUICHE stream. +All the arrived out-of-order data is buffered in QUICHE stream. This buffered is capped by max stream flow control window in QUICHE which is 64MB. Once bytes are put in sequence and ready to be used, OnBodyDataAvailable() is called. The stream implementation overrides this call and calls StreamDecoder::decodeData() in it.Request and response body are buffered in each L7 filter if desired, and the stream itself doesn't buffer any of them unless set as read blocked. + +When upstream or any L7 filter reaches its buffer limit, it will call Http::Stream::readDisable() with false to set QUIC stream to be read blocked. In this state, even if more request/response body is available to be delivered, OnBodyDataAvailable() will not be called. As a result, downstream flow control will not shift as no data will be consumed. As both filters and upstream buffers can call readDisable(), each stream has a counter of how many times the HCM blocks the stream. When the counter is cleared, the stream will set its state to unblocked and thus deliver any new and existing available data buttered in the QUICHE stream. ## Send buffer -We use the unlimited stream send buffer in QUICHE along with a book keeping data structure `EnvoyQuicSimulatedWatermarkBffer` to server the purpose of WatermarkBuffer in Envoy to prevent buffering too much in QUICHE. +We use the unlimited stream send buffer in QUICHE along with a book keeping data structure `EnvoyQuicSimulatedWatermarkBffer` to serve the function of WatermarkBuffer in Envoy to prevent buffering too much in QUICHE. -When the bytes buffered in a stream's send buffer exceeds its high watermark, its inherited method StreamCallbackHelper::runHighWatermarkCallbacks() is called. The buffered bytes will go below stream's low watermark as the stream writes out data gradually via QuicStream::OnCanWrite(). And in this case StreamCallbackHelper::runLowWatermarkCallbacks() will be called. QUICHE buffers all the data upon QuicSpdyStream::WriteBodySlices(), assuming `buffered_data_threshold_` is set high enough, and then writes all or part of them according to flow control window and congestion control window. To prevent transient changes in buffered bytes from triggering these two watermark callbacks one immediately after the other, encodeData() and OnCanWrite() only update the watermark book keeping once at the end if buffered bytes are changed. +When the bytes buffered in a stream's send buffer exceeds its high watermark, its inherited method StreamCallbackHelper::runHighWatermarkCallbacks() is called. The buffered bytes will go below stream's low watermark as the stream writes out data gradually via QuicStream::OnCanWrite(). In this case StreamCallbackHelper::runLowWatermarkCallbacks() will be called. QUICHE buffers all the data upon QuicSpdyStream::WriteBodySlices(), assuming `buffered_data_threshold_` is set high enough, and then writes all or part of them according to flow control window and congestion control window. To prevent transient changes in buffered bytes from triggering these two watermark callbacks one immediately after the other, encodeData() and OnCanWrite() only update the watermark bookkeeping once at the end if buffered bytes are changed. -QUICHE doesn't buffer data in connection. And all the data is buffered in stream.To prevent the case where all streams collectively buffers a lot of data, there is also a simulated watermark buffer for each Quic connection which changes upon each stream write. +QUICHE doesn't buffer data at the local connection layer. All the data is buffered in the respective streams.To prevent the case where all streams collectively buffers a lot of data, there is also a simulated watermark buffer for each QUIC connection which is updated upon each stream write. -When the aggregated buffered bytes goes above high watermark, its registered network callbacks will call Network::ConnectionCallbacks::onAboveWriteBufferHighWatermark(). HCM as a such callback will notify each stream via QUIC codec Http::Connection::onUnderlyingConnectionAboveWriteBufferHighWatermark() which will call each stream's StreamCallbackHelper::runHighWatermarkCallbacks(). There might be a way to simply the call stack as Quic connection already knows about all the stream, there is no need to call to HCM and notify each stream via codec. But here we just follow the same logic as HTTP2 codec does. Same way, any QuicStream::OnCanWrite() may change the aggregated buffered bytes in the connection level book keeping as well. If the buffered bytes goes down below low watermark, same calls will be triggered to propergate onBelowWriteBufferLowWatermark() to each stream callbacks. +When the aggregated buffered bytes goes above high watermark, its registered network callbacks will call Network::ConnectionCallbacks::onAboveWriteBufferHighWatermark(). The HCMwill notify each stream via QUIC codec Http::Connection::onUnderlyingConnectionAboveWriteBufferHighWatermark() which will call each stream's StreamCallbackHelper::runHighWatermarkCallbacks(). There might be a way to simply the call stack as Quic connection already knows about all the stream, there is no need to call to HCM and notify each stream via codec. But here we just follow the same logic as HTTP2 codec does. In the same way, any QuicStream::OnCanWrite() may change the aggregated buffered bytes in the connection level bookkeeping as well. If the buffered bytes goes down below the low watermark, the same calls will be triggered to propergate onBelowWriteBufferLowWatermark() to each stream. diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_dispatcher.cc b/source/extensions/quic_listeners/quiche/envoy_quic_dispatcher.cc index b4f103469d818..2f9956681b176 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_dispatcher.cc +++ b/source/extensions/quic_listeners/quiche/envoy_quic_dispatcher.cc @@ -25,6 +25,13 @@ EnvoyQuicDispatcher::EnvoyQuicDispatcher( // Network::UdpListenerCallbacks which should be called at the beginning // of HandleReadEvent(). And this callback should call quic::Dispatcher::ProcessBufferedChlos(). SetQuicFlag(FLAGS_quic_allow_chlo_buffering, false); + // Set send buffer twice of max flow control window to ensure that stream send + // buffer always takes all the data. + // The max amount of data buffered is the per-stream high watermark + the max + // flow control window of upstream. The per-stream high watermark should be + // smaller than max flow control window to make sure upper stream can be flow + // control blocked early enough not to send more than the threshold allows. + SetQuicFlag(FLAGS_quic_buffered_data_threshold, 2 * 16 * 1024 * 1024); // 32MB } void EnvoyQuicDispatcher::OnConnectionClosed(quic::QuicConnectionId connection_id, diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_server_stream.cc b/source/extensions/quic_listeners/quiche/envoy_quic_server_stream.cc index 39c2e16c22ed6..8a072def74d62 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_server_stream.cc +++ b/source/extensions/quic_listeners/quiche/envoy_quic_server_stream.cc @@ -31,15 +31,23 @@ EnvoyQuicServerStream::EnvoyQuicServerStream(quic::QuicStreamId id, quic::QuicSp quic::StreamType type) : quic::QuicSpdyServerStreamBase(id, session, type), EnvoyQuicStream( - 2 * GetQuicFlag(FLAGS_quic_buffered_data_threshold), - [this]() { runLowWatermarkCallbacks(); }, [this]() { runHighWatermarkCallbacks(); }) {} + // This should be larger than 8k to fully utilize congestion control + // window. And no larger than the max stream flow control window for + // the stream to buffer all the data. + // Ideally this limit should also corelate to peer's receive window + // but not fully depends on that. + 16 * 1024, [this]() { runLowWatermarkCallbacks(); }, + [this]() { runHighWatermarkCallbacks(); }) {} EnvoyQuicServerStream::EnvoyQuicServerStream(quic::PendingStream* pending, quic::QuicSpdySession* session, quic::StreamType type) : quic::QuicSpdyServerStreamBase(pending, session, type), EnvoyQuicStream( - session->config()->ReceivedInitialStreamFlowControlWindowBytes(), - [this]() { runLowWatermarkCallbacks(); }, [this]() { runHighWatermarkCallbacks(); }) {} + // This should be larger than 8k to fully utilize congestion control + // window. And no larger than the max stream flow control window for + // the stream to buffer all the data. + 16 * 1024, [this]() { runLowWatermarkCallbacks(); }, + [this]() { runHighWatermarkCallbacks(); }) {} void EnvoyQuicServerStream::encode100ContinueHeaders(const Http::HeaderMap& headers) { ASSERT(headers.Status()->value() == "100"); @@ -64,13 +72,8 @@ void EnvoyQuicServerStream::encodeData(Buffer::Instance& data, bool end_stream) uint64_t bytes_to_send_new = BufferedDataBytes(); ASSERT(bytes_to_send_old <= bytes_to_send_new); - if (bytes_to_send_new > bytes_to_send_old) { - // If buffered bytes changed, update stream and session's watermark book - // keeping. - sendBufferSimulation().checkHighWatermark(bytes_to_send_new); - dynamic_cast(session())->adjustBytesToSend(bytes_to_send_new - - bytes_to_send_old); - } + maybeCheckWatermark(bytes_to_send_old, bytes_to_send_new, + dynamic_cast(*session())); } void EnvoyQuicServerStream::encodeTrailers(const Http::HeaderMap& trailers) { @@ -207,11 +210,8 @@ void EnvoyQuicServerStream::OnCanWrite() { // As long as OnCanWriteNewData() is no-op, data to sent in buffer shouldn't // increase. ASSERT(buffered_data_new <= buffered_data_old); - if (buffered_data_new < buffered_data_old) { - sendBufferSimulation().checkLowWatermark(buffered_data_new); - dynamic_cast(session())->adjustBytesToSend(buffered_data_new - - buffered_data_old); - } + maybeCheckWatermark(buffered_data_old, buffered_data_new, + dynamic_cast(*session())); } uint32_t EnvoyQuicServerStream::streamId() { return id(); } diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_simulated_watermark_buffer.h b/source/extensions/quic_listeners/quiche/envoy_quic_simulated_watermark_buffer.h index 92e8378e35f13..68ec9ebbea65e 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_simulated_watermark_buffer.h +++ b/source/extensions/quic_listeners/quiche/envoy_quic_simulated_watermark_buffer.h @@ -28,7 +28,7 @@ class EnvoyQuicSimulatedWatermarkBuffer { void checkHighWatermark(uint32_t bytes_buffered) { if (high_watermark_ > 0 && !is_above_high_watermark_ && bytes_buffered > high_watermark_) { - // Just exceeds high watermark. + // Just exceeded the high watermark. ENVOY_LOG_TO_LOGGER(logger_, debug, "Buffered {} bytes, cross high watermark {}", bytes_buffered, high_watermark_); is_above_high_watermark_ = true; @@ -39,7 +39,7 @@ class EnvoyQuicSimulatedWatermarkBuffer { void checkLowWatermark(uint32_t bytes_buffered) { if (low_watermark_ > 0 && !is_below_low_watermark_ && bytes_buffered < low_watermark_) { - // Just cross low watermark. + // Just crossed the low watermark. ENVOY_LOG_TO_LOGGER(logger_, debug, "Buffered {} bytes, cross low watermark {}", bytes_buffered, low_watermark_); is_below_low_watermark_ = true; diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_stream.h b/source/extensions/quic_listeners/quiche/envoy_quic_stream.h index 13349fd87b0fa..c91e55fc20223 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_stream.h +++ b/source/extensions/quic_listeners/quiche/envoy_quic_stream.h @@ -6,6 +6,7 @@ #include "common/http/codec_helper.h" #include "extensions/quic_listeners/quiche/envoy_quic_simulated_watermark_buffer.h" +#include "extensions/quic_listeners/quiche/quic_filter_manager_connection_impl.h" namespace Envoy { namespace Quic { @@ -29,7 +30,6 @@ class EnvoyQuicStream : public Http::StreamEncoder, bool status_changed{false}; if (disable) { ++read_disable_counter_; - ASSERT(read_disable_counter_ == 1); if (read_disable_counter_ == 1) { status_changed = true; } @@ -57,6 +57,21 @@ class EnvoyQuicStream : public Http::StreamEncoder, // any headers and data. void setDecoder(Http::StreamDecoder& decoder) { decoder_ = &decoder; } + void maybeCheckWatermark(uint64_t buffered_data_old, uint64_t buffered_data_new, + QuicFilterManagerConnectionImpl& connection) { + if (buffered_data_new == buffered_data_old) { + return; + } + // If buffered bytes changed, update stream and session's watermark book + // keeping. + if (buffered_data_new > buffered_data_old) { + send_buffer_simulation_.checkHighWatermark(buffered_data_new); + } else { + send_buffer_simulation_.checkLowWatermark(buffered_data_new); + } + connection.adjustBytesToSend(buffered_data_new - buffered_data_old); + } + protected: virtual void switchStreamBlockState(bool should_block) PURE; @@ -69,7 +84,6 @@ class EnvoyQuicStream : public Http::StreamEncoder, return decoder_; } - EnvoyQuicSimulatedWatermarkBuffer& sendBufferSimulation() { return send_buffer_simulation_; } // True once end of stream is propergated to Envoy. Envoy doesn't expect to be // notified more than once about end of stream. So once this is true, no need // to set it in the callback to Envoy stream any more. diff --git a/source/extensions/quic_listeners/quiche/quic_filter_manager_connection_impl.cc b/source/extensions/quic_listeners/quiche/quic_filter_manager_connection_impl.cc index a24b9802c0cb0..04f19bd8f62e7 100644 --- a/source/extensions/quic_listeners/quiche/quic_filter_manager_connection_impl.cc +++ b/source/extensions/quic_listeners/quiche/quic_filter_manager_connection_impl.cc @@ -43,10 +43,9 @@ void QuicFilterManagerConnectionImpl::enableHalfClose(bool enabled) { } void QuicFilterManagerConnectionImpl::setBufferLimits(uint32_t /*limit*/) { - // TODO(danzh): add interface to quic for connection level buffer throttling. - // Currently read buffer is capped by connection level flow control. And - // write buffer limit is set during construction. Change buffer limit during - // the life time of connection is not supported. + // Currently read buffer is capped by connection level flow control. And write buffer limit is set + // during construction. Changing the buffer limit during the life time of the connection is not + // supported. NOT_REACHED_GCOVR_EXCL_LINE; } diff --git a/source/extensions/quic_listeners/quiche/quic_filter_manager_connection_impl.h b/source/extensions/quic_listeners/quiche/quic_filter_manager_connection_impl.h index ad4d5c2629c40..26f5fc6e1bf0e 100644 --- a/source/extensions/quic_listeners/quiche/quic_filter_manager_connection_impl.h +++ b/source/extensions/quic_listeners/quiche/quic_filter_manager_connection_impl.h @@ -97,6 +97,8 @@ class QuicFilterManagerConnectionImpl : public Network::FilterManagerConnection, // Network::WriteBufferSource Network::StreamBuffer getWriteBuffer() override { NOT_REACHED_GCOVR_EXCL_LINE; } + // Update the book keeping of the aggregated buffered bytes cross all the + // streams, and run watermark check. void adjustBytesToSend(int64_t delta); protected: diff --git a/test/extensions/quic_listeners/quiche/envoy_quic_server_stream_test.cc b/test/extensions/quic_listeners/quiche/envoy_quic_server_stream_test.cc index a2b218c6f255b..fc2eb63989263 100644 --- a/test/extensions/quic_listeners/quiche/envoy_quic_server_stream_test.cc +++ b/test/extensions/quic_listeners/quiche/envoy_quic_server_stream_test.cc @@ -94,7 +94,6 @@ class EnvoyQuicServerStreamTest : public testing::TestWithParam { stream_id_(quic_version_.transport_version == quic::QUIC_VERSION_99 ? 4u : 5u), quic_stream_(new EnvoyQuicServerStream(stream_id_, &quic_session_, quic::BIDIRECTIONAL)), response_headers_{{":status", "200"}} { - quic::SetVerbosityLogThreshold(2); quic_stream_->setDecoder(stream_decoder_); quic_stream_->addCallbacks(stream_callbacks_); quic_session_.ActivateStream(std::unique_ptr(quic_stream_)); @@ -305,8 +304,11 @@ TEST_P(EnvoyQuicServerStreamTest, OutOfOrderTrailers) { TEST_P(EnvoyQuicServerStreamTest, ReadDisableUponLargePost) { std::string large_request(1024, 'a'); + // Sending such large request will cause read to be disabled. size_t payload_offset = sendRequest(large_request, false, 512); EXPECT_FALSE(quic_stream_->HasBytesToRead()); + // Disable reading one more time. + quic_stream_->readDisable(true); std::string second_part_request("bbb"); if (quic_version_.transport_version == quic::QUIC_VERSION_99) { std::unique_ptr data_buffer; @@ -322,6 +324,9 @@ TEST_P(EnvoyQuicServerStreamTest, ReadDisableUponLargePost) { EXPECT_CALL(stream_decoder_, decodeData(_, _)).Times(0); quic_stream_->OnStreamFrame(frame); + // Re-enable reading just once shouldn't unblock stream. + quic_stream_->readDisable(false); + // This data frame should also be buffered. std::string last_part_request("ccc"); if (quic_version_.transport_version == quic::QUIC_VERSION_99) { From 75e36e0c50791e04a8440ce5b18e0a790f5bd884 Mon Sep 17 00:00:00 2001 From: Dan Zhang Date: Wed, 16 Oct 2019 17:25:06 -0400 Subject: [PATCH 22/76] above watermark upon stream creation Signed-off-by: Dan Zhang --- .../quic_listeners/quiche/codec_impl.cc | 1 + .../quiche/envoy_quic_server_session.cc | 3 ++ .../quiche/envoy_quic_server_stream.cc | 9 ++++ .../quiche/envoy_quic_server_stream.h | 1 + .../quiche/envoy_quic_server_session_test.cc | 47 ++++++++++++++++++- 5 files changed, 59 insertions(+), 2 deletions(-) diff --git a/source/extensions/quic_listeners/quiche/codec_impl.cc b/source/extensions/quic_listeners/quiche/codec_impl.cc index 6c9ae799b51f3..58ca7197315a5 100644 --- a/source/extensions/quic_listeners/quiche/codec_impl.cc +++ b/source/extensions/quic_listeners/quiche/codec_impl.cc @@ -23,6 +23,7 @@ void QuicHttpServerConnectionImpl::onUnderlyingConnectionAboveWriteBufferHighWat } void QuicHttpServerConnectionImpl::onUnderlyingConnectionBelowWriteBufferLowWatermark() { + std::cerr << "========== onUnderlyingConnectionBelowWriteBufferLowWatermark\n"; for (const auto& it : quic_server_session_.stream_map()) { if (!it.second->is_static()) { ENVOY_LOG(debug, "runLowWatermarkCallbacks on stream {}", it.first); diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_server_session.cc b/source/extensions/quic_listeners/quiche/envoy_quic_server_session.cc index 8a6eba95daa0e..a1a47634ad810 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_server_session.cc +++ b/source/extensions/quic_listeners/quiche/envoy_quic_server_session.cc @@ -48,6 +48,9 @@ quic::QuicSpdyStream* EnvoyQuicServerSession::CreateIncomingStream(quic::QuicStr auto stream = new EnvoyQuicServerStream(id, this, quic::BIDIRECTIONAL); ActivateStream(absl::WrapUnique(stream)); setUpRequestDecoder(*stream); + if (aboveHighWatermark()) { + stream->runHighWatermarkCallbacks(); + } return stream; } diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_server_stream.cc b/source/extensions/quic_listeners/quiche/envoy_quic_server_stream.cc index 8a072def74d62..4b982f3f9d7b9 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_server_stream.cc +++ b/source/extensions/quic_listeners/quiche/envoy_quic_server_stream.cc @@ -203,6 +203,15 @@ void EnvoyQuicServerStream::OnConnectionClosed(quic::QuicErrorCode error, runResetCallbacks(quicErrorCodeToEnvoyResetReason(error)); } +void EnvoyQuicServerStream::OnClose() { + quic::QuicSpdyServerStreamBase::OnClose(); + if (BufferedDataBytes() > 0) { + // If the stream is closed without sending out all bufferred data, regard + // them as sent now and adjust connection buffer book keeping. + dynamic_cast(session())->adjustBytesToSend(0 - BufferedDataBytes()); + } +} + void EnvoyQuicServerStream::OnCanWrite() { uint64_t buffered_data_old = BufferedDataBytes(); quic::QuicSpdyServerStreamBase::OnCanWrite(); diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_server_stream.h b/source/extensions/quic_listeners/quiche/envoy_quic_server_stream.h index e38317907d8ec..38326781be936 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_server_stream.h +++ b/source/extensions/quic_listeners/quiche/envoy_quic_server_stream.h @@ -35,6 +35,7 @@ class EnvoyQuicServerStream : public quic::QuicSpdyServerStreamBase, public Envo // quic::QuicSpdyStream void OnBodyAvailable() override; void OnStreamReset(const quic::QuicRstStreamFrame& frame) override; + void OnClose() override; void OnCanWrite() override; // quic::QuicServerSessionBase void OnConnectionClosed(quic::QuicErrorCode error, quic::ConnectionCloseSource source) override; diff --git a/test/extensions/quic_listeners/quiche/envoy_quic_server_session_test.cc b/test/extensions/quic_listeners/quiche/envoy_quic_server_session_test.cc index 0818d5daa21a6..57536b31900e8 100644 --- a/test/extensions/quic_listeners/quiche/envoy_quic_server_session_test.cc +++ b/test/extensions/quic_listeners/quiche/envoy_quic_server_session_test.cc @@ -548,6 +548,32 @@ TEST_P(EnvoyQuicServerSessionTest, SendBufferWatermark) { EXPECT_CALL(stream_callbacks, onAboveWriteBufferHighWatermark()); stream2->encodeData(buffer2, false); + // Receive another request, the new stream should be notified about connection + // high watermark reached upon creation. + Http::MockStreamDecoder request_decoder3; + Http::MockStreamCallbacks stream_callbacks3; + EXPECT_CALL(http_connection_callbacks_, newStream(_, false)) + .WillOnce(Invoke([&request_decoder3, &stream_callbacks3](Http::StreamEncoder& encoder, + bool) -> Http::StreamDecoder& { + encoder.getStream().addCallbacks(stream_callbacks3); + return request_decoder3; + })); + EXPECT_CALL(stream_callbacks3, onAboveWriteBufferHighWatermark()); + auto stream3 = + dynamic_cast(envoy_quic_session_.GetOrCreateStream(stream_id + 8)); + EXPECT_CALL(request_decoder3, decodeHeaders_(_, /*end_stream=*/true)) + .WillOnce(Invoke([&host](const Http::HeaderMapPtr& decoded_headers, bool) { + EXPECT_EQ(host, decoded_headers->Host()->value().getStringView()); + EXPECT_EQ("/", decoded_headers->Path()->value().getStringView()); + EXPECT_EQ(Http::Headers::get().MethodValues.Get, + decoded_headers->Method()->value().getStringView()); + })); + EXPECT_CALL(request_decoder3, decodeData(_, true)) + .Times(testing::AtMost(1)) + .WillOnce(Invoke([](Buffer::Instance& buffer, bool) { EXPECT_EQ(0, buffer.length()); })); + stream3->OnStreamHeaderList(/*fin=*/true, request_headers.uncompressed_header_bytes(), + request_headers); + // Update flow control window for stream1. quic::QuicWindowUpdateFrame window_update1(quic::kInvalidControlFrameId, stream1->id(), 32 * 1024); @@ -575,13 +601,30 @@ TEST_P(EnvoyQuicServerSessionTest, SendBufferWatermark) { // come down below low watermark. EXPECT_CALL(network_connection_callbacks_, onBelowWriteBufferLowWatermark) .WillOnce(Invoke([this]() { - // This call shouldn't be propagate to streams because they both wrote to end - // of stream. + // This call shouldn't be propagate to stream1 and stream2 because they both wrote to the + // end of stream. http_connection_->onUnderlyingConnectionBelowWriteBufferLowWatermark(); })); + EXPECT_CALL(stream_callbacks3, onBelowWriteBufferLowWatermark()).WillOnce(Invoke([=]() { + std::string super_large_response(40 * 1024, 'a'); + Buffer::OwnedImpl buffer(super_large_response); + // This call will buffer 24k on stream3, raise the buffered bytes above + // high watermarks of the stream and connection. + // But callback will not propogate to stream_callback3 as the steam is + // ended locally. + stream3->encodeData(buffer, true); + })); + EXPECT_CALL(network_connection_callbacks_, onAboveWriteBufferHighWatermark()); envoy_quic_session_.OnCanWrite(); EXPECT_TRUE(stream2->flow_controller()->IsBlocked()); + // Resetting stream3 should lower the bufferred bytes, but callbacks will not + // be triggered because reset callback has been already triggerred. + EXPECT_CALL(stream_callbacks3, onResetStream(Http::StreamResetReason::LocalReset, "")); + // Connection buffered data book keeping should also be updated. + EXPECT_CALL(network_connection_callbacks_, onBelowWriteBufferLowWatermark()); + stream3->resetStream(Http::StreamResetReason::LocalReset); + // Update flow control window for stream1. quic::QuicWindowUpdateFrame window_update3(quic::kInvalidControlFrameId, stream1->id(), 48 * 1024); From 92068a473ec0030e34f5ce56b4847998e086188b Mon Sep 17 00:00:00 2001 From: Dan Zhang Date: Wed, 16 Oct 2019 17:27:33 -0400 Subject: [PATCH 23/76] fix clang_tidy Signed-off-by: Dan Zhang --- source/extensions/quic_listeners/quiche/BUILD | 1 + 1 file changed, 1 insertion(+) diff --git a/source/extensions/quic_listeners/quiche/BUILD b/source/extensions/quic_listeners/quiche/BUILD index a713809722541..91d8eb333b5a5 100644 --- a/source/extensions/quic_listeners/quiche/BUILD +++ b/source/extensions/quic_listeners/quiche/BUILD @@ -98,6 +98,7 @@ envoy_cc_library( tags = ["nofips"], deps = [ ":envoy_quic_simulated_watermark_buffer_lib", + ":quic_filter_manager_connection_lib", "//include/envoy/event:dispatcher_interface", "//include/envoy/http:codec_interface", "//source/common/http:codec_helper_lib", From c6c2177ef3c62fcdad3ce37bf9822438157f337f Mon Sep 17 00:00:00 2001 From: Dan Zhang Date: Thu, 17 Oct 2019 10:47:41 -0400 Subject: [PATCH 24/76] typo Signed-off-by: Dan Zhang --- .../quic_listeners/quiche/envoy_quic_server_session_test.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/extensions/quic_listeners/quiche/envoy_quic_server_session_test.cc b/test/extensions/quic_listeners/quiche/envoy_quic_server_session_test.cc index 57536b31900e8..dc33334d2eff6 100644 --- a/test/extensions/quic_listeners/quiche/envoy_quic_server_session_test.cc +++ b/test/extensions/quic_listeners/quiche/envoy_quic_server_session_test.cc @@ -610,7 +610,7 @@ TEST_P(EnvoyQuicServerSessionTest, SendBufferWatermark) { Buffer::OwnedImpl buffer(super_large_response); // This call will buffer 24k on stream3, raise the buffered bytes above // high watermarks of the stream and connection. - // But callback will not propogate to stream_callback3 as the steam is + // But callback will not propagate to stream_callback3 as the steam is // ended locally. stream3->encodeData(buffer, true); })); From 6175a3421f6b38ef8892d665d7f185d67aac845c Mon Sep 17 00:00:00 2001 From: Dan Zhang Date: Thu, 17 Oct 2019 14:31:55 -0400 Subject: [PATCH 25/76] remove debug log Signed-off-by: Dan Zhang --- source/extensions/quic_listeners/quiche/codec_impl.cc | 1 - 1 file changed, 1 deletion(-) diff --git a/source/extensions/quic_listeners/quiche/codec_impl.cc b/source/extensions/quic_listeners/quiche/codec_impl.cc index 58ca7197315a5..6c9ae799b51f3 100644 --- a/source/extensions/quic_listeners/quiche/codec_impl.cc +++ b/source/extensions/quic_listeners/quiche/codec_impl.cc @@ -23,7 +23,6 @@ void QuicHttpServerConnectionImpl::onUnderlyingConnectionAboveWriteBufferHighWat } void QuicHttpServerConnectionImpl::onUnderlyingConnectionBelowWriteBufferLowWatermark() { - std::cerr << "========== onUnderlyingConnectionBelowWriteBufferLowWatermark\n"; for (const auto& it : quic_server_session_.stream_map()) { if (!it.second->is_static()) { ENVOY_LOG(debug, "runLowWatermarkCallbacks on stream {}", it.first); From 35d4539fbbc24bc09bfe8ff802000184007cdcd3 Mon Sep 17 00:00:00 2001 From: Dan Zhang Date: Thu, 17 Oct 2019 17:01:34 -0400 Subject: [PATCH 26/76] add test for simulated buffer Signed-off-by: Dan Zhang --- .../envoy_quic_simulated_watermark_buffer.h | 12 ++- test/extensions/quic_listeners/quiche/BUILD | 9 +++ ...oy_quic_simulated_watermark_buffer_test.cc | 81 +++++++++++++++++++ 3 files changed, 100 insertions(+), 2 deletions(-) create mode 100644 test/extensions/quic_listeners/quiche/envoy_quic_simulated_watermark_buffer_test.cc diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_simulated_watermark_buffer.h b/source/extensions/quic_listeners/quiche/envoy_quic_simulated_watermark_buffer.h index 68ec9ebbea65e..484cda14da7f6 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_simulated_watermark_buffer.h +++ b/source/extensions/quic_listeners/quiche/envoy_quic_simulated_watermark_buffer.h @@ -28,7 +28,8 @@ class EnvoyQuicSimulatedWatermarkBuffer { void checkHighWatermark(uint32_t bytes_buffered) { if (high_watermark_ > 0 && !is_above_high_watermark_ && bytes_buffered > high_watermark_) { - // Just exceeded the high watermark. + // This is the first time for the buffer to cross the high watermark + // since it was once below low watermark. ENVOY_LOG_TO_LOGGER(logger_, debug, "Buffered {} bytes, cross high watermark {}", bytes_buffered, high_watermark_); is_above_high_watermark_ = true; @@ -39,7 +40,8 @@ class EnvoyQuicSimulatedWatermarkBuffer { void checkLowWatermark(uint32_t bytes_buffered) { if (low_watermark_ > 0 && !is_below_low_watermark_ && bytes_buffered < low_watermark_) { - // Just crossed the low watermark. + // This is the first time for the buffer to cross the low watermark + // since it was once above high watermark. ENVOY_LOG_TO_LOGGER(logger_, debug, "Buffered {} bytes, cross low watermark {}", bytes_buffered, low_watermark_); is_below_low_watermark_ = true; @@ -48,8 +50,14 @@ class EnvoyQuicSimulatedWatermarkBuffer { } } + // True after the buffer goes above high watermark and hasn't come down below low + // watermark yet, even though the buffered data might be between high and low + // watermarks. bool isAboveHighWatermark() const { return is_above_high_watermark_; } + // True after the buffer goes below low watermark and hasn't come up above high + // watermark yet, even though the buffered data might be between high and low + // watermarks. bool isBelowLowWatermark() const { return is_below_low_watermark_; } private: diff --git a/test/extensions/quic_listeners/quiche/BUILD b/test/extensions/quic_listeners/quiche/BUILD index fd0e631a8d738..58983ba5d8cad 100644 --- a/test/extensions/quic_listeners/quiche/BUILD +++ b/test/extensions/quic_listeners/quiche/BUILD @@ -183,3 +183,12 @@ envoy_cc_test( "@envoy_api//envoy/api/v2/listener:pkg_cc_proto", ], ) + +envoy_cc_test( + name = "envoy_quic_simulated_watermark_buffer_test", + srcs = ["envoy_quic_simulated_watermark_buffer_test.cc"], + tags = ["nofips"], + deps = [ + "//source/extensions/quic_listeners/quiche:envoy_quic_simulated_watermark_buffer_lib", + ], +) diff --git a/test/extensions/quic_listeners/quiche/envoy_quic_simulated_watermark_buffer_test.cc b/test/extensions/quic_listeners/quiche/envoy_quic_simulated_watermark_buffer_test.cc new file mode 100644 index 0000000000000..2d78dd7bf65f4 --- /dev/null +++ b/test/extensions/quic_listeners/quiche/envoy_quic_simulated_watermark_buffer_test.cc @@ -0,0 +1,81 @@ +#include "extensions/quic_listeners/quiche/envoy_quic_simulated_watermark_buffer.h" + +#include "gtest/gtest.h" + +namespace Envoy { +namespace Quic { + +class EnvoyQuicSimulatedWatermarkBufferTest : public ::testing::Test, + protected Logger::Loggable { +public: + EnvoyQuicSimulatedWatermarkBufferTest() + : simulated_watermark_buffer_( + low_watermark_, high_watermark_, [this]() { onBelowLowWatermark(); }, + [this]() { onAboveHighWatermark(); }, ENVOY_LOGGER()) {} + + void onAboveHighWatermark() { ++above_high_watermark_; } + + void onBelowLowWatermark() { ++below_low_watermark_; } + +protected: + size_t above_high_watermark_{0}; + size_t below_low_watermark_{0}; + uint32_t high_watermark_{100}; + uint32_t low_watermark_{60}; + EnvoyQuicSimulatedWatermarkBuffer simulated_watermark_buffer_; +}; + +TEST_F(EnvoyQuicSimulatedWatermarkBufferTest, InitialState) { + EXPECT_TRUE(simulated_watermark_buffer_.isBelowLowWatermark()); + EXPECT_FALSE(simulated_watermark_buffer_.isAboveHighWatermark()); + EXPECT_EQ(high_watermark_, simulated_watermark_buffer_.highWatermark()); +} + +TEST_F(EnvoyQuicSimulatedWatermarkBufferTest, GoAboveHighWatermarkAndComeDown) { + simulated_watermark_buffer_.checkHighWatermark(low_watermark_ + 1); + EXPECT_EQ(0U, above_high_watermark_); + // Even though the buffered data is above low watermark, the buffer is still regarded + // as below watermark because it didn't reach high watermark. + EXPECT_TRUE(simulated_watermark_buffer_.isBelowLowWatermark()); + simulated_watermark_buffer_.checkLowWatermark(low_watermark_ - 1); + // Going down below low watermark shouldn't trigger callback as it never + // reached high watermark. + EXPECT_EQ(0U, below_low_watermark_); + + simulated_watermark_buffer_.checkHighWatermark(high_watermark_ + 1); + EXPECT_EQ(1U, above_high_watermark_); + EXPECT_TRUE(simulated_watermark_buffer_.isAboveHighWatermark()); + EXPECT_FALSE(simulated_watermark_buffer_.isBelowLowWatermark()); + + simulated_watermark_buffer_.checkHighWatermark(high_watermark_ + 10); + EXPECT_EQ(1U, above_high_watermark_); + + simulated_watermark_buffer_.checkLowWatermark(low_watermark_); + EXPECT_EQ(0U, below_low_watermark_); + + simulated_watermark_buffer_.checkHighWatermark(high_watermark_ + 10); + // Crossing high watermark continuously shouldn't trigger callback. + EXPECT_EQ(1U, above_high_watermark_); + + // Crossing low watermark after coming down from high watermark should trigger + // callback and change status. + simulated_watermark_buffer_.checkLowWatermark(low_watermark_ - 1); + EXPECT_EQ(1U, below_low_watermark_); + EXPECT_TRUE(simulated_watermark_buffer_.isBelowLowWatermark()); + EXPECT_FALSE(simulated_watermark_buffer_.isAboveHighWatermark()); +} + +TEST_F(EnvoyQuicSimulatedWatermarkBufferTest, NoWatermarkSpecified) { + EnvoyQuicSimulatedWatermarkBuffer buffer( + 0, 0, [this]() { onBelowLowWatermark(); }, [this]() { onAboveHighWatermark(); }, + ENVOY_LOGGER()); + buffer.checkHighWatermark(10); + EXPECT_EQ(0U, above_high_watermark_); + + simulated_watermark_buffer_.checkLowWatermark(0); + EXPECT_EQ(0U, below_low_watermark_); + EXPECT_TRUE(simulated_watermark_buffer_.isBelowLowWatermark()); +} + +} // namespace Quic +} // namespace Envoy From 1200a32ddb0543eba1fc3e239add4ffcb4c3f802 Mon Sep 17 00:00:00 2001 From: Dan Zhang Date: Fri, 18 Oct 2019 17:22:14 -0400 Subject: [PATCH 27/76] watermark for each stream Signed-off-by: Dan Zhang --- .../quic_listeners/quiche/codec_impl.cc | 39 ++++++----- .../quic_listeners/quiche/codec_impl.h | 4 ++ .../quiche/envoy_quic_client_connection.cc | 66 +++++++++---------- .../quiche/envoy_quic_client_connection.h | 8 +-- test/integration/http_integration.cc | 2 - 5 files changed, 58 insertions(+), 61 deletions(-) diff --git a/source/extensions/quic_listeners/quiche/codec_impl.cc b/source/extensions/quic_listeners/quiche/codec_impl.cc index 4d00c6a8d679f..0bc7f150531d7 100644 --- a/source/extensions/quic_listeners/quiche/codec_impl.cc +++ b/source/extensions/quic_listeners/quiche/codec_impl.cc @@ -8,6 +8,21 @@ namespace Quic { bool QuicHttpConnectionImplBase::wantsToWrite() { return quic_session_.HasDataToWrite(); } +void QuicHttpConnectionImplBase::runWatermarkCallbacksForEachStream( + quic::QuicSmallMap, 10>& stream_map, + bool high_watermark) { + for (auto& it : stream_map) { + if (!it.second->is_static()) { + auto stream = dynamic_cast(it.second.get()); + if (high_watermark) { + stream->runHighWatermarkCallbacks(); + } else { + stream->runLowWatermarkCallbacks(); + } + } + } +} + QuicHttpServerConnectionImpl::QuicHttpServerConnectionImpl( EnvoyQuicServerSession& quic_session, Http::ServerConnectionCallbacks& callbacks) : QuicHttpConnectionImplBase(quic_session), quic_server_session_(quic_session) { @@ -15,19 +30,11 @@ QuicHttpServerConnectionImpl::QuicHttpServerConnectionImpl( } void QuicHttpServerConnectionImpl::onUnderlyingConnectionAboveWriteBufferHighWatermark() { - for (auto& it : quic_server_session_.stream_map()) { - if (!it.second->is_static()) { - dynamic_cast(it.second.get())->runHighWatermarkCallbacks(); - } - } + runWatermarkCallbacksForEachStream(quic_server_session_.stream_map(), true); } void QuicHttpServerConnectionImpl::onUnderlyingConnectionBelowWriteBufferLowWatermark() { - for (const auto& it : quic_server_session_.stream_map()) { - if (!it.second->is_static()) { - dynamic_cast(it.second.get())->runLowWatermarkCallbacks(); - } - } + runWatermarkCallbacksForEachStream(quic_server_session_.stream_map(), false); } void QuicHttpServerConnectionImpl::goAway() { @@ -49,19 +56,11 @@ QuicHttpClientConnectionImpl::newStream(Http::StreamDecoder& response_decoder) { } void QuicHttpClientConnectionImpl::onUnderlyingConnectionAboveWriteBufferHighWatermark() { - for (auto& it : quic_client_session_.stream_map()) { - if (!it.second->is_static()) { - dynamic_cast(it.second.get())->runHighWatermarkCallbacks(); - } - } + runWatermarkCallbacksForEachStream(quic_client_session_.stream_map(), true); } void QuicHttpClientConnectionImpl::onUnderlyingConnectionBelowWriteBufferLowWatermark() { - for (const auto& it : quic_client_session_.stream_map()) { - if (!it.second->is_static()) { - dynamic_cast(it.second.get())->runLowWatermarkCallbacks(); - } - } + runWatermarkCallbacksForEachStream(quic_client_session_.stream_map(), false); } } // namespace Quic diff --git a/source/extensions/quic_listeners/quiche/codec_impl.h b/source/extensions/quic_listeners/quiche/codec_impl.h index 141d73220d98a..6dcc12322ad1f 100644 --- a/source/extensions/quic_listeners/quiche/codec_impl.h +++ b/source/extensions/quic_listeners/quiche/codec_impl.h @@ -28,6 +28,10 @@ class QuicHttpConnectionImplBase : public virtual Http::Connection, // stream send buffer. bool wantsToWrite() override; + void runWatermarkCallbacksForEachStream( + quic::QuicSmallMap, 10>& stream_map, + bool high_watermark); + protected: quic::QuicSpdySession& quic_session_; }; diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_client_connection.cc b/source/extensions/quic_listeners/quiche/envoy_quic_client_connection.cc index 9a6dfb01028cf..0692ba34f8b69 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_client_connection.cc +++ b/source/extensions/quic_listeners/quiche/envoy_quic_client_connection.cc @@ -10,6 +10,38 @@ namespace Envoy { namespace Quic { +Network::ConnectionSocketPtr +createConnectionSocket(Network::Address::InstanceConstSharedPtr& peer_addr, + Network::Address::InstanceConstSharedPtr& local_addr, + const Network::ConnectionSocket::OptionsSharedPtr& options) { + Network::IoHandlePtr io_handle = peer_addr->socket(Network::Address::SocketType::Datagram); + auto connection_socket = + std::make_unique(std::move(io_handle), local_addr, peer_addr); + connection_socket->addOptions(Network::SocketOptionFactory::buildIpPacketInfoOptions()); + connection_socket->addOptions(Network::SocketOptionFactory::buildRxQueueOverFlowOptions()); + if (options != nullptr) { + connection_socket->addOptions(options); + } + if (!Network::Socket::applyOptions(connection_socket->options(), *connection_socket, + envoy::api::v2::core::SocketOption::STATE_PREBIND)) { + connection_socket->close(); + ENVOY_LOG_MISC(error, "Fail to apply pre-bind options"); + return connection_socket; + } + local_addr->bind(connection_socket->ioHandle().fd()); + ASSERT(local_addr->ip()); + if (local_addr->ip()->port() == 0) { + // Get ephemeral port number. + local_addr = Network::Address::addressFromFd(connection_socket->ioHandle().fd()); + } + if (!Network::Socket::applyOptions(connection_socket->options(), *connection_socket, + envoy::api::v2::core::SocketOption::STATE_BOUND)) { + ENVOY_LOG_MISC(error, "Fail to apply post-bind options"); + connection_socket->close(); + } + return connection_socket; +} + EnvoyQuicClientConnection::EnvoyQuicClientConnection( const quic::QuicConnectionId& server_connection_id, Network::Address::InstanceConstSharedPtr& initial_peer_address, @@ -79,7 +111,7 @@ void EnvoyQuicClientConnection::setUpConnectionSocket() { if (!Network::Socket::applyOptions(connectionSocket()->options(), *connectionSocket(), envoy::api::v2::core::SocketOption::STATE_LISTENING)) { - ENVOY_LOG_MISC(error, "Fail to apply listening options"); + ENVOY_CONN_LOG(error, "Fail to apply listening options", *this); connectionSocket()->close(); } } @@ -140,37 +172,5 @@ void EnvoyQuicClientConnection::onFileEvent(uint32_t events) { } } -Network::ConnectionSocketPtr EnvoyQuicClientConnection::createConnectionSocket( - Network::Address::InstanceConstSharedPtr& peer_addr, - Network::Address::InstanceConstSharedPtr& local_addr, - const Network::ConnectionSocket::OptionsSharedPtr& options) { - Network::IoHandlePtr io_handle = peer_addr->socket(Network::Address::SocketType::Datagram); - auto connection_socket = - std::make_unique(std::move(io_handle), local_addr, peer_addr); - connection_socket->addOptions(Network::SocketOptionFactory::buildIpPacketInfoOptions()); - connection_socket->addOptions(Network::SocketOptionFactory::buildRxQueueOverFlowOptions()); - if (options != nullptr) { - connection_socket->addOptions(options); - } - if (!Network::Socket::applyOptions(connection_socket->options(), *connection_socket, - envoy::api::v2::core::SocketOption::STATE_PREBIND)) { - connection_socket->close(); - ENVOY_LOG_MISC(error, "Fail to apply pre-bind options"); - return connection_socket; - } - local_addr->bind(connection_socket->ioHandle().fd()); - ASSERT(local_addr->ip()); - if (local_addr->ip()->port() == 0) { - // Get ephemeral port number. - local_addr = Network::Address::addressFromFd(connection_socket->ioHandle().fd()); - } - if (!Network::Socket::applyOptions(connection_socket->options(), *connection_socket, - envoy::api::v2::core::SocketOption::STATE_BOUND)) { - ENVOY_LOG_MISC(error, "Fail to apply post-bind options"); - connection_socket->close(); - } - return connection_socket; -} - } // namespace Quic } // namespace Envoy diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_client_connection.h b/source/extensions/quic_listeners/quiche/envoy_quic_client_connection.h index f9d79af6acca8..0824259a538fa 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_client_connection.h +++ b/source/extensions/quic_listeners/quiche/envoy_quic_client_connection.h @@ -26,10 +26,10 @@ class EnvoyQuicClientConnection : public EnvoyQuicConnection, public Network::Ud // Overridden to un-register all file events. ~EnvoyQuicClientConnection() override; + // Network::UdpPacketProcessor void processPacket(Network::Address::InstanceConstSharedPtr local_address, Network::Address::InstanceConstSharedPtr peer_address, Buffer::InstancePtr buffer, MonotonicTime receive_time) override; - uint64_t maxPacketSize() const override; // Register file event and apply socket options. @@ -51,12 +51,8 @@ class EnvoyQuicClientConnection : public EnvoyQuicConnection, public Network::Ud Event::Dispatcher& dispatcher, Network::ConnectionSocketPtr&& connection_socket); - Network::ConnectionSocketPtr - createConnectionSocket(Network::Address::InstanceConstSharedPtr& peer_addr, - Network::Address::InstanceConstSharedPtr& local_addr, - const Network::ConnectionSocket::OptionsSharedPtr& options); - void onFileEvent(uint32_t events); + uint32_t packets_dropped_{0}; Event::Dispatcher& dispatcher_; Event::FileEventPtr file_event_; diff --git a/test/integration/http_integration.cc b/test/integration/http_integration.cc index 8bc0f9d76ed4f..bf03f35e48c2b 100644 --- a/test/integration/http_integration.cc +++ b/test/integration/http_integration.cc @@ -581,9 +581,7 @@ void HttpIntegrationTest::testRouterUpstreamResponseBeforeRequestComplete() { ASSERT_TRUE(fake_upstreams_[0]->waitForHttpConnection(*dispatcher_, fake_upstream_connection_)); ASSERT_TRUE(fake_upstream_connection_->waitForNewStream(*dispatcher_, upstream_request_)); ASSERT_TRUE(upstream_request_->waitForHeadersComplete()); - upstream_request_->encodeHeaders(default_response_headers_, false); - upstream_request_->encodeData(512, true); response->waitForEndStream(); From 07400f2e31c5095afcadcffbaa9a9f8cbd892823 Mon Sep 17 00:00:00 2001 From: Dan Zhang Date: Fri, 18 Oct 2019 17:50:08 -0400 Subject: [PATCH 28/76] fix proto error Signed-off-by: Dan Zhang --- .../config/api_type_db.generated.pb_text | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/source/common/config/api_type_db.generated.pb_text b/source/common/config/api_type_db.generated.pb_text index dd963044eb702..d473694baa490 100644 --- a/source/common/config/api_type_db.generated.pb_text +++ b/source/common/config/api_type_db.generated.pb_text @@ -4039,6 +4039,14 @@ types { next_version_type_name: "envoy.config.filter.http.dynamic_forward_proxy.v3alpha.FilterConfig" } } +types { + key: "envoy.config.filter.http.dynamic_forward_proxy.v2alpha.PerRouteConfig" + value { + qualified_package: "envoy.config.filter.http.dynamic_forward_proxy.v2alpha" + proto_path: "envoy/config/filter/http/dynamic_forward_proxy/v2alpha/dynamic_forward_proxy.proto" + next_version_type_name: "envoy.config.filter.http.dynamic_forward_proxy.v3alpha.PerRouteConfig" + } +} types { key: "envoy.config.filter.http.dynamic_forward_proxy.v3alpha.FilterConfig" value { @@ -4046,6 +4054,13 @@ types { proto_path: "envoy/config/filter/http/dynamic_forward_proxy/v3alpha/dynamic_forward_proxy.proto" } } +types { + key: "envoy.config.filter.http.dynamic_forward_proxy.v3alpha.PerRouteConfig" + value { + qualified_package: "envoy.config.filter.http.dynamic_forward_proxy.v3alpha" + proto_path: "envoy/config/filter/http/dynamic_forward_proxy/v3alpha/dynamic_forward_proxy.proto" + } +} types { key: "envoy.config.filter.http.ext_authz.v2.AuthorizationRequest" value { @@ -7517,6 +7532,14 @@ types { proto_path: "envoy/service/trace/v3alpha/trace_service.proto" } } +types { + key: "envoy.type.CodecClientType" + value { + qualified_package: "envoy.type" + proto_path: "envoy/type/http.proto" + next_version_type_name: "envoy.type.v3alpha.CodecClientType" + } +} types { key: "envoy.type.DoubleRange" value { @@ -7723,6 +7746,13 @@ types { proto_path: "envoy/type/matcher/v3alpha/value.proto" } } +types { + key: "envoy.type.v3alpha.CodecClientType" + value { + qualified_package: "envoy.type.v3alpha" + proto_path: "envoy/type/v3alpha/http.proto" + } +} types { key: "envoy.type.v3alpha.DoubleRange" value { @@ -8380,6 +8410,10 @@ next_version_proto_paths { key: "envoy/service/trace/v2/trace_service.proto" value: "envoy/service/trace/v3alpha/trace_service.proto" } +next_version_proto_paths { + key: "envoy/type/http.proto" + value: "envoy/type/v3alpha/http.proto" +} next_version_proto_paths { key: "envoy/type/http_status.proto" value: "envoy/type/v3alpha/http_status.proto" From b06844fc2546d36db395dc599b9531c8453c1e41 Mon Sep 17 00:00:00 2001 From: Dan Zhang Date: Fri, 18 Oct 2019 18:02:32 -0400 Subject: [PATCH 29/76] doc Signed-off-by: Dan Zhang --- docs/quiche_integration.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/quiche_integration.md b/docs/quiche_integration.md index 4a60403cc50ca..91529fbe41a6d 100644 --- a/docs/quiche_integration.md +++ b/docs/quiche_integration.md @@ -16,13 +16,13 @@ The HCM will call encoder's encodeHeaders() to write response headers, and then ### Flow Control -## Receive buffer +#### Receive buffer -All the arrived out-of-order data is buffered in QUICHE stream. This buffered is capped by max stream flow control window in QUICHE which is 64MB. Once bytes are put in sequence and ready to be used, OnBodyDataAvailable() is called. The stream implementation overrides this call and calls StreamDecoder::decodeData() in it.Request and response body are buffered in each L7 filter if desired, and the stream itself doesn't buffer any of them unless set as read blocked. +All arrived out-of-order data is buffered in QUICHE stream. This buffered is capped by max stream flow control window in QUICHE which is 64MB. Once bytes are put in sequence and ready to be used, OnBodyDataAvailable() is called. The stream implementation overrides this call and calls StreamDecoder::decodeData() in it.Request and response body are buffered in each L7 filter if desired, and the stream itself doesn't buffer any of them unless set as read blocked. When upstream or any L7 filter reaches its buffer limit, it will call Http::Stream::readDisable() with false to set QUIC stream to be read blocked. In this state, even if more request/response body is available to be delivered, OnBodyDataAvailable() will not be called. As a result, downstream flow control will not shift as no data will be consumed. As both filters and upstream buffers can call readDisable(), each stream has a counter of how many times the HCM blocks the stream. When the counter is cleared, the stream will set its state to unblocked and thus deliver any new and existing available data buttered in the QUICHE stream. -## Send buffer +#### Send buffer We use the unlimited stream send buffer in QUICHE along with a book keeping data structure `EnvoyQuicSimulatedWatermarkBffer` to serve the function of WatermarkBuffer in Envoy to prevent buffering too much in QUICHE. From 8d637bce3a8a33e896d5e8bd298c5143a3853a29 Mon Sep 17 00:00:00 2001 From: Dan Zhang Date: Mon, 21 Oct 2019 17:08:25 -0400 Subject: [PATCH 30/76] add test util Signed-off-by: Dan Zhang --- .../quiche/envoy_quic_server_stream.cc | 8 +- test/extensions/quic_listeners/quiche/BUILD | 10 ++ .../quiche/envoy_quic_server_stream_test.cc | 93 ++++--------------- .../quic_listeners/quiche/test_utils.h | 57 ++++++++++++ 4 files changed, 91 insertions(+), 77 deletions(-) create mode 100644 test/extensions/quic_listeners/quiche/test_utils.h diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_server_stream.cc b/source/extensions/quic_listeners/quiche/envoy_quic_server_stream.cc index 9f05182af88af..df0019a2706bc 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_server_stream.cc +++ b/source/extensions/quic_listeners/quiche/envoy_quic_server_stream.cc @@ -68,8 +68,8 @@ void EnvoyQuicServerStream::encodeData(Buffer::Instance& data, bool end_stream) // If buffered bytes changed, update stream and session's watermark book // keeping. sendBufferSimulation().checkHighWatermark(bytes_to_send_new); - dynamic_cast(session())->adjustBytesToSend(bytes_to_send_new - - bytes_to_send_old); + dynamic_cast(session())->adjustBytesToSend(bytes_to_send_new - + bytes_to_send_old); } } @@ -209,8 +209,8 @@ void EnvoyQuicServerStream::OnCanWrite() { ASSERT(buffered_data_new <= buffered_data_old); if (buffered_data_new < buffered_data_old) { sendBufferSimulation().checkLowWatermark(buffered_data_new); - dynamic_cast(session())->adjustBytesToSend(buffered_data_new - - buffered_data_old); + dynamic_cast(session())->adjustBytesToSend(buffered_data_new - + buffered_data_old); } } diff --git a/test/extensions/quic_listeners/quiche/BUILD b/test/extensions/quic_listeners/quiche/BUILD index a1f9e7b2986a7..102d405f95a8b 100644 --- a/test/extensions/quic_listeners/quiche/BUILD +++ b/test/extensions/quic_listeners/quiche/BUILD @@ -58,6 +58,7 @@ envoy_cc_test( tags = ["nofips"], deps = [ ":quic_test_utils_for_envoy_lib", + ":test_utils_lib", "//source/common/http:headers_lib", "//source/extensions/quic_listeners/quiche:envoy_quic_alarm_factory_lib", "//source/extensions/quic_listeners/quiche:envoy_quic_connection_helper_lib", @@ -181,3 +182,12 @@ envoy_cc_test( "@envoy_api//envoy/api/v2/listener:pkg_cc_proto", ], ) + +envoy_cc_test_library( + name = "test_utils_lib", + hdrs = ["test_utils.h"], + tags = ["nofips"], + deps = [ + "@com_googlesource_quiche//:quic_core_http_spdy_session_lib", + ], +) diff --git a/test/extensions/quic_listeners/quiche/envoy_quic_server_stream_test.cc b/test/extensions/quic_listeners/quiche/envoy_quic_server_stream_test.cc index 290096b9c8408..0cf860ec53e9f 100644 --- a/test/extensions/quic_listeners/quiche/envoy_quic_server_stream_test.cc +++ b/test/extensions/quic_listeners/quiche/envoy_quic_server_stream_test.cc @@ -1,31 +1,20 @@ -#include "extensions/quic_listeners/quiche/envoy_quic_server_stream.h" - -#pragma GCC diagnostic push -// QUICHE allows unused parameters. -#pragma GCC diagnostic ignored "-Wunused-parameter" -// QUICHE uses offsetof(). -#pragma GCC diagnostic ignored "-Winvalid-offsetof" - -#include "quiche/quic/core/quic_versions.h" -#include "quiche/quic/core/http/quic_server_session_base.h" -#include "quiche/quic/test_tools/quic_test_utils.h" -#include "quiche/quic/core/quic_utils.h" - -#pragma GCC diagnostic pop - #include #include "common/event/libevent_scheduler.h" #include "common/http/headers.h" -#include "test/test_common/utility.h" + #include "extensions/quic_listeners/quiche/envoy_quic_alarm_factory.h" -#include "extensions/quic_listeners/quiche/envoy_quic_server_session.h" -#include "extensions/quic_listeners/quiche/envoy_quic_server_connection.h" #include "extensions/quic_listeners/quiche/envoy_quic_connection_helper.h" -#include "test/test_common/utility.h" -#include "test/mocks/http/stream_decoder.h" +#include "extensions/quic_listeners/quiche/envoy_quic_server_connection.h" +#include "extensions/quic_listeners/quiche/envoy_quic_server_session.h" +#include "extensions/quic_listeners/quiche/envoy_quic_server_stream.h" + +#include "test/extensions/quic_listeners/quiche/test_utils.h" #include "test/mocks/http/mocks.h" +#include "test/mocks/http/stream_decoder.h" #include "test/mocks/network/mocks.h" +#include "test/test_common/utility.h" + #include "gmock/gmock.h" #include "gtest/gtest.h" @@ -36,43 +25,6 @@ using testing::Return; namespace Envoy { namespace Quic { -class MockQuicServerSession : public EnvoyQuicServerSession { -public: - MockQuicServerSession(const quic::QuicConfig& config, - const quic::ParsedQuicVersionVector& supported_versions, - std::unique_ptr connection, - quic::QuicSession::Visitor* visitor, - quic::QuicCryptoServerStream::Helper* helper, - const quic::QuicCryptoServerConfig* crypto_config, - quic::QuicCompressedCertsCache* compressed_certs_cache, - Event::Dispatcher& dispatcher, uint32_t send_buffer_limit) - : EnvoyQuicServerSession(config, supported_versions, std::move(connection), visitor, helper, - crypto_config, compressed_certs_cache, dispatcher, - send_buffer_limit) {} - - MOCK_METHOD1(CreateIncomingStream, quic::QuicSpdyStream*(quic::QuicStreamId id)); - MOCK_METHOD1(CreateIncomingStream, quic::QuicSpdyStream*(quic::PendingStream* pending)); - MOCK_METHOD0(CreateOutgoingBidirectionalStream, quic::QuicSpdyStream*()); - MOCK_METHOD0(CreateOutgoingUnidirectionalStream, quic::QuicSpdyStream*()); - MOCK_METHOD1(ShouldCreateIncomingStream, bool(quic::QuicStreamId id)); - MOCK_METHOD0(ShouldCreateOutgoingBidirectionalStream, bool()); - MOCK_METHOD0(ShouldCreateOutgoingUnidirectionalStream, bool()); - MOCK_METHOD1(ShouldYield, bool(quic::QuicStreamId stream_id)); - MOCK_METHOD5(WritevData, - quic::QuicConsumedData(quic::QuicStream* stream, quic::QuicStreamId id, - size_t write_length, quic::QuicStreamOffset offset, - quic::StreamSendingState state)); - - quic::QuicCryptoServerStream* - CreateQuicCryptoServerStream(const quic::QuicCryptoServerConfig* crypto_config, - quic::QuicCompressedCertsCache* compressed_certs_cache) override { - return new quic::QuicCryptoServerStream(crypto_config, compressed_certs_cache, this, - stream_helper()); - } - - using quic::QuicServerSessionBase::ActivateStream; -}; - class EnvoyQuicServerStreamTest : public testing::TestWithParam { public: EnvoyQuicServerStreamTest() @@ -85,17 +37,12 @@ class EnvoyQuicServerStreamTest : public testing::TestWithParam { listener_stats_({ALL_LISTENER_STATS(POOL_COUNTER(listener_config_.listenerScope()), POOL_GAUGE(listener_config_.listenerScope()), POOL_HISTOGRAM(listener_config_.listenerScope()))}), - quic_connection_(new EnvoyQuicServerConnection( - quic::test::TestConnectionId(), - quic::QuicSocketAddress(quic::QuicIpAddress::Any6(), 12345), connection_helper_, - alarm_factory_, &writer_, - /*owns_writer=*/false, {quic_version_}, listener_config_, listener_stats_)), - quic_session_(quic_config_, {quic_version_}, - std::unique_ptr(quic_connection_), - /*visitor=*/nullptr, - /*helper=*/nullptr, /*crypto_config=*/nullptr, - /*compressed_certs_cache=*/nullptr, *dispatcher_, - quic_config_.GetInitialStreamFlowControlWindowToSend()), + quic_connection_(quic::test::TestConnectionId(), + quic::QuicSocketAddress(quic::QuicIpAddress::Any6(), 12345), + connection_helper_, alarm_factory_, &writer_, + /*owns_writer=*/false, {quic_version_}, listener_config_, listener_stats_), + quic_session_(quic_config_, {quic_version_}, &quic_connection_, *dispatcher_, + quic_config_.GetInitialStreamFlowControlWindowToSend() * 2), stream_id_(quic_version_.transport_version == quic::QUIC_VERSION_99 ? 4u : 5u), quic_stream_(new EnvoyQuicServerStream(stream_id_, &quic_session_, quic::BIDIRECTIONAL)), response_headers_{{":status", "200"}} { @@ -103,7 +50,6 @@ class EnvoyQuicServerStreamTest : public testing::TestWithParam { quic_stream_->setDecoder(stream_decoder_); quic_stream_->addCallbacks(stream_callbacks_); quic_session_.ActivateStream(std::unique_ptr(quic_stream_)); - EXPECT_CALL(quic_session_, ShouldYield(_)).WillRepeatedly(Return(false)); EXPECT_CALL(quic_session_, WritevData(_, _, _, _, _)) .WillRepeatedly(Invoke([](quic::QuicStream*, quic::QuicStreamId, size_t write_length, quic::QuicStreamOffset, quic::StreamSendingState) { @@ -135,8 +81,9 @@ class EnvoyQuicServerStreamTest : public testing::TestWithParam { } void TearDown() override { - if (quic_connection_->connected()) { - quic_session_.close(Network::ConnectionCloseType::NoFlush); + if (quic_connection_.connected()) { + quic_connection_.CloseConnection(quic::QUIC_NO_ERROR, "Closed by application", + quic::ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET); } } @@ -150,8 +97,8 @@ class EnvoyQuicServerStreamTest : public testing::TestWithParam { quic::QuicConfig quic_config_; testing::NiceMock listener_config_; Server::ListenerStats listener_stats_; - EnvoyQuicServerConnection* quic_connection_; - MockQuicServerSession quic_session_; + EnvoyQuicServerConnection quic_connection_; + MockEnvoyQuicSession quic_session_; quic::QuicStreamId stream_id_; EnvoyQuicServerStream* quic_stream_; Http::MockStreamDecoder stream_decoder_; diff --git a/test/extensions/quic_listeners/quiche/test_utils.h b/test/extensions/quic_listeners/quiche/test_utils.h new file mode 100644 index 0000000000000..1b8489f4a6c3b --- /dev/null +++ b/test/extensions/quic_listeners/quiche/test_utils.h @@ -0,0 +1,57 @@ +#include "extensions/quic_listeners/quiche/quic_filter_manager_connection_impl.h" + +#pragma GCC diagnostic push +// QUICHE allows unused parameters. +#pragma GCC diagnostic ignored "-Wunused-parameter" +// QUICHE uses offsetof(). +#pragma GCC diagnostic ignored "-Winvalid-offsetof" + +#include "quiche/quic/core/http/quic_spdy_session.h" +#include "quiche/quic/test_tools/quic_test_utils.h" +#include "quiche/quic/core/quic_utils.h" + +#pragma GCC diagnostic pop + +namespace Envoy { +namespace Quic { + +class MockEnvoyQuicSession : public quic::QuicSpdySession, public QuicFilterManagerConnectionImpl { +public: + MockEnvoyQuicSession(const quic::QuicConfig& config, + const quic::ParsedQuicVersionVector& supported_versions, + EnvoyQuicConnection* connection, Event::Dispatcher& dispatcher, + uint32_t send_buffer_limit) + : quic::QuicSpdySession(connection, /*visitor=*/nullptr, config, supported_versions), + QuicFilterManagerConnectionImpl(*connection, dispatcher, send_buffer_limit) { + crypto_stream_ = std::make_unique(this); + } + + // From QuicSession. + MOCK_METHOD1(CreateIncomingStream, quic::QuicSpdyStream*(quic::QuicStreamId id)); + MOCK_METHOD1(CreateIncomingStream, quic::QuicSpdyStream*(quic::PendingStream* pending)); + MOCK_METHOD0(CreateOutgoingBidirectionalStream, quic::QuicSpdyStream*()); + MOCK_METHOD0(CreateOutgoingUnidirectionalStream, quic::QuicSpdyStream*()); + MOCK_METHOD1(ShouldCreateIncomingStream, bool(quic::QuicStreamId id)); + MOCK_METHOD0(ShouldCreateOutgoingBidirectionalStream, bool()); + MOCK_METHOD0(ShouldCreateOutgoingUnidirectionalStream, bool()); + MOCK_METHOD5(WritevData, + quic::QuicConsumedData(quic::QuicStream* stream, quic::QuicStreamId id, + size_t write_length, quic::QuicStreamOffset offset, + quic::StreamSendingState state)); + + absl::string_view requestedServerName() const override { + return {GetCryptoStream()->crypto_negotiated_params().sni}; + } + + quic::QuicCryptoStream* GetMutableCryptoStream() override { return crypto_stream_.get(); } + + const quic::QuicCryptoStream* GetCryptoStream() const override { return crypto_stream_.get(); } + + using quic::QuicSpdySession::ActivateStream; + +private: + std::unique_ptr crypto_stream_; +}; + +} // namespace Quic +} // namespace Envoy From 20fe85ca17f2df0000cede62d9c97f140248332d Mon Sep 17 00:00:00 2001 From: Dan Zhang Date: Mon, 21 Oct 2019 17:08:25 -0400 Subject: [PATCH 31/76] add test util Signed-off-by: Dan Zhang --- .../quiche/envoy_quic_server_stream.cc | 9 +- test/extensions/quic_listeners/quiche/BUILD | 12 ++- .../quiche/envoy_quic_server_stream_test.cc | 88 +++++-------------- .../quic_listeners/quiche/test_utils.h | 57 ++++++++++++ 4 files changed, 91 insertions(+), 75 deletions(-) create mode 100644 test/extensions/quic_listeners/quiche/test_utils.h diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_server_stream.cc b/source/extensions/quic_listeners/quiche/envoy_quic_server_stream.cc index 4b982f3f9d7b9..23d3bca4c76e0 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_server_stream.cc +++ b/source/extensions/quic_listeners/quiche/envoy_quic_server_stream.cc @@ -73,7 +73,7 @@ void EnvoyQuicServerStream::encodeData(Buffer::Instance& data, bool end_stream) uint64_t bytes_to_send_new = BufferedDataBytes(); ASSERT(bytes_to_send_old <= bytes_to_send_new); maybeCheckWatermark(bytes_to_send_old, bytes_to_send_new, - dynamic_cast(*session())); + dynamic_cast(*session())); } void EnvoyQuicServerStream::encodeTrailers(const Http::HeaderMap& trailers) { @@ -208,7 +208,8 @@ void EnvoyQuicServerStream::OnClose() { if (BufferedDataBytes() > 0) { // If the stream is closed without sending out all bufferred data, regard // them as sent now and adjust connection buffer book keeping. - dynamic_cast(session())->adjustBytesToSend(0 - BufferedDataBytes()); + dynamic_cast(session())->adjustBytesToSend( + 0 - BufferedDataBytes()); } } @@ -220,13 +221,13 @@ void EnvoyQuicServerStream::OnCanWrite() { // increase. ASSERT(buffered_data_new <= buffered_data_old); maybeCheckWatermark(buffered_data_old, buffered_data_new, - dynamic_cast(*session())); + dynamic_cast(*session())); } uint32_t EnvoyQuicServerStream::streamId() { return id(); } Network::Connection* EnvoyQuicServerStream::connection() { - return dynamic_cast(session()); + return dynamic_cast(session()); } } // namespace Quic diff --git a/test/extensions/quic_listeners/quiche/BUILD b/test/extensions/quic_listeners/quiche/BUILD index 58983ba5d8cad..ad57d8c3ef6a4 100644 --- a/test/extensions/quic_listeners/quiche/BUILD +++ b/test/extensions/quic_listeners/quiche/BUILD @@ -58,6 +58,7 @@ envoy_cc_test( tags = ["nofips"], deps = [ ":quic_test_utils_for_envoy_lib", + ":test_utils_lib", "//source/common/http:headers_lib", "//source/extensions/quic_listeners/quiche:envoy_quic_alarm_factory_lib", "//source/extensions/quic_listeners/quiche:envoy_quic_connection_helper_lib", @@ -188,7 +189,12 @@ envoy_cc_test( name = "envoy_quic_simulated_watermark_buffer_test", srcs = ["envoy_quic_simulated_watermark_buffer_test.cc"], tags = ["nofips"], - deps = [ - "//source/extensions/quic_listeners/quiche:envoy_quic_simulated_watermark_buffer_lib", - ], + deps = ["//source/extensions/quic_listeners/quiche:envoy_quic_simulated_watermark_buffer_lib"], +) + +envoy_cc_test_library( + name = "test_utils_lib", + hdrs = ["test_utils.h"], + tags = ["nofips"], + deps = ["@com_googlesource_quiche//:quic_core_http_spdy_session_lib"], ) diff --git a/test/extensions/quic_listeners/quiche/envoy_quic_server_stream_test.cc b/test/extensions/quic_listeners/quiche/envoy_quic_server_stream_test.cc index fc2eb63989263..e8c88281270b8 100644 --- a/test/extensions/quic_listeners/quiche/envoy_quic_server_stream_test.cc +++ b/test/extensions/quic_listeners/quiche/envoy_quic_server_stream_test.cc @@ -1,33 +1,20 @@ -#include - -#include "extensions/quic_listeners/quiche/envoy_quic_server_stream.h" - -#pragma GCC diagnostic push -// QUICHE allows unused parameters. -#pragma GCC diagnostic ignored "-Wunused-parameter" -// QUICHE uses offsetof(). -#pragma GCC diagnostic ignored "-Winvalid-offsetof" - -#include "quiche/quic/core/quic_versions.h" -#include "quiche/quic/core/http/quic_server_session_base.h" -#include "quiche/quic/test_tools/quic_test_utils.h" -#include "quiche/quic/core/quic_utils.h" - -#pragma GCC diagnostic pop - #include #include "common/event/libevent_scheduler.h" #include "common/http/headers.h" -#include "test/test_common/utility.h" + #include "extensions/quic_listeners/quiche/envoy_quic_alarm_factory.h" -#include "extensions/quic_listeners/quiche/envoy_quic_server_session.h" -#include "extensions/quic_listeners/quiche/envoy_quic_server_connection.h" #include "extensions/quic_listeners/quiche/envoy_quic_connection_helper.h" -#include "test/test_common/utility.h" -#include "test/mocks/http/stream_decoder.h" +#include "extensions/quic_listeners/quiche/envoy_quic_server_connection.h" +#include "extensions/quic_listeners/quiche/envoy_quic_server_session.h" +#include "extensions/quic_listeners/quiche/envoy_quic_server_stream.h" + +#include "test/extensions/quic_listeners/quiche/test_utils.h" #include "test/mocks/http/mocks.h" +#include "test/mocks/http/stream_decoder.h" #include "test/mocks/network/mocks.h" +#include "test/test_common/utility.h" + #include "gmock/gmock.h" #include "gtest/gtest.h" @@ -38,36 +25,6 @@ using testing::Return; namespace Envoy { namespace Quic { -class MockQuicServerSession : public EnvoyQuicServerSession { -public: - MockQuicServerSession(const quic::QuicConfig& config, - const quic::ParsedQuicVersionVector& supported_versions, - std::unique_ptr connection, - quic::QuicSession::Visitor* visitor, - quic::QuicCryptoServerStream::Helper* helper, - const quic::QuicCryptoServerConfig* crypto_config, - quic::QuicCompressedCertsCache* compressed_certs_cache, - Event::Dispatcher& dispatcher, uint32_t send_buffer_limit) - : EnvoyQuicServerSession(config, supported_versions, std::move(connection), visitor, helper, - crypto_config, compressed_certs_cache, dispatcher, - send_buffer_limit) {} - - MOCK_METHOD1(CreateIncomingStream, quic::QuicSpdyStream*(quic::QuicStreamId id)); - MOCK_METHOD1(CreateIncomingStream, quic::QuicSpdyStream*(quic::PendingStream* pending)); - MOCK_METHOD0(CreateOutgoingBidirectionalStream, quic::QuicSpdyStream*()); - MOCK_METHOD0(CreateOutgoingUnidirectionalStream, quic::QuicSpdyStream*()); - MOCK_METHOD1(ShouldCreateIncomingStream, bool(quic::QuicStreamId id)); - MOCK_METHOD0(ShouldCreateOutgoingBidirectionalStream, bool()); - MOCK_METHOD0(ShouldCreateOutgoingUnidirectionalStream, bool()); - MOCK_METHOD1(ShouldYield, bool(quic::QuicStreamId stream_id)); - MOCK_METHOD5(WritevData, - quic::QuicConsumedData(quic::QuicStream* stream, quic::QuicStreamId id, - size_t write_length, quic::QuicStreamOffset offset, - quic::StreamSendingState state)); - - using quic::QuicServerSessionBase::ActivateStream; -}; - class EnvoyQuicServerStreamTest : public testing::TestWithParam { public: EnvoyQuicServerStreamTest() @@ -80,24 +37,18 @@ class EnvoyQuicServerStreamTest : public testing::TestWithParam { listener_stats_({ALL_LISTENER_STATS(POOL_COUNTER(listener_config_.listenerScope()), POOL_GAUGE(listener_config_.listenerScope()), POOL_HISTOGRAM(listener_config_.listenerScope()))}), - quic_connection_(new EnvoyQuicServerConnection( - quic::test::TestConnectionId(), - quic::QuicSocketAddress(quic::QuicIpAddress::Any6(), 12345), connection_helper_, - alarm_factory_, &writer_, - /*owns_writer=*/false, {quic_version_}, listener_config_, listener_stats_)), - quic_session_(quic_config_, {quic_version_}, - std::unique_ptr(quic_connection_), - /*visitor=*/nullptr, - /*helper=*/nullptr, /*crypto_config=*/nullptr, - /*compressed_certs_cache=*/nullptr, *dispatcher_, - quic_config_.GetInitialSessionFlowControlWindowToSend()), + quic_connection_(quic::test::TestConnectionId(), + quic::QuicSocketAddress(quic::QuicIpAddress::Any6(), 12345), + connection_helper_, alarm_factory_, &writer_, + /*owns_writer=*/false, {quic_version_}, listener_config_, listener_stats_), + quic_session_(quic_config_, {quic_version_}, &quic_connection_, *dispatcher_, + quic_config_.GetInitialStreamFlowControlWindowToSend() * 2), stream_id_(quic_version_.transport_version == quic::QUIC_VERSION_99 ? 4u : 5u), quic_stream_(new EnvoyQuicServerStream(stream_id_, &quic_session_, quic::BIDIRECTIONAL)), response_headers_{{":status", "200"}} { quic_stream_->setDecoder(stream_decoder_); quic_stream_->addCallbacks(stream_callbacks_); quic_session_.ActivateStream(std::unique_ptr(quic_stream_)); - EXPECT_CALL(quic_session_, ShouldYield(_)).WillRepeatedly(Return(false)); EXPECT_CALL(quic_session_, WritevData(_, _, _, _, _)) .WillRepeatedly(Invoke([](quic::QuicStream*, quic::QuicStreamId, size_t write_length, quic::QuicStreamOffset, quic::StreamSendingState) { @@ -129,8 +80,9 @@ class EnvoyQuicServerStreamTest : public testing::TestWithParam { } void TearDown() override { - if (quic_connection_->connected()) { - quic_session_.close(Network::ConnectionCloseType::NoFlush); + if (quic_connection_.connected()) { + quic_connection_.CloseConnection(quic::QUIC_NO_ERROR, "Closed by application", + quic::ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET); } } @@ -182,8 +134,8 @@ class EnvoyQuicServerStreamTest : public testing::TestWithParam { quic::QuicConfig quic_config_; testing::NiceMock listener_config_; Server::ListenerStats listener_stats_; - EnvoyQuicServerConnection* quic_connection_; - MockQuicServerSession quic_session_; + EnvoyQuicServerConnection quic_connection_; + MockEnvoyQuicSession quic_session_; quic::QuicStreamId stream_id_; EnvoyQuicServerStream* quic_stream_; Http::MockStreamDecoder stream_decoder_; diff --git a/test/extensions/quic_listeners/quiche/test_utils.h b/test/extensions/quic_listeners/quiche/test_utils.h new file mode 100644 index 0000000000000..6efb3678a5dcf --- /dev/null +++ b/test/extensions/quic_listeners/quiche/test_utils.h @@ -0,0 +1,57 @@ +#include "extensions/quic_listeners/quiche/quic_filter_manager_connection_impl.h" + +#pragma GCC diagnostic push +// QUICHE allows unused parameters. +#pragma GCC diagnostic ignored "-Wunused-parameter" +// QUICHE uses offsetof(). +#pragma GCC diagnostic ignored "-Winvalid-offsetof" + +#include "quiche/quic/core/http/quic_spdy_session.h" +#include "quiche/quic/test_tools/quic_test_utils.h" +#include "quiche/quic/core/quic_utils.h" + +#pragma GCC diagnostic pop + +namespace Envoy { +namespace Quic { + +class MockEnvoyQuicSession : public quic::QuicSpdySession, public QuicFilterManagerConnectionImpl { +public: + MockEnvoyQuicSession(const quic::QuicConfig& config, + const quic::ParsedQuicVersionVector& supported_versions, + EnvoyQuicConnection* connection, Event::Dispatcher& dispatcher, + uint32_t send_buffer_limit) + : quic::QuicSpdySession(connection, /*visitor=*/nullptr, config, supported_versions), + QuicFilterManagerConnectionImpl(connection, dispatcher, send_buffer_limit) { + crypto_stream_ = std::make_unique(this); + } + + // From QuicSession. + MOCK_METHOD1(CreateIncomingStream, quic::QuicSpdyStream*(quic::QuicStreamId id)); + MOCK_METHOD1(CreateIncomingStream, quic::QuicSpdyStream*(quic::PendingStream* pending)); + MOCK_METHOD0(CreateOutgoingBidirectionalStream, quic::QuicSpdyStream*()); + MOCK_METHOD0(CreateOutgoingUnidirectionalStream, quic::QuicSpdyStream*()); + MOCK_METHOD1(ShouldCreateIncomingStream, bool(quic::QuicStreamId id)); + MOCK_METHOD0(ShouldCreateOutgoingBidirectionalStream, bool()); + MOCK_METHOD0(ShouldCreateOutgoingUnidirectionalStream, bool()); + MOCK_METHOD5(WritevData, + quic::QuicConsumedData(quic::QuicStream* stream, quic::QuicStreamId id, + size_t write_length, quic::QuicStreamOffset offset, + quic::StreamSendingState state)); + + absl::string_view requestedServerName() const override { + return {GetCryptoStream()->crypto_negotiated_params().sni}; + } + + quic::QuicCryptoStream* GetMutableCryptoStream() override { return crypto_stream_.get(); } + + const quic::QuicCryptoStream* GetCryptoStream() const override { return crypto_stream_.get(); } + + using quic::QuicSpdySession::ActivateStream; + +private: + std::unique_ptr crypto_stream_; +}; + +} // namespace Quic +} // namespace Envoy From 484ffba3df1a450db1f8148b4fe78fe115a52f2b Mon Sep 17 00:00:00 2001 From: Dan Zhang Date: Mon, 21 Oct 2019 18:09:43 -0400 Subject: [PATCH 32/76] fix typos in doc Signed-off-by: Dan Zhang --- docs/quiche_integration.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/quiche_integration.md b/docs/quiche_integration.md index 91529fbe41a6d..45de216cc6b44 100644 --- a/docs/quiche_integration.md +++ b/docs/quiche_integration.md @@ -18,16 +18,16 @@ The HCM will call encoder's encodeHeaders() to write response headers, and then #### Receive buffer -All arrived out-of-order data is buffered in QUICHE stream. This buffered is capped by max stream flow control window in QUICHE which is 64MB. Once bytes are put in sequence and ready to be used, OnBodyDataAvailable() is called. The stream implementation overrides this call and calls StreamDecoder::decodeData() in it.Request and response body are buffered in each L7 filter if desired, and the stream itself doesn't buffer any of them unless set as read blocked. +All arrived out-of-order data is buffered in QUICHE stream. This buffer is capped by max stream flow control window in QUICHE which is 16MB. Once bytes are put in sequence and ready to be used, OnBodyDataAvailable() is called. The stream implementation overrides this call and calls StreamDecoder::decodeData() in it. Request and response body are buffered in each L7 filter if desired, and the stream itself doesn't buffer any of them unless set as read blocked. -When upstream or any L7 filter reaches its buffer limit, it will call Http::Stream::readDisable() with false to set QUIC stream to be read blocked. In this state, even if more request/response body is available to be delivered, OnBodyDataAvailable() will not be called. As a result, downstream flow control will not shift as no data will be consumed. As both filters and upstream buffers can call readDisable(), each stream has a counter of how many times the HCM blocks the stream. When the counter is cleared, the stream will set its state to unblocked and thus deliver any new and existing available data buttered in the QUICHE stream. +When upstream or any L7 filter reaches its buffer limit, it will call Http::Stream::readDisable() with false to set QUIC stream to be read blocked. In this state, even if more request/response body is available to be delivered, OnBodyDataAvailable() will not be called. As a result, downstream flow control will not shift as no data will be consumed. As both filters and upstream buffers can call readDisable(), each stream has a counter of how many times the HCM blocks the stream. When the counter is cleared, the stream will set its state to unblocked and thus deliver any new and existing available data buffered in the QUICHE stream. #### Send buffer -We use the unlimited stream send buffer in QUICHE along with a book keeping data structure `EnvoyQuicSimulatedWatermarkBffer` to serve the function of WatermarkBuffer in Envoy to prevent buffering too much in QUICHE. +We use the unlimited stream send buffer in QUICHE along with a book keeping data structure `EnvoyQuicSimulatedWatermarkBuffer` to serve the function of WatermarkBuffer in Envoy to prevent buffering too much in QUICHE. When the bytes buffered in a stream's send buffer exceeds its high watermark, its inherited method StreamCallbackHelper::runHighWatermarkCallbacks() is called. The buffered bytes will go below stream's low watermark as the stream writes out data gradually via QuicStream::OnCanWrite(). In this case StreamCallbackHelper::runLowWatermarkCallbacks() will be called. QUICHE buffers all the data upon QuicSpdyStream::WriteBodySlices(), assuming `buffered_data_threshold_` is set high enough, and then writes all or part of them according to flow control window and congestion control window. To prevent transient changes in buffered bytes from triggering these two watermark callbacks one immediately after the other, encodeData() and OnCanWrite() only update the watermark bookkeeping once at the end if buffered bytes are changed. QUICHE doesn't buffer data at the local connection layer. All the data is buffered in the respective streams.To prevent the case where all streams collectively buffers a lot of data, there is also a simulated watermark buffer for each QUIC connection which is updated upon each stream write. -When the aggregated buffered bytes goes above high watermark, its registered network callbacks will call Network::ConnectionCallbacks::onAboveWriteBufferHighWatermark(). The HCMwill notify each stream via QUIC codec Http::Connection::onUnderlyingConnectionAboveWriteBufferHighWatermark() which will call each stream's StreamCallbackHelper::runHighWatermarkCallbacks(). There might be a way to simply the call stack as Quic connection already knows about all the stream, there is no need to call to HCM and notify each stream via codec. But here we just follow the same logic as HTTP2 codec does. In the same way, any QuicStream::OnCanWrite() may change the aggregated buffered bytes in the connection level bookkeeping as well. If the buffered bytes goes down below the low watermark, the same calls will be triggered to propergate onBelowWriteBufferLowWatermark() to each stream. +When the aggregated buffered bytes goes above high watermark, its registered network callbacks will call Network::ConnectionCallbacks::onAboveWriteBufferHighWatermark(). The HCM will notify each stream via QUIC codec Http::Connection::onUnderlyingConnectionAboveWriteBufferHighWatermark() which will call each stream's StreamCallbackHelper::runHighWatermarkCallbacks(). There might be a way to simply the call stack as Quic connection already knows about all the stream, there is no need to call to HCM and notify each stream via codec. But here we just follow the same logic as HTTP2 codec does. In the same way, any QuicStream::OnCanWrite() may change the aggregated buffered bytes in the connection level bookkeeping as well. If the buffered bytes goes down below the low watermark, the same calls will be triggered to propagate onBelowWriteBufferLowWatermark() to each stream. From e967b4691a533bb9823bb6f21db51a44d6146810 Mon Sep 17 00:00:00 2001 From: Dan Zhang Date: Wed, 23 Oct 2019 17:44:08 -0400 Subject: [PATCH 33/76] add test for client stream and session Signed-off-by: Dan Zhang --- source/extensions/quic_listeners/quiche/BUILD | 2 + .../quic_listeners/quiche/codec_impl.cc | 1 + .../quiche/envoy_quic_client_connection.cc | 32 -- .../quiche/envoy_quic_client_connection.h | 16 +- .../quiche/envoy_quic_client_session.cc | 3 +- .../quiche/envoy_quic_client_stream.cc | 14 +- .../quic_listeners/quiche/envoy_quic_utils.cc | 34 ++ .../quic_listeners/quiche/envoy_quic_utils.h | 8 + test/extensions/quic_listeners/quiche/BUILD | 41 ++- .../quiche/envoy_quic_client_session_test.cc | 242 +++++++++++++ .../quiche/envoy_quic_client_stream_test.cc | 318 ++++++++++++++++++ .../quiche/envoy_quic_server_session_test.cc | 2 - .../integration/quic_http_integration_test.cc | 1 + 13 files changed, 662 insertions(+), 52 deletions(-) create mode 100644 test/extensions/quic_listeners/quiche/envoy_quic_client_session_test.cc create mode 100644 test/extensions/quic_listeners/quiche/envoy_quic_client_stream_test.cc diff --git a/source/extensions/quic_listeners/quiche/BUILD b/source/extensions/quic_listeners/quiche/BUILD index b5898d29b942e..19dfafd14b740 100644 --- a/source/extensions/quic_listeners/quiche/BUILD +++ b/source/extensions/quic_listeners/quiche/BUILD @@ -292,6 +292,8 @@ envoy_cc_library( "//include/envoy/http:codec_interface", "//source/common/http:header_map_lib", "//source/common/network:address_lib", + "//source/common/network:listen_socket_lib", + "//source/common/network:socket_option_factory_lib", "@com_googlesource_quiche//:quic_core_http_header_list_lib", ], ) diff --git a/source/extensions/quic_listeners/quiche/codec_impl.cc b/source/extensions/quic_listeners/quiche/codec_impl.cc index 0bc7f150531d7..c6d418e845153 100644 --- a/source/extensions/quic_listeners/quiche/codec_impl.cc +++ b/source/extensions/quic_listeners/quiche/codec_impl.cc @@ -51,6 +51,7 @@ Http::StreamEncoder& QuicHttpClientConnectionImpl::newStream(Http::StreamDecoder& response_decoder) { auto stream = dynamic_cast( quic_client_session_.CreateOutgoingBidirectionalStream()); + ASSERT(stream != nullptr, "Fail to create QUIC stream."); stream->setDecoder(response_decoder); return *stream; } diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_client_connection.cc b/source/extensions/quic_listeners/quiche/envoy_quic_client_connection.cc index 0692ba34f8b69..c8ab991a630a6 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_client_connection.cc +++ b/source/extensions/quic_listeners/quiche/envoy_quic_client_connection.cc @@ -10,38 +10,6 @@ namespace Envoy { namespace Quic { -Network::ConnectionSocketPtr -createConnectionSocket(Network::Address::InstanceConstSharedPtr& peer_addr, - Network::Address::InstanceConstSharedPtr& local_addr, - const Network::ConnectionSocket::OptionsSharedPtr& options) { - Network::IoHandlePtr io_handle = peer_addr->socket(Network::Address::SocketType::Datagram); - auto connection_socket = - std::make_unique(std::move(io_handle), local_addr, peer_addr); - connection_socket->addOptions(Network::SocketOptionFactory::buildIpPacketInfoOptions()); - connection_socket->addOptions(Network::SocketOptionFactory::buildRxQueueOverFlowOptions()); - if (options != nullptr) { - connection_socket->addOptions(options); - } - if (!Network::Socket::applyOptions(connection_socket->options(), *connection_socket, - envoy::api::v2::core::SocketOption::STATE_PREBIND)) { - connection_socket->close(); - ENVOY_LOG_MISC(error, "Fail to apply pre-bind options"); - return connection_socket; - } - local_addr->bind(connection_socket->ioHandle().fd()); - ASSERT(local_addr->ip()); - if (local_addr->ip()->port() == 0) { - // Get ephemeral port number. - local_addr = Network::Address::addressFromFd(connection_socket->ioHandle().fd()); - } - if (!Network::Socket::applyOptions(connection_socket->options(), *connection_socket, - envoy::api::v2::core::SocketOption::STATE_BOUND)) { - ENVOY_LOG_MISC(error, "Fail to apply post-bind options"); - connection_socket->close(); - } - return connection_socket; -} - EnvoyQuicClientConnection::EnvoyQuicClientConnection( const quic::QuicConnectionId& server_connection_id, Network::Address::InstanceConstSharedPtr& initial_peer_address, diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_client_connection.h b/source/extensions/quic_listeners/quiche/envoy_quic_client_connection.h index 0824259a538fa..32e952fdb9df0 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_client_connection.h +++ b/source/extensions/quic_listeners/quiche/envoy_quic_client_connection.h @@ -23,6 +23,14 @@ class EnvoyQuicClientConnection : public EnvoyQuicConnection, public Network::Ud Event::Dispatcher& dispatcher, const Network::ConnectionSocket::OptionsSharedPtr& options); + EnvoyQuicClientConnection(const quic::QuicConnectionId& server_connection_id, + quic::QuicConnectionHelperInterface& helper, + quic::QuicAlarmFactory& alarm_factory, quic::QuicPacketWriter* writer, + bool owns_writer, + const quic::ParsedQuicVersionVector& supported_versions, + Event::Dispatcher& dispatcher, + Network::ConnectionSocketPtr&& connection_socket); + // Overridden to un-register all file events. ~EnvoyQuicClientConnection() override; @@ -43,14 +51,6 @@ class EnvoyQuicClientConnection : public EnvoyQuicConnection, public Network::Ud Event::Dispatcher& dispatcher, Network::ConnectionSocketPtr&& connection_socket); - EnvoyQuicClientConnection(const quic::QuicConnectionId& server_connection_id, - quic::QuicConnectionHelperInterface& helper, - quic::QuicAlarmFactory& alarm_factory, quic::QuicPacketWriter* writer, - bool owns_writer, - const quic::ParsedQuicVersionVector& supported_versions, - Event::Dispatcher& dispatcher, - Network::ConnectionSocketPtr&& connection_socket); - void onFileEvent(uint32_t events); uint32_t packets_dropped_{0}; diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_client_session.cc b/source/extensions/quic_listeners/quiche/envoy_quic_client_session.cc index f9aeb81df3230..d218986736c6b 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_client_session.cc +++ b/source/extensions/quic_listeners/quiche/envoy_quic_client_session.cc @@ -12,12 +12,11 @@ EnvoyQuicClientSession::EnvoyQuicClientSession( : QuicFilterManagerConnectionImpl(*connection, dispatcher, send_buffer_limit), quic::QuicSpdyClientSession(config, supported_versions, connection.release(), server_id, crypto_config, push_promise_index) { - Initialize(); } EnvoyQuicClientSession::~EnvoyQuicClientSession() { ASSERT(!connection()->connected()); - QuicFilterManagerConnectionImpl::quic_connection_ = nullptr; + quic_connection_ = nullptr; } absl::string_view EnvoyQuicClientSession::requestedServerName() const { diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_client_stream.cc b/source/extensions/quic_listeners/quiche/envoy_quic_client_stream.cc index 3fc24dea4e25c..c2d1c042f7025 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_client_stream.cc +++ b/source/extensions/quic_listeners/quiche/envoy_quic_client_stream.cc @@ -8,7 +8,6 @@ #include "quiche/quic/core/quic_session.h" #include "quiche/quic/core/http/quic_header_list.h" -#include "quiche/quic/core/quic_session.h" #include "quiche/spdy/core/spdy_header_block.h" #include "extensions/quic_listeners/quiche/platform/quic_mem_slice_span_impl.h" @@ -67,8 +66,8 @@ void EnvoyQuicClientStream::encodeData(Buffer::Instance& data, bool end_stream) // If buffered bytes changed, update stream and session's watermark book // keeping. sendBufferSimulation().checkHighWatermark(bytes_to_send_new); - dynamic_cast(session())->adjustBytesToSend(bytes_to_send_new - - bytes_to_send_old); + dynamic_cast(session())->adjustBytesToSend(bytes_to_send_new - + bytes_to_send_old); } } @@ -173,10 +172,11 @@ void EnvoyQuicClientStream::OnBodyAvailable() { void EnvoyQuicClientStream::OnTrailingHeadersComplete(bool fin, size_t frame_len, const quic::QuicHeaderList& header_list) { quic::QuicSpdyStream::OnTrailingHeadersComplete(fin, frame_len, header_list); + ASSERT(trailers_decompressed()); if (session()->connection()->connected() && (quic::VersionUsesQpack(transport_version()) || sequencer()->IsClosed()) && !FinishedReadingTrailers()) { - // Before QPack trailers can arrive before body. Only decode trailers after finishing decoding + // Before QPack, trailers can arrive before body. Only decode trailers after finishing decoding // body. ASSERT(decoder() != nullptr); decoder()->decodeTrailers(spdyHeaderBlockToEnvoyHeaders(received_trailers())); @@ -204,15 +204,15 @@ void EnvoyQuicClientStream::OnCanWrite() { ASSERT(buffered_data_new <= buffered_data_old); if (buffered_data_new < buffered_data_old) { sendBufferSimulation().checkLowWatermark(buffered_data_new); - dynamic_cast(session())->adjustBytesToSend(buffered_data_new - - buffered_data_old); + dynamic_cast(session())->adjustBytesToSend(buffered_data_new - + buffered_data_old); } } uint32_t EnvoyQuicClientStream::streamId() { return id(); } Network::Connection* EnvoyQuicClientStream::connection() { - return dynamic_cast(session()); + return dynamic_cast(session()); } } // namespace Quic diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_utils.cc b/source/extensions/quic_listeners/quiche/envoy_quic_utils.cc index 6be73faef9546..b02f76c7b1ade 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_utils.cc +++ b/source/extensions/quic_listeners/quiche/envoy_quic_utils.cc @@ -2,6 +2,8 @@ #include +#include "common/network/socket_option_factory.h" + namespace Envoy { namespace Quic { @@ -107,5 +109,37 @@ Http::StreamResetReason quicErrorCodeToEnvoyResetReason(quic::QuicErrorCode erro } } +Network::ConnectionSocketPtr +createConnectionSocket(Network::Address::InstanceConstSharedPtr& peer_addr, + Network::Address::InstanceConstSharedPtr& local_addr, + const Network::ConnectionSocket::OptionsSharedPtr& options) { + Network::IoHandlePtr io_handle = peer_addr->socket(Network::Address::SocketType::Datagram); + auto connection_socket = + std::make_unique(std::move(io_handle), local_addr, peer_addr); + connection_socket->addOptions(Network::SocketOptionFactory::buildIpPacketInfoOptions()); + connection_socket->addOptions(Network::SocketOptionFactory::buildRxQueueOverFlowOptions()); + if (options != nullptr) { + connection_socket->addOptions(options); + } + if (!Network::Socket::applyOptions(connection_socket->options(), *connection_socket, + envoy::api::v2::core::SocketOption::STATE_PREBIND)) { + connection_socket->close(); + ENVOY_LOG_MISC(error, "Fail to apply pre-bind options"); + return connection_socket; + } + local_addr->bind(connection_socket->ioHandle().fd()); + ASSERT(local_addr->ip()); + if (local_addr->ip()->port() == 0) { + // Get ephemeral port number. + local_addr = Network::Address::addressFromFd(connection_socket->ioHandle().fd()); + } + if (!Network::Socket::applyOptions(connection_socket->options(), *connection_socket, + envoy::api::v2::core::SocketOption::STATE_BOUND)) { + ENVOY_LOG_MISC(error, "Fail to apply post-bind options"); + connection_socket->close(); + } + return connection_socket; +} + } // namespace Quic } // namespace Envoy diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_utils.h b/source/extensions/quic_listeners/quiche/envoy_quic_utils.h index 6505b22a66e28..037239e4ff249 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_utils.h +++ b/source/extensions/quic_listeners/quiche/envoy_quic_utils.h @@ -3,6 +3,7 @@ #include "common/common/assert.h" #include "common/http/header_map_impl.h" #include "common/network/address_impl.h" +#include "common/network/listen_socket_impl.h" #include "quiche/quic/core/http/quic_header_list.h" #include "quiche/quic/core/quic_error_codes.h" @@ -36,5 +37,12 @@ Http::StreamResetReason quicRstErrorToEnvoyResetReason(quic::QuicRstStreamErrorC // Called when underlying QUIC connection is closed either locally or by peer. Http::StreamResetReason quicErrorCodeToEnvoyResetReason(quic::QuicErrorCode error); +// Create a connection socket instance and apply given socket options to the +// socket. IP_PKTINFO and SO_RXQ_OVFL is always set if supported. +Network::ConnectionSocketPtr +createConnectionSocket(Network::Address::InstanceConstSharedPtr& peer_addr, + Network::Address::InstanceConstSharedPtr& local_addr, + const Network::ConnectionSocket::OptionsSharedPtr& options); + } // namespace Quic } // namespace Envoy diff --git a/test/extensions/quic_listeners/quiche/BUILD b/test/extensions/quic_listeners/quiche/BUILD index 102d405f95a8b..1e88b1f176549 100644 --- a/test/extensions/quic_listeners/quiche/BUILD +++ b/test/extensions/quic_listeners/quiche/BUILD @@ -63,7 +63,25 @@ envoy_cc_test( "//source/extensions/quic_listeners/quiche:envoy_quic_alarm_factory_lib", "//source/extensions/quic_listeners/quiche:envoy_quic_connection_helper_lib", "//source/extensions/quic_listeners/quiche:envoy_quic_server_connection_lib", - "//source/extensions/quic_listeners/quiche:envoy_quic_server_session_lib", + "//test/mocks/http:http_mocks", + "//test/mocks/http:stream_decoder_mock", + "//test/mocks/network:network_mocks", + "//test/test_common:utility_lib", + "@com_googlesource_quiche//:quic_core_http_spdy_session_lib", + ], +) + +envoy_cc_test( + name = "envoy_quic_client_stream_test", + srcs = ["envoy_quic_client_stream_test.cc"], + tags = ["nofips"], + deps = [ + ":quic_test_utils_for_envoy_lib", + ":test_utils_lib", + "//source/common/http:headers_lib", + "//source/extensions/quic_listeners/quiche:envoy_quic_alarm_factory_lib", + "//source/extensions/quic_listeners/quiche:envoy_quic_client_connection_lib", + "//source/extensions/quic_listeners/quiche:envoy_quic_connection_helper_lib", "//test/mocks/http:http_mocks", "//test/mocks/http:stream_decoder_mock", "//test/mocks/network:network_mocks", @@ -97,6 +115,27 @@ envoy_cc_test( ], ) +envoy_cc_test( + name = "envoy_quic_client_session_test", + srcs = ["envoy_quic_client_session_test.cc"], + tags = ["nofips"], + deps = [ + ":quic_test_utils_for_envoy_lib", + "//include/envoy/stats:stats_macros", + "//source/extensions/quic_listeners/quiche:codec_lib", + "//source/extensions/quic_listeners/quiche:envoy_quic_alarm_factory_lib", + "//source/extensions/quic_listeners/quiche:envoy_quic_client_connection_lib", + "//source/extensions/quic_listeners/quiche:envoy_quic_client_session_lib", + "//source/extensions/quic_listeners/quiche:envoy_quic_connection_helper_lib", + "//test/mocks/http:http_mocks", + "//test/mocks/http:stream_decoder_mock", + "//test/mocks/network:network_mocks", + "//test/mocks/stats:stats_mocks", + "//test/test_common:logging_lib", + "//test/test_common:simulated_time_system_lib", + ], +) + envoy_cc_test( name = "active_quic_listener_test", srcs = ["active_quic_listener_test.cc"], diff --git a/test/extensions/quic_listeners/quiche/envoy_quic_client_session_test.cc b/test/extensions/quic_listeners/quiche/envoy_quic_client_session_test.cc new file mode 100644 index 0000000000000..20dcb586ea254 --- /dev/null +++ b/test/extensions/quic_listeners/quiche/envoy_quic_client_session_test.cc @@ -0,0 +1,242 @@ +#pragma GCC diagnostic push +// QUICHE allows unused parameters. +#pragma GCC diagnostic ignored "-Wunused-parameter" +// QUICHE uses offsetof(). +#pragma GCC diagnostic ignored "-Winvalid-offsetof" + +#include "quiche/quic/core/crypto/null_encrypter.h" +#include "quiche/quic/test_tools/crypto_test_utils.h" +#include "quiche/quic/test_tools/quic_test_utils.h" + +#pragma GCC diagnostic pop + +#include "extensions/quic_listeners/quiche/envoy_quic_client_session.h" +#include "extensions/quic_listeners/quiche/envoy_quic_client_connection.h" +#include "extensions/quic_listeners/quiche/codec_impl.h" +#include "extensions/quic_listeners/quiche/envoy_quic_connection_helper.h" +#include "extensions/quic_listeners/quiche/envoy_quic_alarm_factory.h" +#include "extensions/quic_listeners/quiche/envoy_quic_utils.h" + +#include "envoy/stats/stats_macros.h" +#include "test/mocks/event/mocks.h" +#include "test/mocks/http/stream_decoder.h" +#include "test/mocks/http/mocks.h" +#include "test/mocks/network/mocks.h" +#include "test/mocks/stats/mocks.h" +#include "test/test_common/logging.h" +#include "test/test_common/simulated_time_system.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +using testing::_; +using testing::Invoke; +using testing::Return; +using testing::ReturnRef; + +namespace Envoy { +namespace Quic { + +class TestEnvoyQuicClientConnection : public EnvoyQuicClientConnection { +public: + TestEnvoyQuicClientConnection(const quic::QuicConnectionId& server_connection_id, + quic::QuicConnectionHelperInterface& helper, + quic::QuicAlarmFactory& alarm_factory, + quic::QuicPacketWriter& writer, + const quic::ParsedQuicVersionVector& supported_versions, + Event::Dispatcher& dispatcher, + Network::ConnectionSocketPtr&& connection_socket) + : EnvoyQuicClientConnection(server_connection_id, helper, alarm_factory, &writer, false, + supported_versions, dispatcher, std::move(connection_socket)) { + SetDefaultEncryptionLevel(quic::ENCRYPTION_FORWARD_SECURE); + SetEncrypter(quic::ENCRYPTION_FORWARD_SECURE, + std::make_unique(quic::Perspective::IS_CLIENT)); + quic::SetVerbosityLogThreshold(3); + } + + MOCK_METHOD2(SendConnectionClosePacket, void(quic::QuicErrorCode, const std::string&)); + MOCK_METHOD1(SendControlFrame, bool(const quic::QuicFrame& frame)); + + using EnvoyQuicClientConnection::connectionStats; +}; + +class TestQuicCryptoClientStream : public quic::QuicCryptoClientStream { +public: + TestQuicCryptoClientStream(const quic::QuicServerId& server_id, quic::QuicSession* session, + std::unique_ptr verify_context, + quic::QuicCryptoClientConfig* crypto_config, + ProofHandler* proof_handler) + : quic::QuicCryptoClientStream(server_id, session, std::move(verify_context), crypto_config, + proof_handler) {} + + bool encryption_established() const override { return true; } +}; + +class TestEnvoyQuicClientSession : public EnvoyQuicClientSession { +public: + TestEnvoyQuicClientSession(const quic::QuicConfig& config, + const quic::ParsedQuicVersionVector& supported_versions, + std::unique_ptr connection, + const quic::QuicServerId& server_id, + quic::QuicCryptoClientConfig* crypto_config, + quic::QuicClientPushPromiseIndex* push_promise_index, + Event::Dispatcher& dispatcher, uint32_t send_buffer_limit) + : EnvoyQuicClientSession(config, supported_versions, std::move(connection), server_id, + crypto_config, push_promise_index, dispatcher, send_buffer_limit) {} + + std::unique_ptr CreateQuicCryptoStream() override { + std::cerr << "========= CreateQuicCryptoStream \n"; + return std::make_unique( + server_id(), this, crypto_config()->proof_verifier()->CreateDefaultContext(), + crypto_config(), this); + } +}; + +class EnvoyQuicClientSessionTest : public testing::TestWithParam { +public: + EnvoyQuicClientSessionTest() + : api_(Api::createApiForTest(time_system_)), dispatcher_(api_->allocateDispatcher()), + connection_helper_(*dispatcher_), + alarm_factory_(*dispatcher_, *connection_helper_.GetClock()), quic_version_([]() { + SetQuicReloadableFlag(quic_enable_version_99, GetParam()); + std::cerr << "============= version use crypto frame " + << quic::QuicVersionUsesCryptoFrames( + quic::CurrentSupportedVersions()[0].transport_version) + << "\n"; + return quic::ParsedVersionOfIndex(quic::CurrentSupportedVersions(), 0); + }()), + peer_addr_(Network::Utility::getAddressWithPort(*Network::Utility::getIpv6LoopbackAddress(), + 12345)), + self_addr_(Network::Utility::getAddressWithPort(*Network::Utility::getIpv6LoopbackAddress(), + 54321)), + quic_connection_(new TestEnvoyQuicClientConnection( + quic::test::TestConnectionId(), connection_helper_, alarm_factory_, writer_, + quic_version_, *dispatcher_, createConnectionSocket(peer_addr_, self_addr_, nullptr))), + crypto_config_(quic::test::crypto_test_utils::ProofVerifierForTesting()), + envoy_quic_session_(quic_config_, quic_version_, + std::unique_ptr(quic_connection_), + quic::QuicServerId("example.com", 443, false), &crypto_config_, nullptr, + *dispatcher_, + /*send_buffer_limit*/ 1024 * 1024), + http_connection_(envoy_quic_session_, http_connection_callbacks_) { + EXPECT_EQ(time_system_.systemTime(), envoy_quic_session_.streamInfo().startTime()); + EXPECT_EQ(EMPTY_STRING, envoy_quic_session_.nextProtocol()); + EXPECT_EQ(Http::Protocol::Http3, http_connection_.protocol()); + + time_system_.sleep(std::chrono::milliseconds(1)); + ON_CALL(writer_, WritePacket(_, _, _, _, _)) + .WillByDefault(testing::Return(quic::WriteResult(quic::WRITE_STATUS_OK, 1))); + } + + void SetUp() override { + envoy_quic_session_.Initialize(); + envoy_quic_session_.addConnectionCallbacks(network_connection_callbacks_); + envoy_quic_session_.setConnectionStats( + {read_total_, read_current_, write_total_, write_current_, nullptr, nullptr}); + EXPECT_EQ(&read_total_, &quic_connection_->connectionStats().read_total_); + envoy_quic_session_.connect(); + } + + void TearDown() override { + if (quic_connection_->connected()) { + EXPECT_CALL(*quic_connection_, + SendConnectionClosePacket(quic::QUIC_NO_ERROR, "Closed by application")); + EXPECT_CALL(network_connection_callbacks_, onEvent(Network::ConnectionEvent::LocalClose)); + envoy_quic_session_.close(Network::ConnectionCloseType::NoFlush); + } + } + + EnvoyQuicClientStream& sendGetRequest(Http::StreamDecoder& response_decoder, + Http::StreamCallbacks& stream_callbacks) { + auto& stream = + dynamic_cast(http_connection_.newStream(response_decoder)); + stream.getStream().addCallbacks(stream_callbacks); + + std::string host("www.abc.com"); + Http::TestHeaderMapImpl request_headers{ + {":authority", host}, {":method", "GET"}, {":path", "/"}}; + stream.encodeHeaders(request_headers, true); + return stream; + } + +protected: + Event::SimulatedTimeSystemHelper time_system_; + Api::ApiPtr api_; + Event::DispatcherPtr dispatcher_; + EnvoyQuicConnectionHelper connection_helper_; + EnvoyQuicAlarmFactory alarm_factory_; + quic::ParsedQuicVersionVector quic_version_; + testing::NiceMock writer_; + Network::Address::InstanceConstSharedPtr peer_addr_; + Network::Address::InstanceConstSharedPtr self_addr_; + TestEnvoyQuicClientConnection* quic_connection_; + quic::QuicConfig quic_config_; + quic::QuicCryptoClientConfig crypto_config_; + TestEnvoyQuicClientSession envoy_quic_session_; + Network::MockConnectionCallbacks network_connection_callbacks_; + Http::MockServerConnectionCallbacks http_connection_callbacks_; + testing::StrictMock read_total_; + testing::StrictMock read_current_; + testing::StrictMock write_total_; + testing::StrictMock write_current_; + QuicHttpClientConnectionImpl http_connection_; +}; + +INSTANTIATE_TEST_SUITE_P(EnvoyQuicClientSessionTests, EnvoyQuicClientSessionTest, + testing::ValuesIn({true, false})); + +TEST_P(EnvoyQuicClientSessionTest, NewStream) { + Http::MockStreamDecoder response_decoder; + Http::MockStreamCallbacks stream_callbacks; + EnvoyQuicClientStream& stream = sendGetRequest(response_decoder, stream_callbacks); + + quic::QuicHeaderList headers; + headers.OnHeaderBlockStart(); + headers.OnHeader(":status", "200"); + headers.OnHeaderBlockEnd(/*uncompressed_header_bytes=*/0, /*compressed_header_bytes=*/0); + // Response headers should be propagated to decoder. + EXPECT_CALL(response_decoder, decodeHeaders_(_, /*end_stream=*/true)) + .WillOnce(Invoke([](const Http::HeaderMapPtr& decoded_headers, bool) { + EXPECT_EQ("200", decoded_headers->Status()->value().getStringView()); + })); + stream.OnStreamHeaderList(/*fin=*/true, headers.uncompressed_header_bytes(), headers); +} + +TEST_P(EnvoyQuicClientSessionTest, OnResetFrame) { + Http::MockStreamDecoder response_decoder; + Http::MockStreamCallbacks stream_callbacks; + EnvoyQuicClientStream& stream = sendGetRequest(response_decoder, stream_callbacks); + + // G-QUIC or IETF bi-directional stream. + quic::QuicStreamId stream_id = stream.id(); + quic::QuicRstStreamFrame rst1(/*control_frame_id=*/1u, stream_id, + quic::QUIC_ERROR_PROCESSING_STREAM, /*bytes_written=*/0u); + EXPECT_CALL(stream_callbacks, onResetStream(Http::StreamResetReason::RemoteReset, _)); + stream.OnStreamReset(rst1); +} + +TEST_P(EnvoyQuicClientSessionTest, ConnectionClose) { + std::string error_details("dummy details"); + quic::QuicErrorCode error(quic::QUIC_INVALID_FRAME_DATA); + quic::QuicConnectionCloseFrame frame(error, error_details); + EXPECT_CALL(network_connection_callbacks_, onEvent(Network::ConnectionEvent::RemoteClose)); + quic_connection_->OnConnectionCloseFrame(frame); + EXPECT_EQ(absl::StrCat(quic::QuicErrorCodeToString(error), " with details: ", error_details), + envoy_quic_session_.transportFailureReason()); + EXPECT_EQ(Network::Connection::State::Closed, envoy_quic_session_.state()); +} + +TEST_P(EnvoyQuicClientSessionTest, ConnectionCloseWithActiveStream) { + Http::MockStreamDecoder response_decoder; + Http::MockStreamCallbacks stream_callbacks; + EnvoyQuicClientStream& stream = sendGetRequest(response_decoder, stream_callbacks); + EXPECT_CALL(*quic_connection_, + SendConnectionClosePacket(quic::QUIC_NO_ERROR, "Closed by application")); + EXPECT_CALL(network_connection_callbacks_, onEvent(Network::ConnectionEvent::LocalClose)); + EXPECT_CALL(stream_callbacks, onResetStream(Http::StreamResetReason::ConnectionTermination, _)); + envoy_quic_session_.close(Network::ConnectionCloseType::NoFlush); + EXPECT_EQ(Network::Connection::State::Closed, envoy_quic_session_.state()); + EXPECT_TRUE(stream.write_side_closed() && stream.reading_stopped()); +} + +} // namespace Quic +} // namespace Envoy diff --git a/test/extensions/quic_listeners/quiche/envoy_quic_client_stream_test.cc b/test/extensions/quic_listeners/quiche/envoy_quic_client_stream_test.cc new file mode 100644 index 0000000000000..e8a821751c9d0 --- /dev/null +++ b/test/extensions/quic_listeners/quiche/envoy_quic_client_stream_test.cc @@ -0,0 +1,318 @@ +#pragma GCC diagnostic push +// QUICHE allows unused parameters. +#pragma GCC diagnostic ignored "-Wunused-parameter" +// QUICHE uses offsetof(). +#pragma GCC diagnostic ignored "-Winvalid-offsetof" + +#include "quiche/quic/core/http/quic_spdy_client_session.h" +#include "quiche/quic/test_tools/crypto_test_utils.h" + +#pragma GCC diagnostic pop + +#include "extensions/quic_listeners/quiche/envoy_quic_alarm_factory.h" +#include "extensions/quic_listeners/quiche/envoy_quic_connection_helper.h" +#include "extensions/quic_listeners/quiche/envoy_quic_client_connection.h" +#include "extensions/quic_listeners/quiche/envoy_quic_client_stream.h" +#include "extensions/quic_listeners/quiche/envoy_quic_utils.h" + +#include "test/extensions/quic_listeners/quiche/test_utils.h" +#include "test/mocks/http/mocks.h" +#include "test/mocks/http/stream_decoder.h" +#include "test/mocks/network/mocks.h" +#include "test/test_common/utility.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace Envoy { +namespace Quic { + +using testing::_; +using testing::Invoke; +using testing::Return; + +class MockEnvoyQuicClientSession : public quic::QuicSpdyClientSession, + public QuicFilterManagerConnectionImpl { +public: + MockEnvoyQuicClientSession(const quic::QuicConfig& config, + const quic::ParsedQuicVersionVector& supported_versions, + EnvoyQuicConnection* connection, Event::Dispatcher& dispatcher, + uint32_t send_buffer_limit) + : quic::QuicSpdyClientSession(config, supported_versions, connection, + quic::QuicServerId("example.com", 443, false), &crypto_config_, + nullptr), + QuicFilterManagerConnectionImpl(*connection, dispatcher, send_buffer_limit), + crypto_config_(quic::test::crypto_test_utils::ProofVerifierForTesting()) {} + + // From QuicSession. + MOCK_METHOD1(CreateIncomingStream, quic::QuicSpdyClientStream*(quic::QuicStreamId id)); + MOCK_METHOD1(CreateIncomingStream, quic::QuicSpdyClientStream*(quic::PendingStream* pending)); + MOCK_METHOD0(CreateOutgoingBidirectionalStream, quic::QuicSpdyClientStream*()); + MOCK_METHOD0(CreateOutgoingUnidirectionalStream, quic::QuicSpdyClientStream*()); + MOCK_METHOD1(ShouldCreateIncomingStream, bool(quic::QuicStreamId id)); + MOCK_METHOD0(ShouldCreateOutgoingBidirectionalStream, bool()); + MOCK_METHOD0(ShouldCreateOutgoingUnidirectionalStream, bool()); + MOCK_METHOD5(WritevData, + quic::QuicConsumedData(quic::QuicStream* stream, quic::QuicStreamId id, + size_t write_length, quic::QuicStreamOffset offset, + quic::StreamSendingState state)); + + absl::string_view requestedServerName() const override { + return {GetCryptoStream()->crypto_negotiated_params().sni}; + } + + using quic::QuicSpdySession::ActivateStream; + +private: + quic::QuicCryptoClientConfig crypto_config_; +}; + +class EnvoyQuicClientStreamTest : public testing::TestWithParam { +public: + EnvoyQuicClientStreamTest() + : api_(Api::createApiForTest()), dispatcher_(api_->allocateDispatcher()), + connection_helper_(*dispatcher_), + alarm_factory_(*dispatcher_, *connection_helper_.GetClock()), quic_version_([]() { + SetQuicReloadableFlag(quic_enable_version_99, GetParam()); + return quic::CurrentSupportedVersions()[0]; + }()), + peer_addr_(Network::Utility::getAddressWithPort(*Network::Utility::getIpv6LoopbackAddress(), + 12345)), + self_addr_(Network::Utility::getAddressWithPort(*Network::Utility::getIpv6LoopbackAddress(), + 54321)), + quic_connection_(new EnvoyQuicClientConnection( + quic::test::TestConnectionId(), connection_helper_, alarm_factory_, &writer_, + /*owns_writer=*/false, {quic_version_}, *dispatcher_, + createConnectionSocket(peer_addr_, self_addr_, nullptr))), + quic_session_(quic_config_, {quic_version_}, quic_connection_, *dispatcher_, + quic_config_.GetInitialStreamFlowControlWindowToSend() * 2), + stream_id_(quic_version_.transport_version == quic::QUIC_VERSION_99 ? 4u : 5u), + quic_stream_(new EnvoyQuicClientStream(stream_id_, &quic_session_, quic::BIDIRECTIONAL)), + request_headers_{{":authority", host_}, {":method", "GET"}, {":path", "/"}} { + quic::SetVerbosityLogThreshold(3); + quic_stream_->setDecoder(stream_decoder_); + quic_stream_->addCallbacks(stream_callbacks_); + quic_session_.ActivateStream(std::unique_ptr(quic_stream_)); + EXPECT_CALL(quic_session_, WritevData(_, _, _, _, _)) + .WillRepeatedly(Invoke([](quic::QuicStream*, quic::QuicStreamId, size_t write_length, + quic::QuicStreamOffset, quic::StreamSendingState) { + return quic::QuicConsumedData{write_length, true}; + })); + EXPECT_CALL(writer_, WritePacket(_, _, _, _, _)) + .WillRepeatedly(Invoke([](const char*, size_t buf_len, const quic::QuicIpAddress&, + const quic::QuicSocketAddress&, quic::PerPacketOptions*) { + return quic::WriteResult{quic::WRITE_STATUS_OK, static_cast(buf_len)}; + })); + } + + void SetUp() override { + quic_session_.Initialize(); + quic_connection_->setUpConnectionSocket(); + response_headers_.OnHeaderBlockStart(); + response_headers_.OnHeader(":status", "200"); + response_headers_.OnHeaderBlockEnd(/*uncompressed_header_bytes=*/0, + /*compressed_header_bytes=*/0); + + trailers_.OnHeaderBlockStart(); + trailers_.OnHeader("key1", "value1"); + if (quic_version_.transport_version != quic::QUIC_VERSION_99) { + // ":final-offset" is required and stripped off by quic. + trailers_.OnHeader(":final-offset", absl::StrCat("", response_body_.length())); + } + trailers_.OnHeaderBlockEnd(/*uncompressed_header_bytes=*/0, /*compressed_header_bytes=*/0); + } + + void TearDown() override { + if (quic_connection_->connected()) { + quic_connection_->CloseConnection( + quic::QUIC_NO_ERROR, "Closed by application", + quic::ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET); + } + } + +protected: + Api::ApiPtr api_; + Event::DispatcherPtr dispatcher_; + EnvoyQuicConnectionHelper connection_helper_; + EnvoyQuicAlarmFactory alarm_factory_; + testing::NiceMock writer_; + quic::ParsedQuicVersion quic_version_; + quic::QuicConfig quic_config_; + Network::Address::InstanceConstSharedPtr peer_addr_; + Network::Address::InstanceConstSharedPtr self_addr_; + EnvoyQuicClientConnection* quic_connection_; + MockEnvoyQuicClientSession quic_session_; + quic::QuicStreamId stream_id_; + EnvoyQuicClientStream* quic_stream_; + Http::MockStreamDecoder stream_decoder_; + Http::MockStreamCallbacks stream_callbacks_; + std::string host_{"www.abc.com"}; + Http::TestHeaderMapImpl request_headers_; + quic::QuicHeaderList response_headers_; + quic::QuicHeaderList trailers_; + Buffer::OwnedImpl request_body_{"Hello world"}; + std::string response_body_{"OK\n"}; +}; + +INSTANTIATE_TEST_SUITE_P(EnvoyQuicClientStreamTests, EnvoyQuicClientStreamTest, + testing::ValuesIn({true, false})); + +TEST_P(EnvoyQuicClientStreamTest, PostRequestAndResponse) { + quic_stream_->encodeHeaders(request_headers_, false); + quic_stream_->encodeData(request_body_, true); + + EXPECT_CALL(stream_decoder_, decodeHeaders_(_, /*end_stream=*/false)) + .WillOnce(Invoke([](const Http::HeaderMapPtr& headers, bool) { + EXPECT_EQ("200", headers->Status()->value().getStringView()); + })); + if (quic_version_.transport_version == quic::QUIC_VERSION_99) { + quic_stream_->OnHeadersDecoded(response_headers_); + } else { + quic_stream_->OnStreamHeaderList(/*fin=*/false, response_headers_.uncompressed_header_bytes(), + response_headers_); + } + EXPECT_TRUE(quic_stream_->FinishedReadingHeaders()); + + EXPECT_CALL(stream_decoder_, decodeData(_, _)) + .Times(testing::AtMost(2)) + .WillOnce(Invoke([&](Buffer::Instance& buffer, bool finished_reading) { + EXPECT_EQ(response_body_, buffer.toString()); + EXPECT_FALSE(finished_reading); + })) + // Depends on QUIC version, there may be an empty STREAM_FRAME with FIN. But + // since there is trailers, finished_reading should always be false. + .WillOnce(Invoke([](Buffer::Instance& buffer, bool finished_reading) { + EXPECT_FALSE(finished_reading); + EXPECT_EQ(0, buffer.length()); + })); + std::string data = response_body_; + if (quic_version_.transport_version == quic::QUIC_VERSION_99) { + std::unique_ptr data_buffer; + quic::HttpEncoder encoder; + quic::QuicByteCount data_frame_header_length = + encoder.SerializeDataFrameHeader(response_body_.length(), &data_buffer); + quic::QuicStringPiece data_frame_header(data_buffer.get(), data_frame_header_length); + data = absl::StrCat(data_frame_header, response_body_); + } + quic::QuicStreamFrame frame(stream_id_, false, 0, data); + quic_stream_->OnStreamFrame(frame); + + EXPECT_CALL(stream_decoder_, decodeTrailers_(_)) + .WillOnce(Invoke([](const Http::HeaderMapPtr& headers) { + Http::LowerCaseString key1("key1"); + Http::LowerCaseString key2(":final-offset"); + EXPECT_EQ("value1", headers->get(key1)->value().getStringView()); + EXPECT_EQ(nullptr, headers->get(key2)); + })); + quic_stream_->OnStreamHeaderList(/*fin=*/true, trailers_.uncompressed_header_bytes(), trailers_); +} + +TEST_P(EnvoyQuicClientStreamTest, OutOfOrderTrailers) { + if (quic::VersionUsesQpack(quic_version_.transport_version)) { + EXPECT_CALL(stream_callbacks_, onResetStream(_, _)); + return; + } + quic_stream_->encodeHeaders(request_headers_, true); + EXPECT_CALL(stream_decoder_, decodeHeaders_(_, /*end_stream=*/false)) + .WillOnce(Invoke([](const Http::HeaderMapPtr& headers, bool) { + EXPECT_EQ("200", headers->Status()->value().getStringView()); + })); + if (quic_version_.transport_version == quic::QUIC_VERSION_99) { + quic_stream_->OnHeadersDecoded(response_headers_); + } else { + quic_stream_->OnStreamHeaderList(/*fin=*/false, response_headers_.uncompressed_header_bytes(), + response_headers_); + } + EXPECT_TRUE(quic_stream_->FinishedReadingHeaders()); + + // Trailer should be delivered to HCM later after body arrives. + quic_stream_->OnStreamHeaderList(/*fin=*/true, trailers_.uncompressed_header_bytes(), trailers_); + + std::string data = response_body_; + if (quic_version_.transport_version == quic::QUIC_VERSION_99) { + std::unique_ptr data_buffer; + quic::HttpEncoder encoder; + quic::QuicByteCount data_frame_header_length = + encoder.SerializeDataFrameHeader(response_body_.length(), &data_buffer); + quic::QuicStringPiece data_frame_header(data_buffer.get(), data_frame_header_length); + data = absl::StrCat(data_frame_header, response_body_); + } + quic::QuicStreamFrame frame(stream_id_, false, 0, data); + EXPECT_CALL(stream_decoder_, decodeData(_, _)) + .Times(testing::AtMost(2)) + .WillOnce(Invoke([this](Buffer::Instance& buffer, bool finished_reading) { + EXPECT_EQ(response_body_, buffer.toString()); + EXPECT_FALSE(finished_reading); + })) + // Depends on QUIC version, there may be an empty STREAM_FRAME with FIN. But + // since there is trailers, finished_reading should always be false. + .WillOnce(Invoke([](Buffer::Instance& buffer, bool finished_reading) { + EXPECT_FALSE(finished_reading); + EXPECT_EQ(0, buffer.length()); + })); + + EXPECT_CALL(stream_decoder_, decodeTrailers_(_)) + .WillOnce(Invoke([](const Http::HeaderMapPtr& headers) { + Http::LowerCaseString key1("key1"); + Http::LowerCaseString key2(":final-offset"); + EXPECT_EQ("value1", headers->get(key1)->value().getStringView()); + EXPECT_EQ(nullptr, headers->get(key2)); + })); + quic_stream_->OnStreamFrame(frame); +} + +TEST_P(EnvoyQuicClientStreamTest, WatermarkSendBuffer) { + request_headers_.addCopy(":content-length", "32770"); // 32KB + 2 byte + quic_stream_->encodeHeaders(request_headers_, /*end_stream=*/false); + // encode 32kB request body. first 16KB shoudl be written out right away. The + // rest should be buffered. The high watermark is 16KB, so this call should + // make the send buffer reach its high watermark. + std::string request(32 * 1024 + 1, 'a'); + Buffer::OwnedImpl buffer(request); + EXPECT_CALL(stream_callbacks_, onAboveWriteBufferHighWatermark()); + quic_stream_->encodeData(buffer, false); + + EXPECT_EQ(0u, buffer.length()); + EXPECT_TRUE(quic_stream_->flow_controller()->IsBlocked()); + // Bump connection flow control window large enough not to cause connection + // level flow control blocked. + quic::QuicWindowUpdateFrame window_update( + quic::kInvalidControlFrameId, + quic::QuicUtils::GetInvalidStreamId(quic_version_.transport_version), 1024 * 1024); + quic_session_.OnWindowUpdateFrame(window_update); + + // Receive a WINDOW_UPDATE frame not large enough to drain half of the send + // buffer. + quic::QuicWindowUpdateFrame window_update1(quic::kInvalidControlFrameId, quic_stream_->id(), + 16 * 1024 + 8 * 1024); + quic_stream_->OnWindowUpdateFrame(window_update1); + EXPECT_FALSE(quic_stream_->flow_controller()->IsBlocked()); + quic_session_.OnCanWrite(); + EXPECT_TRUE(quic_stream_->flow_controller()->IsBlocked()); + + // Receive another WINDOW_UPDATE frame to drain the send buffer till below low + // watermark. + quic::QuicWindowUpdateFrame window_update2(quic::kInvalidControlFrameId, quic_stream_->id(), + 16 * 1024 + 8 * 1024 + 1024); + quic_stream_->OnWindowUpdateFrame(window_update2); + EXPECT_FALSE(quic_stream_->flow_controller()->IsBlocked()); + EXPECT_CALL(stream_callbacks_, onBelowWriteBufferLowWatermark()).WillOnce(Invoke([this]() { + std::string rest_request(1, 'a'); + Buffer::OwnedImpl buffer(rest_request); + quic_stream_->encodeData(buffer, true); + })); + quic_session_.OnCanWrite(); + EXPECT_TRUE(quic_stream_->flow_controller()->IsBlocked()); + + quic::QuicWindowUpdateFrame window_update3(quic::kInvalidControlFrameId, quic_stream_->id(), + 32 * 1024 + 1024); + quic_stream_->OnWindowUpdateFrame(window_update3); + quic_session_.OnCanWrite(); + + EXPECT_TRUE(quic_stream_->local_end_stream_); + EXPECT_TRUE(quic_stream_->write_side_closed()); + EXPECT_CALL(stream_callbacks_, onResetStream(_, _)); +} + +} // namespace Quic +} // namespace Envoy diff --git a/test/extensions/quic_listeners/quiche/envoy_quic_server_session_test.cc b/test/extensions/quic_listeners/quiche/envoy_quic_server_session_test.cc index 2e62a91b66534..d674ae9ae7f00 100644 --- a/test/extensions/quic_listeners/quiche/envoy_quic_server_session_test.cc +++ b/test/extensions/quic_listeners/quiche/envoy_quic_server_session_test.cc @@ -40,8 +40,6 @@ using testing::Invoke; using testing::Return; using testing::ReturnRef; -#include - namespace Envoy { namespace Quic { diff --git a/test/extensions/quic_listeners/quiche/integration/quic_http_integration_test.cc b/test/extensions/quic_listeners/quiche/integration/quic_http_integration_test.cc index 86d5f08b48f58..e0d23101b75e0 100644 --- a/test/extensions/quic_listeners/quiche/integration/quic_http_integration_test.cc +++ b/test/extensions/quic_listeners/quiche/integration/quic_http_integration_test.cc @@ -61,6 +61,7 @@ class QuicHttpIntegrationTest : public testing::TestWithParam( quic_config_, supported_versions_, std::move(connection), server_id_, &crypto_config_, &push_promise_index_, *dispatcher_, 0); + session->Initialize(); return session; } From 90a69535006452ce9e0b257353864c67195f9765 Mon Sep 17 00:00:00 2001 From: Dan Zhang Date: Wed, 23 Oct 2019 19:11:38 -0400 Subject: [PATCH 34/76] improve comment Signed-off-by: Dan Zhang --- .../quiche/envoy_quic_server_session.h | 3 +++ .../quiche/envoy_quic_server_stream.cc | 14 +++++++---- .../envoy_quic_simulated_watermark_buffer.h | 9 ++++---- .../quic_listeners/quiche/envoy_quic_stream.h | 5 +++- .../quiche/envoy_quic_server_session_test.cc | 23 ++++--------------- .../quiche/envoy_quic_server_stream_test.cc | 22 +++++++++--------- 6 files changed, 36 insertions(+), 40 deletions(-) diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_server_session.h b/source/extensions/quic_listeners/quiche/envoy_quic_server_session.h index 5c5561cbb7f52..e6cd850ea486f 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_server_session.h +++ b/source/extensions/quic_listeners/quiche/envoy_quic_server_session.h @@ -20,6 +20,9 @@ namespace Envoy { namespace Quic { // Act as a Network::Connection to HCM and a FilterManager to FilterFactoryCb. +// TODO(danzh) Lifetime of quic connection and filter manager connection can be +// simplified by changing the inheritance to a member variable instantiated +// before quic_connection_. class EnvoyQuicServerSession : public quic::QuicServerSessionBase, public QuicFilterManagerConnectionImpl { public: diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_server_stream.cc b/source/extensions/quic_listeners/quiche/envoy_quic_server_stream.cc index 23d3bca4c76e0..9d0dabf2f2810 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_server_stream.cc +++ b/source/extensions/quic_listeners/quiche/envoy_quic_server_stream.cc @@ -34,7 +34,7 @@ EnvoyQuicServerStream::EnvoyQuicServerStream(quic::QuicStreamId id, quic::QuicSp // This should be larger than 8k to fully utilize congestion control // window. And no larger than the max stream flow control window for // the stream to buffer all the data. - // Ideally this limit should also corelate to peer's receive window + // Ideally this limit should also correlate to peer's receive window // but not fully depends on that. 16 * 1024, [this]() { runLowWatermarkCallbacks(); }, [this]() { runHighWatermarkCallbacks(); }) {} @@ -68,7 +68,11 @@ void EnvoyQuicServerStream::encodeData(Buffer::Instance& data, bool end_stream) uint64_t bytes_to_send_old = BufferedDataBytes(); // QUIC stream must take all. WriteBodySlices(quic::QuicMemSliceSpan(quic::QuicMemSliceSpanImpl(data)), end_stream); - ASSERT(data.length() == 0); + if (data.length() > 0) { + // Send buffer didn't take all the data, threshold needs to be adjusted. + Reset(quic::QUIC_BAD_APPLICATION_PAYLOAD); + return; + } uint64_t bytes_to_send_new = BufferedDataBytes(); ASSERT(bytes_to_send_old <= bytes_to_send_new); @@ -89,7 +93,7 @@ void EnvoyQuicServerStream::encodeMetadata(const Http::MetadataMapVector& /*meta } void EnvoyQuicServerStream::resetStream(Http::StreamResetReason reason) { - // Higher layers expect calling resetStream() to immediately raise reset callbacks. + // Upper layers expect calling resetStream() to immediately raise reset callbacks. runResetCallbacks(reason); if (local_end_stream_ && !reading_stopped()) { // This is after 200 early response. Reset with QUIC_STREAM_NO_ERROR instead @@ -106,7 +110,7 @@ void EnvoyQuicServerStream::switchStreamBlockState(bool should_block) { if (should_block) { sequencer()->SetBlockedUntilFlush(); } else { - ASSERT(read_disable_counter_ == 0, "readDisable called in btw."); + ASSERT(read_disable_counter_ == 0, "readDisable called in between."); sequencer()->SetUnblocked(); } } @@ -206,7 +210,7 @@ void EnvoyQuicServerStream::OnConnectionClosed(quic::QuicErrorCode error, void EnvoyQuicServerStream::OnClose() { quic::QuicSpdyServerStreamBase::OnClose(); if (BufferedDataBytes() > 0) { - // If the stream is closed without sending out all bufferred data, regard + // If the stream is closed without sending out all buffered data, regard // them as sent now and adjust connection buffer book keeping. dynamic_cast(session())->adjustBytesToSend( 0 - BufferedDataBytes()); diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_simulated_watermark_buffer.h b/source/extensions/quic_listeners/quiche/envoy_quic_simulated_watermark_buffer.h index 484cda14da7f6..79ab88add7b6a 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_simulated_watermark_buffer.h +++ b/source/extensions/quic_listeners/quiche/envoy_quic_simulated_watermark_buffer.h @@ -10,8 +10,9 @@ namespace Envoy { namespace Quic { // A class, together with a stand alone buffer, used to achieve the purpose of WatermarkBuffer. -// Itself doesn't have buffer or do bookeeping of buffered bytes. But provided with buffered_bytes, +// Itself doesn't have buffer or bookkeep buffered bytes. But provided with buffered_bytes, // it re-acts upon crossing high/low watermarks. +// It's no-op if provided low and high watermark are 0. class EnvoyQuicSimulatedWatermarkBuffer { public: EnvoyQuicSimulatedWatermarkBuffer(uint32_t low_watermark, uint32_t high_watermark, @@ -28,8 +29,7 @@ class EnvoyQuicSimulatedWatermarkBuffer { void checkHighWatermark(uint32_t bytes_buffered) { if (high_watermark_ > 0 && !is_above_high_watermark_ && bytes_buffered > high_watermark_) { - // This is the first time for the buffer to cross the high watermark - // since it was once below low watermark. + // Transitioning from below low watermark to above high watermark. ENVOY_LOG_TO_LOGGER(logger_, debug, "Buffered {} bytes, cross high watermark {}", bytes_buffered, high_watermark_); is_above_high_watermark_ = true; @@ -40,8 +40,7 @@ class EnvoyQuicSimulatedWatermarkBuffer { void checkLowWatermark(uint32_t bytes_buffered) { if (low_watermark_ > 0 && !is_below_low_watermark_ && bytes_buffered < low_watermark_) { - // This is the first time for the buffer to cross the low watermark - // since it was once above high watermark. + // Transitioning from above high watermark to below low watermark. ENVOY_LOG_TO_LOGGER(logger_, debug, "Buffered {} bytes, cross low watermark {}", bytes_buffered, low_watermark_); is_below_low_watermark_ = true; diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_stream.h b/source/extensions/quic_listeners/quiche/envoy_quic_stream.h index c91e55fc20223..be9f54563f1cb 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_stream.h +++ b/source/extensions/quic_listeners/quiche/envoy_quic_stream.h @@ -42,6 +42,9 @@ class EnvoyQuicStream : public Http::StreamEncoder, } if (status_changed && !in_decode_data_callstack_) { + // Avoid calling this while decoding data because transient disabling and + // enabling reading may trigger another decoding data inside the + // callstack which messes up stream state. switchStreamBlockState(disable); } } @@ -84,7 +87,7 @@ class EnvoyQuicStream : public Http::StreamEncoder, return decoder_; } - // True once end of stream is propergated to Envoy. Envoy doesn't expect to be + // True once end of stream is propagated to Envoy. Envoy doesn't expect to be // notified more than once about end of stream. So once this is true, no need // to set it in the callback to Envoy stream any more. bool end_stream_decoded_{false}; diff --git a/test/extensions/quic_listeners/quiche/envoy_quic_server_session_test.cc b/test/extensions/quic_listeners/quiche/envoy_quic_server_session_test.cc index 5b2d5b80b6da1..2a3eb4612bbe2 100644 --- a/test/extensions/quic_listeners/quiche/envoy_quic_server_session_test.cc +++ b/test/extensions/quic_listeners/quiche/envoy_quic_server_session_test.cc @@ -76,17 +76,7 @@ class TestEnvoyQuicServerConnection : public EnvoyQuicServerConnection { // Derive to have simpler priority mechanism. class TestEnvoyQuicServerSession : public EnvoyQuicServerSession { public: - TestEnvoyQuicServerSession(const quic::QuicConfig& config, - const quic::ParsedQuicVersionVector& supported_versions, - std::unique_ptr connection, - quic::QuicSession::Visitor* visitor, - quic::QuicCryptoServerStream::Helper* helper, - const quic::QuicCryptoServerConfig* crypto_config, - quic::QuicCompressedCertsCache* compressed_certs_cache, - Event::Dispatcher& dispatcher, uint32_t send_buffer_limit) - : EnvoyQuicServerSession(config, supported_versions, std::move(connection), visitor, helper, - crypto_config, compressed_certs_cache, dispatcher, - send_buffer_limit) {} + using EnvoyQuicServerSession::EnvoyQuicServerSession; bool ShouldYield(quic::QuicStreamId /*stream_id*/) override { // Never yield to other stream so that it's easier to predict stream write @@ -429,11 +419,8 @@ TEST_P(EnvoyQuicServerSessionTest, NetworkConnectionInterface) { class TestQuicCryptoServerStream : public quic::QuicCryptoServerStream { public: - explicit TestQuicCryptoServerStream(const quic::QuicCryptoServerConfig* crypto_config, - quic::QuicCompressedCertsCache* compressed_certs_cache, - quic::QuicServerSessionBase* session, - quic::QuicCryptoServerStream::Helper* helper) - : quic::QuicCryptoServerStream(crypto_config, compressed_certs_cache, session, helper) {} + using quic::QuicCryptoServerStream::QuicCryptoServerStream; + bool encryption_established() const override { return true; } }; @@ -618,8 +605,8 @@ TEST_P(EnvoyQuicServerSessionTest, SendBufferWatermark) { envoy_quic_session_.OnCanWrite(); EXPECT_TRUE(stream2->flow_controller()->IsBlocked()); - // Resetting stream3 should lower the bufferred bytes, but callbacks will not - // be triggered because reset callback has been already triggerred. + // Resetting stream3 should lower the buffered bytes, but callbacks will not + // be triggered because reset callback has been already triggered. EXPECT_CALL(stream_callbacks3, onResetStream(Http::StreamResetReason::LocalReset, "")); // Connection buffered data book keeping should also be updated. EXPECT_CALL(network_connection_callbacks_, onBelowWriteBufferLowWatermark()); diff --git a/test/extensions/quic_listeners/quiche/envoy_quic_server_stream_test.cc b/test/extensions/quic_listeners/quiche/envoy_quic_server_stream_test.cc index e8c88281270b8..5753a293d4bf7 100644 --- a/test/extensions/quic_listeners/quiche/envoy_quic_server_stream_test.cc +++ b/test/extensions/quic_listeners/quiche/envoy_quic_server_stream_test.cc @@ -43,7 +43,7 @@ class EnvoyQuicServerStreamTest : public testing::TestWithParam { /*owns_writer=*/false, {quic_version_}, listener_config_, listener_stats_), quic_session_(quic_config_, {quic_version_}, &quic_connection_, *dispatcher_, quic_config_.GetInitialStreamFlowControlWindowToSend() * 2), - stream_id_(quic_version_.transport_version == quic::QUIC_VERSION_99 ? 4u : 5u), + stream_id_(VersionUsesQpack(quic_version_.transport_version) ? 4u : 5u), quic_stream_(new EnvoyQuicServerStream(stream_id_, &quic_session_, quic::BIDIRECTIONAL)), response_headers_{{":status", "200"}} { quic_stream_->setDecoder(stream_decoder_); @@ -72,7 +72,7 @@ class EnvoyQuicServerStreamTest : public testing::TestWithParam { trailers_.OnHeaderBlockStart(); trailers_.OnHeader("key1", "value1"); - if (quic_version_.transport_version != quic::QUIC_VERSION_99) { + if (!quic::VersionUsesQpack(quic_version_.transport_version)) { // ":final-offset" is required and stripped off by quic. trailers_.OnHeader(":final-offset", absl::StrCat("", request_body_.length())); } @@ -94,7 +94,7 @@ class EnvoyQuicServerStreamTest : public testing::TestWithParam { EXPECT_EQ(Http::Headers::get().MethodValues.Get, headers->Method()->value().getStringView()); })); - if (quic_version_.transport_version == quic::QUIC_VERSION_99) { + if (quic::VersionUsesQpack(quic_version_.transport_version)) { quic_stream_->OnHeadersDecoded(request_headers_); } else { quic_stream_->OnStreamHeaderList(/*fin=*/false, request_headers_.uncompressed_header_bytes(), @@ -111,7 +111,7 @@ class EnvoyQuicServerStreamTest : public testing::TestWithParam { } })); std::string data = payload; - if (quic_version_.transport_version == quic::QUIC_VERSION_99) { + if (quic::VersionUsesQpack(quic_version_.transport_version)) { std::unique_ptr data_buffer; quic::HttpEncoder encoder; quic::QuicByteCount data_frame_header_length = @@ -168,7 +168,7 @@ TEST_P(EnvoyQuicServerStreamTest, DecodeHeadersBodyAndTrailers) { EXPECT_TRUE(quic_stream_->FinishedReadingHeaders()); std::string data = request_body_; - if (quic_version_.transport_version == quic::QUIC_VERSION_99) { + if (quic::VersionUsesQpack(quic_version_.transport_version)) { std::unique_ptr data_buffer; quic::HttpEncoder encoder; quic::QuicByteCount data_frame_header_length = @@ -222,7 +222,7 @@ TEST_P(EnvoyQuicServerStreamTest, OutOfOrderTrailers) { quic_stream_->OnStreamHeaderList(/*fin=*/true, trailers_.uncompressed_header_bytes(), trailers_); std::string data = request_body_; - if (quic_version_.transport_version == quic::QUIC_VERSION_99) { + if (quic::VersionUsesQpack(quic_version_.transport_version)) { std::unique_ptr data_buffer; quic::HttpEncoder encoder; quic::QuicByteCount data_frame_header_length = @@ -262,7 +262,7 @@ TEST_P(EnvoyQuicServerStreamTest, ReadDisableUponLargePost) { // Disable reading one more time. quic_stream_->readDisable(true); std::string second_part_request("bbb"); - if (quic_version_.transport_version == quic::QUIC_VERSION_99) { + if (quic::VersionUsesQpack(quic_version_.transport_version)) { std::unique_ptr data_buffer; quic::HttpEncoder encoder; quic::QuicByteCount data_frame_header_length = @@ -281,7 +281,7 @@ TEST_P(EnvoyQuicServerStreamTest, ReadDisableUponLargePost) { // This data frame should also be buffered. std::string last_part_request("ccc"); - if (quic_version_.transport_version == quic::QUIC_VERSION_99) { + if (quic::VersionUsesQpack(quic_version_.transport_version)) { std::unique_ptr data_buffer; quic::HttpEncoder encoder; quic::QuicByteCount data_frame_header_length = @@ -315,7 +315,7 @@ TEST_P(EnvoyQuicServerStreamTest, ReadDisableAndReEnableImmediately) { EXPECT_EQ(Http::Headers::get().MethodValues.Get, headers->Method()->value().getStringView()); })); - if (quic_version_.transport_version == quic::QUIC_VERSION_99) { + if (quic::VersionUsesQpack(quic_version_.transport_version)) { quic_stream_->OnHeadersDecoded(request_headers_); } else { quic_stream_->OnStreamHeaderList(/*fin=*/false, request_headers_.uncompressed_header_bytes(), @@ -333,7 +333,7 @@ TEST_P(EnvoyQuicServerStreamTest, ReadDisableAndReEnableImmediately) { quic_stream_->readDisable(false); })); std::string data = payload; - if (quic_version_.transport_version == quic::QUIC_VERSION_99) { + if (quic::VersionUsesQpack(quic_version_.transport_version)) { std::unique_ptr data_buffer; quic::HttpEncoder encoder; quic::QuicByteCount data_frame_header_length = @@ -345,7 +345,7 @@ TEST_P(EnvoyQuicServerStreamTest, ReadDisableAndReEnableImmediately) { quic_stream_->OnStreamFrame(frame); std::string last_part_request("bbb"); - if (quic_version_.transport_version == quic::QUIC_VERSION_99) { + if (quic::VersionUsesQpack(quic_version_.transport_version)) { std::unique_ptr data_buffer; quic::HttpEncoder encoder; quic::QuicByteCount data_frame_header_length = From bddb942b74399c44c9433ce3899b02d4524c0c05 Mon Sep 17 00:00:00 2001 From: Dan Zhang Date: Thu, 24 Oct 2019 16:52:10 -0400 Subject: [PATCH 35/76] code cleanup in test Signed-off-by: Dan Zhang --- .../quiche/envoy_quic_server_stream.cc | 22 +-- .../quiche/envoy_quic_server_stream.h | 3 + .../envoy_quic_simulated_watermark_buffer.h | 17 +-- .../quic_listeners/quiche/envoy_quic_stream.h | 2 + .../quic_filter_manager_connection_impl.h | 2 + .../quiche/envoy_quic_server_session_test.cc | 10 +- .../quiche/envoy_quic_server_stream_test.cc | 141 ++++++------------ 7 files changed, 71 insertions(+), 126 deletions(-) diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_server_stream.cc b/source/extensions/quic_listeners/quiche/envoy_quic_server_stream.cc index 9d0dabf2f2810..ec50a92cd8ef8 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_server_stream.cc +++ b/source/extensions/quic_listeners/quiche/envoy_quic_server_stream.cc @@ -76,8 +76,7 @@ void EnvoyQuicServerStream::encodeData(Buffer::Instance& data, bool end_stream) uint64_t bytes_to_send_new = BufferedDataBytes(); ASSERT(bytes_to_send_old <= bytes_to_send_new); - maybeCheckWatermark(bytes_to_send_old, bytes_to_send_new, - dynamic_cast(*session())); + maybeCheckWatermark(bytes_to_send_old, bytes_to_send_new, *filterManagerConnection()); } void EnvoyQuicServerStream::encodeTrailers(const Http::HeaderMap& trailers) { @@ -152,8 +151,13 @@ void EnvoyQuicServerStream::OnBodyAvailable() { // True if no trailer and FIN read. bool finished_reading = IsDoneReading(); - bool empty_payload_with_fin = buffer->length() == 0 && finished_reading; - if (!empty_payload_with_fin || !end_stream_decoded_) { + bool empty_payload_with_fin = buffer->length() == 0 && fin_received(); + // If this call is triggered by an empty frame with FIN which is not from peer + // but synthesized by stream itself upon receiving HEADERS with FIN or + // TRAILERS, do not deliver end of stream here. Because either decodeHeaders + // already delivered it or decodeTrailers will be called. + bool skip_decoding = empty_payload_with_fin && (end_stream_decoded_ || !finished_reading); + if (!skip_decoding) { ASSERT(decoder() != nullptr); decoder()->decodeData(*buffer, finished_reading); if (finished_reading) { @@ -212,8 +216,7 @@ void EnvoyQuicServerStream::OnClose() { if (BufferedDataBytes() > 0) { // If the stream is closed without sending out all buffered data, regard // them as sent now and adjust connection buffer book keeping. - dynamic_cast(session())->adjustBytesToSend( - 0 - BufferedDataBytes()); + filterManagerConnection()->adjustBytesToSend(0 - BufferedDataBytes()); } } @@ -224,13 +227,14 @@ void EnvoyQuicServerStream::OnCanWrite() { // As long as OnCanWriteNewData() is no-op, data to sent in buffer shouldn't // increase. ASSERT(buffered_data_new <= buffered_data_old); - maybeCheckWatermark(buffered_data_old, buffered_data_new, - dynamic_cast(*session())); + maybeCheckWatermark(buffered_data_old, buffered_data_new, *filterManagerConnection()); } uint32_t EnvoyQuicServerStream::streamId() { return id(); } -Network::Connection* EnvoyQuicServerStream::connection() { +Network::Connection* EnvoyQuicServerStream::connection() { return filterManagerConnection(); } + +QuicFilterManagerConnectionImpl* EnvoyQuicServerStream::filterManagerConnection() { return dynamic_cast(session()); } diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_server_stream.h b/source/extensions/quic_listeners/quiche/envoy_quic_server_stream.h index 38326781be936..e79ac3d081ed2 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_server_stream.h +++ b/source/extensions/quic_listeners/quiche/envoy_quic_server_stream.h @@ -51,6 +51,9 @@ class EnvoyQuicServerStream : public quic::QuicSpdyServerStreamBase, public Envo const quic::QuicHeaderList& header_list) override; void OnTrailingHeadersComplete(bool fin, size_t frame_len, const quic::QuicHeaderList& header_list) override; + +private: + QuicFilterManagerConnectionImpl* filterManagerConnection(); }; } // namespace Quic diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_simulated_watermark_buffer.h b/source/extensions/quic_listeners/quiche/envoy_quic_simulated_watermark_buffer.h index 79ab88add7b6a..eca6706800a3d 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_simulated_watermark_buffer.h +++ b/source/extensions/quic_listeners/quiche/envoy_quic_simulated_watermark_buffer.h @@ -28,23 +28,21 @@ class EnvoyQuicSimulatedWatermarkBuffer { uint32_t highWatermark() const { return high_watermark_; } void checkHighWatermark(uint32_t bytes_buffered) { - if (high_watermark_ > 0 && !is_above_high_watermark_ && bytes_buffered > high_watermark_) { + if (high_watermark_ > 0 && !is_full_ && bytes_buffered > high_watermark_) { // Transitioning from below low watermark to above high watermark. ENVOY_LOG_TO_LOGGER(logger_, debug, "Buffered {} bytes, cross high watermark {}", bytes_buffered, high_watermark_); - is_above_high_watermark_ = true; - is_below_low_watermark_ = false; + is_full_ = true; above_high_watermark_(); } } void checkLowWatermark(uint32_t bytes_buffered) { - if (low_watermark_ > 0 && !is_below_low_watermark_ && bytes_buffered < low_watermark_) { + if (low_watermark_ > 0 && is_full_ && bytes_buffered < low_watermark_) { // Transitioning from above high watermark to below low watermark. ENVOY_LOG_TO_LOGGER(logger_, debug, "Buffered {} bytes, cross low watermark {}", bytes_buffered, low_watermark_); - is_below_low_watermark_ = true; - is_above_high_watermark_ = false; + is_full_ = false; below_low_watermark_(); } } @@ -52,18 +50,17 @@ class EnvoyQuicSimulatedWatermarkBuffer { // True after the buffer goes above high watermark and hasn't come down below low // watermark yet, even though the buffered data might be between high and low // watermarks. - bool isAboveHighWatermark() const { return is_above_high_watermark_; } + bool isAboveHighWatermark() const { return is_full_; } // True after the buffer goes below low watermark and hasn't come up above high // watermark yet, even though the buffered data might be between high and low // watermarks. - bool isBelowLowWatermark() const { return is_below_low_watermark_; } + bool isBelowLowWatermark() const { return !is_full_; } private: uint32_t low_watermark_{0}; - bool is_below_low_watermark_{true}; uint32_t high_watermark_{0}; - bool is_above_high_watermark_{false}; + bool is_full_{false}; std::function below_low_watermark_; std::function above_high_watermark_; spdlog::logger& logger_; diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_stream.h b/source/extensions/quic_listeners/quiche/envoy_quic_stream.h index be9f54563f1cb..3262373c4b5bb 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_stream.h +++ b/source/extensions/quic_listeners/quiche/envoy_quic_stream.h @@ -17,6 +17,8 @@ class EnvoyQuicStream : public Http::StreamEncoder, public Http::StreamCallbackHelper, protected Logger::Loggable { public: + // |buffer_limit| is the high watermark of the stream send buffer, and the low + // watermark will be half of it. EnvoyQuicStream(uint32_t buffer_limit, std::function below_low_watermark, std::function above_high_watermark) : send_buffer_simulation_(buffer_limit / 2, buffer_limit, std::move(below_low_watermark), diff --git a/source/extensions/quic_listeners/quiche/quic_filter_manager_connection_impl.h b/source/extensions/quic_listeners/quiche/quic_filter_manager_connection_impl.h index 33e8681f707f4..0db77afce6e5a 100644 --- a/source/extensions/quic_listeners/quiche/quic_filter_manager_connection_impl.h +++ b/source/extensions/quic_listeners/quiche/quic_filter_manager_connection_impl.h @@ -38,6 +38,8 @@ class QuicFilterManagerConnectionImpl : public Network::FilterManagerConnection, void enableHalfClose(bool enabled) override; void close(Network::ConnectionCloseType type) override; Event::Dispatcher& dispatcher() override { return dispatcher_; } + // Using this for purpose other than logging is not safe. Because QUIC connection id can be + // 18 bytes, so there might be collision when it's hashed to 8 bytes. uint64_t id() const override { return id_; } std::string nextProtocol() const override { return EMPTY_STRING; } void noDelay(bool /*enable*/) override { diff --git a/test/extensions/quic_listeners/quiche/envoy_quic_server_session_test.cc b/test/extensions/quic_listeners/quiche/envoy_quic_server_session_test.cc index 2a3eb4612bbe2..f6bc145398e4f 100644 --- a/test/extensions/quic_listeners/quiche/envoy_quic_server_session_test.cc +++ b/test/extensions/quic_listeners/quiche/envoy_quic_server_session_test.cc @@ -436,18 +436,13 @@ TEST_P(EnvoyQuicServerSessionTest, SendBufferWatermark) { quic::ENCRYPTION_FORWARD_SECURE, std::make_unique(quic::Perspective::IS_SERVER)); // Drive congestion control manually. - auto send_algorithm = new testing::StrictMock; + auto send_algorithm = new testing::NiceMock; quic::test::QuicConnectionPeer::SetSendAlgorithm(quic_connection_, send_algorithm); EXPECT_CALL(*send_algorithm, CanSend(_)).WillRepeatedly(Return(true)); - EXPECT_CALL(*send_algorithm, OnPacketSent(_, _, _, _, _)).Times(AnyNumber()); EXPECT_CALL(*send_algorithm, GetCongestionWindow()).WillRepeatedly(Return(quic::kDefaultTCPMSS)); EXPECT_CALL(*send_algorithm, PacingRate(_)).WillRepeatedly(Return(quic::QuicBandwidth::Zero())); - EXPECT_CALL(*send_algorithm, HasReliableBandwidthEstimate()).Times(AnyNumber()); EXPECT_CALL(*send_algorithm, BandwidthEstimate()) .WillRepeatedly(Return(quic::QuicBandwidth::Zero())); - EXPECT_CALL(*send_algorithm, InSlowStart()).Times(AnyNumber()); - EXPECT_CALL(*send_algorithm, InRecovery()).Times(AnyNumber()); - EXPECT_CALL(*send_algorithm, OnApplicationLimited(_)).Times(AnyNumber()); EXPECT_CALL(*quic_connection_, SendControlFrame(_)).Times(AnyNumber()); // Bump connection flow control window large enough not to interfere @@ -520,9 +515,6 @@ TEST_P(EnvoyQuicServerSessionTest, SendBufferWatermark) { EXPECT_EQ(Http::Headers::get().MethodValues.Get, decoded_headers->Method()->value().getStringView()); })); - EXPECT_CALL(request_decoder2, decodeData(_, true)) - .Times(testing::AtMost(1)) - .WillOnce(Invoke([](Buffer::Instance& buffer, bool) { EXPECT_EQ(0, buffer.length()); })); stream2->OnStreamHeaderList(/*fin=*/true, request_headers.uncompressed_header_bytes(), request_headers); stream2->encodeHeaders(response_headers, false); diff --git a/test/extensions/quic_listeners/quiche/envoy_quic_server_stream_test.cc b/test/extensions/quic_listeners/quiche/envoy_quic_server_stream_test.cc index 5753a293d4bf7..e3bbaac8d0e4a 100644 --- a/test/extensions/quic_listeners/quiche/envoy_quic_server_stream_test.cc +++ b/test/extensions/quic_listeners/quiche/envoy_quic_server_stream_test.cc @@ -65,7 +65,7 @@ class EnvoyQuicServerStreamTest : public testing::TestWithParam { quic_session_.Initialize(); request_headers_.OnHeaderBlockStart(); request_headers_.OnHeader(":authority", host_); - request_headers_.OnHeader(":method", "GET"); + request_headers_.OnHeader(":method", "POST"); request_headers_.OnHeader(":path", "/"); request_headers_.OnHeaderBlockEnd(/*uncompressed_header_bytes=*/0, /*compressed_header_bytes=*/0); @@ -81,17 +81,29 @@ class EnvoyQuicServerStreamTest : public testing::TestWithParam { void TearDown() override { if (quic_connection_.connected()) { - quic_connection_.CloseConnection(quic::QUIC_NO_ERROR, "Closed by application", - quic::ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET); + quic_session_.close(Network::ConnectionCloseType::NoFlush); } } + std::string bodyToStreamPayload(const std::string& body) { + std::string data = body; + if (quic::VersionUsesQpack(quic_version_.transport_version)) { + std::unique_ptr data_buffer; + quic::HttpEncoder encoder; + quic::QuicByteCount data_frame_header_length = + encoder.SerializeDataFrameHeader(body.length(), &data_buffer); + quic::QuicStringPiece data_frame_header(data_buffer.get(), data_frame_header_length); + data = absl::StrCat(data_frame_header, body); + } + return data; + } + size_t sendRequest(const std::string& payload, bool fin, size_t decoder_buffer_high_watermark) { EXPECT_CALL(stream_decoder_, decodeHeaders_(_, /*end_stream=*/false)) .WillOnce(Invoke([this](const Http::HeaderMapPtr& headers, bool) { EXPECT_EQ(host_, headers->Host()->value().getStringView()); EXPECT_EQ("/", headers->Path()->value().getStringView()); - EXPECT_EQ(Http::Headers::get().MethodValues.Get, + EXPECT_EQ(Http::Headers::get().MethodValues.Post, headers->Method()->value().getStringView()); })); if (quic::VersionUsesQpack(quic_version_.transport_version)) { @@ -110,15 +122,7 @@ class EnvoyQuicServerStreamTest : public testing::TestWithParam { quic_stream_->readDisable(true); } })); - std::string data = payload; - if (quic::VersionUsesQpack(quic_version_.transport_version)) { - std::unique_ptr data_buffer; - quic::HttpEncoder encoder; - quic::QuicByteCount data_frame_header_length = - encoder.SerializeDataFrameHeader(payload.length(), &data_buffer); - quic::QuicStringPiece data_frame_header(data_buffer.get(), data_frame_header_length); - data = absl::StrCat(data_frame_header, payload); - } + std::string data = bodyToStreamPayload(payload); quic::QuicStreamFrame frame(stream_id_, fin, 0, data); quic_stream_->OnStreamFrame(frame); return data.length(); @@ -150,47 +154,35 @@ class EnvoyQuicServerStreamTest : public testing::TestWithParam { INSTANTIATE_TEST_SUITE_P(EnvoyQuicServerStreamTests, EnvoyQuicServerStreamTest, testing::ValuesIn({true, false})); -TEST_P(EnvoyQuicServerStreamTest, PostRequestAndResponse) { - sendRequest(request_body_, true, request_body_.size() * 2); - quic_stream_->encodeHeaders(response_headers_, /*end_stream=*/true); -} +TEST_P(EnvoyQuicServerStreamTest, GetRequestAndResponse) { + quic::QuicHeaderList request_headers; + request_headers.OnHeaderBlockStart(); + request_headers.OnHeader(":authority", host_); + request_headers.OnHeader(":method", "GET"); + request_headers.OnHeader(":path", "/"); + request_headers.OnHeaderBlockEnd(/*uncompressed_header_bytes=*/0, + /*compressed_header_bytes=*/0); -TEST_P(EnvoyQuicServerStreamTest, DecodeHeadersBodyAndTrailers) { - EXPECT_CALL(stream_decoder_, decodeHeaders_(_, /*end_stream=*/false)) + EXPECT_CALL(stream_decoder_, decodeHeaders_(_, /*end_stream=*/true)) .WillOnce(Invoke([this](const Http::HeaderMapPtr& headers, bool) { EXPECT_EQ(host_, headers->Host()->value().getStringView()); EXPECT_EQ("/", headers->Path()->value().getStringView()); EXPECT_EQ(Http::Headers::get().MethodValues.Get, headers->Method()->value().getStringView()); })); - quic_stream_->OnStreamHeaderList(/*fin=*/false, request_headers_.uncompressed_header_bytes(), - request_headers_); + quic_stream_->OnStreamHeaderList(/*fin=*/true, request_headers.uncompressed_header_bytes(), + request_headers); EXPECT_TRUE(quic_stream_->FinishedReadingHeaders()); + quic_stream_->encodeHeaders(response_headers_, /*end_stream=*/true); +} - std::string data = request_body_; - if (quic::VersionUsesQpack(quic_version_.transport_version)) { - std::unique_ptr data_buffer; - quic::HttpEncoder encoder; - quic::QuicByteCount data_frame_header_length = - encoder.SerializeDataFrameHeader(request_body_.length(), &data_buffer); - quic::QuicStringPiece data_frame_header(data_buffer.get(), data_frame_header_length); - data = absl::StrCat(data_frame_header, request_body_); - } - quic::QuicStreamFrame frame(stream_id_, false, 0, data); - EXPECT_CALL(stream_decoder_, decodeData(_, _)) - .Times(testing::AtMost(2)) - .WillOnce(Invoke([this](Buffer::Instance& buffer, bool finished_reading) { - EXPECT_EQ(request_body_, buffer.toString()); - EXPECT_FALSE(finished_reading); - })) - // Depends on QUIC version, there may be an empty STREAM_FRAME with FIN. But - // since there is trailers, finished_reading should always be false. - .WillOnce(Invoke([](Buffer::Instance& buffer, bool finished_reading) { - EXPECT_FALSE(finished_reading); - EXPECT_EQ(0, buffer.length()); - })); - quic_stream_->OnStreamFrame(frame); +TEST_P(EnvoyQuicServerStreamTest, PostRequestAndResponse) { + sendRequest(request_body_, true, request_body_.size() * 2); + quic_stream_->encodeHeaders(response_headers_, /*end_stream=*/true); +} +TEST_P(EnvoyQuicServerStreamTest, DecodeHeadersBodyAndTrailers) { + sendRequest(request_body_, false, request_body_.size() * 2); EXPECT_CALL(stream_decoder_, decodeTrailers_(_)) .WillOnce(Invoke([](const Http::HeaderMapPtr& headers) { Http::LowerCaseString key1("key1"); @@ -211,7 +203,7 @@ TEST_P(EnvoyQuicServerStreamTest, OutOfOrderTrailers) { .WillOnce(Invoke([this](const Http::HeaderMapPtr& headers, bool) { EXPECT_EQ(host_, headers->Host()->value().getStringView()); EXPECT_EQ("/", headers->Path()->value().getStringView()); - EXPECT_EQ(Http::Headers::get().MethodValues.Get, + EXPECT_EQ(Http::Headers::get().MethodValues.Post, headers->Method()->value().getStringView()); })); quic_stream_->OnStreamHeaderList(/*fin=*/false, request_headers_.uncompressed_header_bytes(), @@ -221,27 +213,12 @@ TEST_P(EnvoyQuicServerStreamTest, OutOfOrderTrailers) { // Trailer should be delivered to HCM later after body arrives. quic_stream_->OnStreamHeaderList(/*fin=*/true, trailers_.uncompressed_header_bytes(), trailers_); - std::string data = request_body_; - if (quic::VersionUsesQpack(quic_version_.transport_version)) { - std::unique_ptr data_buffer; - quic::HttpEncoder encoder; - quic::QuicByteCount data_frame_header_length = - encoder.SerializeDataFrameHeader(request_body_.length(), &data_buffer); - quic::QuicStringPiece data_frame_header(data_buffer.get(), data_frame_header_length); - data = absl::StrCat(data_frame_header, request_body_); - } + std::string data = bodyToStreamPayload(request_body_); quic::QuicStreamFrame frame(stream_id_, false, 0, data); EXPECT_CALL(stream_decoder_, decodeData(_, _)) - .Times(testing::AtMost(2)) .WillOnce(Invoke([this](Buffer::Instance& buffer, bool finished_reading) { EXPECT_EQ(request_body_, buffer.toString()); EXPECT_FALSE(finished_reading); - })) - // Depends on QUIC version, there may be an empty STREAM_FRAME with FIN. But - // since there is trailers, finished_reading should always be false. - .WillOnce(Invoke([](Buffer::Instance& buffer, bool finished_reading) { - EXPECT_FALSE(finished_reading); - EXPECT_EQ(0, buffer.length()); })); EXPECT_CALL(stream_decoder_, decodeTrailers_(_)) @@ -261,15 +238,7 @@ TEST_P(EnvoyQuicServerStreamTest, ReadDisableUponLargePost) { EXPECT_FALSE(quic_stream_->HasBytesToRead()); // Disable reading one more time. quic_stream_->readDisable(true); - std::string second_part_request("bbb"); - if (quic::VersionUsesQpack(quic_version_.transport_version)) { - std::unique_ptr data_buffer; - quic::HttpEncoder encoder; - quic::QuicByteCount data_frame_header_length = - encoder.SerializeDataFrameHeader(second_part_request.length(), &data_buffer); - quic::QuicStringPiece data_frame_header(data_buffer.get(), data_frame_header_length); - second_part_request = absl::StrCat(data_frame_header, second_part_request); - } + std::string second_part_request = bodyToStreamPayload("bbb"); // Receiving more data shouldn't push the receiving pipe line as the stream // should have been marked blocked. quic::QuicStreamFrame frame(stream_id_, false, payload_offset, second_part_request); @@ -280,15 +249,7 @@ TEST_P(EnvoyQuicServerStreamTest, ReadDisableUponLargePost) { quic_stream_->readDisable(false); // This data frame should also be buffered. - std::string last_part_request("ccc"); - if (quic::VersionUsesQpack(quic_version_.transport_version)) { - std::unique_ptr data_buffer; - quic::HttpEncoder encoder; - quic::QuicByteCount data_frame_header_length = - encoder.SerializeDataFrameHeader(last_part_request.length(), &data_buffer); - quic::QuicStringPiece data_frame_header(data_buffer.get(), data_frame_header_length); - last_part_request = absl::StrCat(data_frame_header, last_part_request); - } + std::string last_part_request = bodyToStreamPayload("ccc"); quic::QuicStreamFrame frame2(stream_id_, true, payload_offset + second_part_request.length(), last_part_request); quic_stream_->OnStreamFrame(frame2); @@ -312,7 +273,7 @@ TEST_P(EnvoyQuicServerStreamTest, ReadDisableAndReEnableImmediately) { .WillOnce(Invoke([this](const Http::HeaderMapPtr& headers, bool) { EXPECT_EQ(host_, headers->Host()->value().getStringView()); EXPECT_EQ("/", headers->Path()->value().getStringView()); - EXPECT_EQ(Http::Headers::get().MethodValues.Get, + EXPECT_EQ(Http::Headers::get().MethodValues.Post, headers->Method()->value().getStringView()); })); if (quic::VersionUsesQpack(quic_version_.transport_version)) { @@ -332,27 +293,11 @@ TEST_P(EnvoyQuicServerStreamTest, ReadDisableAndReEnableImmediately) { // Re-enable reading should not trigger another decodeData. quic_stream_->readDisable(false); })); - std::string data = payload; - if (quic::VersionUsesQpack(quic_version_.transport_version)) { - std::unique_ptr data_buffer; - quic::HttpEncoder encoder; - quic::QuicByteCount data_frame_header_length = - encoder.SerializeDataFrameHeader(payload.length(), &data_buffer); - quic::QuicStringPiece data_frame_header(data_buffer.get(), data_frame_header_length); - data = absl::StrCat(data_frame_header, payload); - } + std::string data = bodyToStreamPayload(payload); quic::QuicStreamFrame frame(stream_id_, false, 0, data); quic_stream_->OnStreamFrame(frame); - std::string last_part_request("bbb"); - if (quic::VersionUsesQpack(quic_version_.transport_version)) { - std::unique_ptr data_buffer; - quic::HttpEncoder encoder; - quic::QuicByteCount data_frame_header_length = - encoder.SerializeDataFrameHeader(last_part_request.length(), &data_buffer); - quic::QuicStringPiece data_frame_header(data_buffer.get(), data_frame_header_length); - last_part_request = absl::StrCat(data_frame_header, last_part_request); - } + std::string last_part_request = bodyToStreamPayload("bbb"); quic::QuicStreamFrame frame2(stream_id_, true, data.length(), last_part_request); EXPECT_CALL(stream_decoder_, decodeData(_, _)) .WillOnce(Invoke([&](Buffer::Instance& buffer, bool finished_reading) { From 81e2fa9cd3c767f43d2d4678ef383c6d313b7fd1 Mon Sep 17 00:00:00 2001 From: Dan Zhang Date: Thu, 24 Oct 2019 16:56:42 -0400 Subject: [PATCH 36/76] fix quiche_copts Signed-off-by: Dan Zhang --- bazel/external/quiche.BUILD | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bazel/external/quiche.BUILD b/bazel/external/quiche.BUILD index ac6e391d9a220..1b9e08f22145d 100644 --- a/bazel/external/quiche.BUILD +++ b/bazel/external/quiche.BUILD @@ -3120,7 +3120,7 @@ envoy_cc_test_library( hdrs = [ "quiche/quic/test_tools/quic_server_session_base_peer.h", ], - copts = quiche_copt, + copts = quiche_copts, repository = "@envoy", tags = ["nofips"], deps = [ From 9f00be6ef0185ead5af44784d5f94984ade658f5 Mon Sep 17 00:00:00 2001 From: Dan Zhang Date: Fri, 25 Oct 2019 12:32:24 -0400 Subject: [PATCH 37/76] add quic codec factory Signed-off-by: Dan Zhang --- include/envoy/http/codec.h | 2 ++ source/common/http/BUILD | 8 +++++-- source/common/http/codec_client.cc | 20 ++++++----------- source/common/http/http3/BUILD | 21 ++++++++++++++++++ source/common/http/http3/quic_codec_factory.h | 22 +++++++++++++++++++ source/common/http/http3/well_known_names.h | 19 ++++++++++++++++ .../network/http_connection_manager/config.cc | 12 +++++----- source/extensions/quic_listeners/quiche/BUILD | 3 +++ .../quic_listeners/quiche/codec_impl.cc | 18 +++++++++++++++ .../quic_listeners/quiche/codec_impl.h | 22 +++++++++++++++++++ .../quiche/envoy_quic_client_session.cc | 3 +-- .../quic_listeners/quiche/integration/BUILD | 1 + 12 files changed, 128 insertions(+), 23 deletions(-) create mode 100644 source/common/http/http3/BUILD create mode 100644 source/common/http/http3/quic_codec_factory.h create mode 100644 source/common/http/http3/well_known_names.h diff --git a/include/envoy/http/codec.h b/include/envoy/http/codec.h index 4c456d2e296e5..34241c73b0bfa 100644 --- a/include/envoy/http/codec.h +++ b/include/envoy/http/codec.h @@ -363,6 +363,8 @@ class Connection { virtual void onUnderlyingConnectionBelowWriteBufferLowWatermark() PURE; }; +using ConnectionPtr = std::unique_ptr; + /** * Callbacks for downstream connection watermark limits. */ diff --git a/source/common/http/BUILD b/source/common/http/BUILD index 1ab166c6412a4..cfe6b92cb48f2 100644 --- a/source/common/http/BUILD +++ b/source/common/http/BUILD @@ -48,10 +48,12 @@ envoy_cc_library( "//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_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/extensions/quic_listeners/quiche:codec_lib", ], ) @@ -182,15 +184,17 @@ envoy_cc_library( "//source/common/common:regex_lib", "//source/common/common:scope_tracker", "//source/common/common:utility_lib", + "//source/common/config:utility_lib", "//source/common/http/http1:codec_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:utility_lib", "//source/common/router:config_lib", "//source/common/runtime:uuid_util_lib", "//source/common/stats:timespan_lib", "//source/common/stream_info:stream_info_lib", "//source/common/tracing:http_tracer_lib", - "//source/extensions/quic_listeners/quiche:codec_lib", ], ) diff --git a/source/common/http/codec_client.cc b/source/common/http/codec_client.cc index 642d4d962b2ad..92e7c2ff55382 100644 --- a/source/common/http/codec_client.cc +++ b/source/common/http/codec_client.cc @@ -4,13 +4,14 @@ #include #include "common/common/enum_to_int.h" +#include "common/config/utility.h" #include "common/http/exception.h" #include "common/http/http1/codec_impl.h" #include "common/http/http2/codec_impl.h" +#include "common/http/http3/quic_codec_factory.h" +#include "common/http/http3/well_known_names.h" #include "common/http/utility.h" -#include "extensions/quic_listeners/quiche/codec_impl.h" - namespace Envoy { namespace Http { @@ -158,17 +159,10 @@ CodecClientProd::CodecClientProd(Type type, Network::ClientConnectionPtr&& conne break; } case Type::HTTP3: { - // TODO(danzh) this enforce dependency from core code to QUICHE. Is there a - // better way to aoivd such dependency in case QUICHE breaks Envoy build. - // Alternatives: - // 1) move codec creation to Network::Connection instance, in - // QUIC's case, EnvoyQuicClientSession. This is not ideal as - // Network::Connection is not necessart to speak HTTP. - // 2) make codec creation in a static registered factory again. It can be - // only necessary for QUIC and for HTTP2 and HTTP1 just use the existing - // logic. - codec_ = std::make_unique( - dynamic_cast(*connection_), *this); + codec_ = std::unique_ptr(dynamic_cast( + Config::Utility::getAndCheckFactory( + Http::QuicCodecNames::get().Client) + .createQuicHttpConnection(*connection_, *this))); } } } diff --git a/source/common/http/http3/BUILD b/source/common/http/http3/BUILD new file mode 100644 index 0000000000000..086a4f4b902d2 --- /dev/null +++ b/source/common/http/http3/BUILD @@ -0,0 +1,21 @@ +licenses(["notice"]) # Apache 2 + +load( + "//bazel:envoy_build_system.bzl", + "envoy_cc_library", + "envoy_package", +) + +envoy_package() + +envoy_cc_library( + name = "quic_codec_factory_lib", + hdrs = ["quic_codec_factory.h"], + deps = ["//include/envoy/http:codec_interface"], +) + +envoy_cc_library( + name = "well_known_names", + hdrs = ["well_known_names.h"], + deps = ["//source/common/singleton:const_singleton"], +) diff --git a/source/common/http/http3/quic_codec_factory.h b/source/common/http/http3/quic_codec_factory.h new file mode 100644 index 0000000000000..cb4aff6627ebf --- /dev/null +++ b/source/common/http/http3/quic_codec_factory.h @@ -0,0 +1,22 @@ +#pragma once + +#include + +#include "envoy/http/codec.h" +#include "envoy/network/connection.h" + +namespace Envoy { +namespace Http { + +class QuicHttpConnectionFactory { +public: + virtual ~QuicHttpConnectionFactory() {} + + virtual std::string name() const PURE; + + virtual Connection* createQuicHttpConnection(Network::Connection& connection, + ConnectionCallbacks& callbacks) PURE; +}; + +} // namespace Http +} // namespace Envoy diff --git a/source/common/http/http3/well_known_names.h b/source/common/http/http3/well_known_names.h new file mode 100644 index 0000000000000..6bb0b88d4c951 --- /dev/null +++ b/source/common/http/http3/well_known_names.h @@ -0,0 +1,19 @@ +#pragma once + +#include + +#include "common/singleton/const_singleton.h" + +namespace Envoy { +namespace Http { + +class QuicCodecNameValues { +public: + const std::string Client = "client_codec"; + const std::string Server = "server_codec"; +}; + +using QuicCodecNames = ConstSingleton; + +} // namespace Http +} // namespace Envoy diff --git a/source/extensions/filters/network/http_connection_manager/config.cc b/source/extensions/filters/network/http_connection_manager/config.cc index 6959725374a49..2d66019198261 100644 --- a/source/extensions/filters/network/http_connection_manager/config.cc +++ b/source/extensions/filters/network/http_connection_manager/config.cc @@ -19,6 +19,8 @@ #include "common/http/default_server_string.h" #include "common/http/http1/codec_impl.h" #include "common/http/http2/codec_impl.h" +#include "common/http/http3/quic_codec_factory.h" +#include "common/http/http3/well_known_names.h" #include "common/http/utility.h" #include "common/json/config_schemas.h" #include "common/protobuf/utility.h" @@ -26,8 +28,6 @@ #include "common/router/scoped_rds.h" #include "common/runtime/runtime_impl.h" -#include "extensions/quic_listeners/quiche/codec_impl.h" - namespace Envoy { namespace Extensions { namespace NetworkFilters { @@ -428,10 +428,10 @@ HttpConnectionManagerConfig::createCodec(Network::Connection& connection, connection, callbacks, context_.scope(), http2_settings_, maxRequestHeadersKb(), maxRequestHeadersCount()); case CodecType::HTTP3: - // TODO(danzh) same as client side. This enforce dependency on QUICHE. Is there a - // better way to aoivd such dependency in case QUICHE breaks this extension. - return std::make_unique( - dynamic_cast(connection), callbacks); + return std::unique_ptr(dynamic_cast( + Config::Utility::getAndCheckFactory( + Http::QuicCodecNames::get().Server) + .createQuicHttpConnection(connection, callbacks))); case CodecType::AUTO: return Http::ConnectionManagerUtility::autoCreateCodec( connection, data, callbacks, context_.scope(), http1_settings_, http2_settings_, diff --git a/source/extensions/quic_listeners/quiche/BUILD b/source/extensions/quic_listeners/quiche/BUILD index 19dfafd14b740..93c4efb4ee8af 100644 --- a/source/extensions/quic_listeners/quiche/BUILD +++ b/source/extensions/quic_listeners/quiche/BUILD @@ -112,6 +112,9 @@ envoy_cc_library( ":envoy_quic_client_session_lib", ":envoy_quic_server_session_lib", "//include/envoy/http:codec_interface", + "//include/envoy/registry", + "//source/common/http/http3:quic_codec_factory_lib", + "//source/common/http/http3:well_known_names", "@com_googlesource_quiche//:quic_core_http_spdy_session_lib", ], ) diff --git a/source/extensions/quic_listeners/quiche/codec_impl.cc b/source/extensions/quic_listeners/quiche/codec_impl.cc index c6d418e845153..214be67d56b9b 100644 --- a/source/extensions/quic_listeners/quiche/codec_impl.cc +++ b/source/extensions/quic_listeners/quiche/codec_impl.cc @@ -64,5 +64,23 @@ void QuicHttpClientConnectionImpl::onUnderlyingConnectionBelowWriteBufferLowWate runWatermarkCallbacksForEachStream(quic_client_session_.stream_map(), false); } +Http::Connection* +QuicHttpClientConnectionFactory::createQuicHttpConnection(Network::Connection& connection, + Http::ConnectionCallbacks& callbacks) { + return new Quic::QuicHttpClientConnectionImpl( + dynamic_cast(connection), callbacks); +} + +Http::Connection* +QuicHttpServerConnectionFactory::createQuicHttpConnection(Network::Connection& connection, + Http::ConnectionCallbacks& callbacks) { + return new Quic::QuicHttpServerConnectionImpl( + dynamic_cast(connection), + dynamic_cast(callbacks)); +} + +REGISTER_FACTORY(QuicHttpClientConnectionFactory, Http::QuicHttpConnectionFactory); +REGISTER_FACTORY(QuicHttpServerConnectionFactory, Http::QuicHttpConnectionFactory); + } // namespace Quic } // namespace Envoy diff --git a/source/extensions/quic_listeners/quiche/codec_impl.h b/source/extensions/quic_listeners/quiche/codec_impl.h index 6dcc12322ad1f..38617d7e4ff77 100644 --- a/source/extensions/quic_listeners/quiche/codec_impl.h +++ b/source/extensions/quic_listeners/quiche/codec_impl.h @@ -1,7 +1,10 @@ #include "envoy/http/codec.h" +#include "envoy/registry/registry.h" #include "common/common/assert.h" #include "common/common/logger.h" +#include "common/http/http3/quic_codec_factory.h" +#include "common/http/http3/well_known_names.h" #include "extensions/quic_listeners/quiche/envoy_quic_client_session.h" #include "extensions/quic_listeners/quiche/envoy_quic_server_session.h" @@ -74,5 +77,24 @@ class QuicHttpClientConnectionImpl : public QuicHttpConnectionImplBase, EnvoyQuicClientSession& quic_client_session_; }; +class QuicHttpClientConnectionFactory : public Http::QuicHttpConnectionFactory { +public: + Http::Connection* createQuicHttpConnection(Network::Connection& connection, + Http::ConnectionCallbacks& callbacks) override; + + std::string name() const override { return "client_codec"; } +}; + +class QuicHttpServerConnectionFactory : public Http::QuicHttpConnectionFactory { +public: + Http::Connection* createQuicHttpConnection(Network::Connection& connection, + Http::ConnectionCallbacks& callbacks) override; + + std::string name() const override { return "server_codec"; } +}; + +DECLARE_FACTORY(QuicHttpClientConnectionFactory); +DECLARE_FACTORY(QuicHttpServerConnectionFactory); + } // namespace Quic } // namespace Envoy diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_client_session.cc b/source/extensions/quic_listeners/quiche/envoy_quic_client_session.cc index d218986736c6b..566627d4574b1 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_client_session.cc +++ b/source/extensions/quic_listeners/quiche/envoy_quic_client_session.cc @@ -11,8 +11,7 @@ EnvoyQuicClientSession::EnvoyQuicClientSession( uint32_t send_buffer_limit) : QuicFilterManagerConnectionImpl(*connection, dispatcher, send_buffer_limit), quic::QuicSpdyClientSession(config, supported_versions, connection.release(), server_id, - crypto_config, push_promise_index) { -} + crypto_config, push_promise_index) {} EnvoyQuicClientSession::~EnvoyQuicClientSession() { ASSERT(!connection()->connected()); diff --git a/test/extensions/quic_listeners/quiche/integration/BUILD b/test/extensions/quic_listeners/quiche/integration/BUILD index 4e2cde2d9bd8d..984bfd686fe75 100644 --- a/test/extensions/quic_listeners/quiche/integration/BUILD +++ b/test/extensions/quic_listeners/quiche/integration/BUILD @@ -19,6 +19,7 @@ envoy_cc_test( tags = ["nofips"], deps = [ "//source/extensions/quic_listeners/quiche:active_quic_listener_config_lib", + "//source/extensions/quic_listeners/quiche:codec_lib", "//source/extensions/quic_listeners/quiche:envoy_quic_client_connection_lib", "//source/extensions/quic_listeners/quiche:envoy_quic_client_session_lib", "//source/extensions/quic_listeners/quiche:envoy_quic_connection_helper_lib", From 497cf1e998cf3c7810bde663ca9464abc64aa921 Mon Sep 17 00:00:00 2001 From: Dan Zhang Date: Fri, 25 Oct 2019 12:45:01 -0400 Subject: [PATCH 38/76] fix clang format Signed-off-by: Dan Zhang --- .../quic_listeners/quiche/envoy_quic_client_stream_test.cc | 2 +- .../quic_listeners/quiche/envoy_quic_server_stream_test.cc | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/extensions/quic_listeners/quiche/envoy_quic_client_stream_test.cc b/test/extensions/quic_listeners/quiche/envoy_quic_client_stream_test.cc index e8a821751c9d0..846117fec4fe4 100644 --- a/test/extensions/quic_listeners/quiche/envoy_quic_client_stream_test.cc +++ b/test/extensions/quic_listeners/quiche/envoy_quic_client_stream_test.cc @@ -264,7 +264,7 @@ TEST_P(EnvoyQuicClientStreamTest, OutOfOrderTrailers) { TEST_P(EnvoyQuicClientStreamTest, WatermarkSendBuffer) { request_headers_.addCopy(":content-length", "32770"); // 32KB + 2 byte quic_stream_->encodeHeaders(request_headers_, /*end_stream=*/false); - // encode 32kB request body. first 16KB shoudl be written out right away. The + // Encode 32kB request body. first 16KB should be written out right away. The // rest should be buffered. The high watermark is 16KB, so this call should // make the send buffer reach its high watermark. std::string request(32 * 1024 + 1, 'a'); diff --git a/test/extensions/quic_listeners/quiche/envoy_quic_server_stream_test.cc b/test/extensions/quic_listeners/quiche/envoy_quic_server_stream_test.cc index 0cf860ec53e9f..f7ed0651ff8f8 100644 --- a/test/extensions/quic_listeners/quiche/envoy_quic_server_stream_test.cc +++ b/test/extensions/quic_listeners/quiche/envoy_quic_server_stream_test.cc @@ -283,7 +283,7 @@ TEST_P(EnvoyQuicServerStreamTest, WatermarkSendBuffer) { response_headers_.addCopy(":content-length", "32770"); // 32KB + 2 byte quic_stream_->encodeHeaders(response_headers_, /*end_stream=*/false); - // encode 32kB response body. first 16KB shoudl be written out right away. The + // Encode 32kB response body. first 16KB should be written out right away. The // rest should be buffered. The high watermark is 16KB, so this call should // make the send buffer reach its high watermark. std::string response(32 * 1024 + 1, 'a'); From 00fceea623c1642a3b2506217094bbe758f95146 Mon Sep 17 00:00:00 2001 From: Dan Zhang Date: Fri, 25 Oct 2019 16:07:17 -0400 Subject: [PATCH 39/76] fix client stream test Signed-off-by: Dan Zhang --- test/extensions/quic_listeners/quiche/BUILD | 2 ++ .../quiche/envoy_quic_client_stream_test.cc | 19 ++++++++++--------- .../quiche/envoy_quic_server_stream_test.cc | 17 +++++++++-------- 3 files changed, 21 insertions(+), 17 deletions(-) diff --git a/test/extensions/quic_listeners/quiche/BUILD b/test/extensions/quic_listeners/quiche/BUILD index 1e88b1f176549..6dc7a72fdbdcf 100644 --- a/test/extensions/quic_listeners/quiche/BUILD +++ b/test/extensions/quic_listeners/quiche/BUILD @@ -63,6 +63,7 @@ envoy_cc_test( "//source/extensions/quic_listeners/quiche:envoy_quic_alarm_factory_lib", "//source/extensions/quic_listeners/quiche:envoy_quic_connection_helper_lib", "//source/extensions/quic_listeners/quiche:envoy_quic_server_connection_lib", + "//source/extensions/quic_listeners/quiche:envoy_quic_server_session_lib", "//test/mocks/http:http_mocks", "//test/mocks/http:stream_decoder_mock", "//test/mocks/network:network_mocks", @@ -81,6 +82,7 @@ envoy_cc_test( "//source/common/http:headers_lib", "//source/extensions/quic_listeners/quiche:envoy_quic_alarm_factory_lib", "//source/extensions/quic_listeners/quiche:envoy_quic_client_connection_lib", + "//source/extensions/quic_listeners/quiche:envoy_quic_client_session_lib", "//source/extensions/quic_listeners/quiche:envoy_quic_connection_helper_lib", "//test/mocks/http:http_mocks", "//test/mocks/http:stream_decoder_mock", diff --git a/test/extensions/quic_listeners/quiche/envoy_quic_client_stream_test.cc b/test/extensions/quic_listeners/quiche/envoy_quic_client_stream_test.cc index 846117fec4fe4..8cb57ce1af239 100644 --- a/test/extensions/quic_listeners/quiche/envoy_quic_client_stream_test.cc +++ b/test/extensions/quic_listeners/quiche/envoy_quic_client_stream_test.cc @@ -88,15 +88,15 @@ class EnvoyQuicClientStreamTest : public testing::TestWithParam { quic_config_.GetInitialStreamFlowControlWindowToSend() * 2), stream_id_(quic_version_.transport_version == quic::QUIC_VERSION_99 ? 4u : 5u), quic_stream_(new EnvoyQuicClientStream(stream_id_, &quic_session_, quic::BIDIRECTIONAL)), - request_headers_{{":authority", host_}, {":method", "GET"}, {":path", "/"}} { + request_headers_{{":authority", host_}, {":method", "POST"}, {":path", "/"}} { quic::SetVerbosityLogThreshold(3); quic_stream_->setDecoder(stream_decoder_); quic_stream_->addCallbacks(stream_callbacks_); quic_session_.ActivateStream(std::unique_ptr(quic_stream_)); EXPECT_CALL(quic_session_, WritevData(_, _, _, _, _)) .WillRepeatedly(Invoke([](quic::QuicStream*, quic::QuicStreamId, size_t write_length, - quic::QuicStreamOffset, quic::StreamSendingState) { - return quic::QuicConsumedData{write_length, true}; + quic::QuicStreamOffset, quic::StreamSendingState state) { + return quic::QuicConsumedData{write_length, state != quic::NO_FIN}; })); EXPECT_CALL(writer_, WritePacket(_, _, _, _, _)) .WillRepeatedly(Invoke([](const char*, size_t buf_len, const quic::QuicIpAddress&, @@ -262,6 +262,13 @@ TEST_P(EnvoyQuicClientStreamTest, OutOfOrderTrailers) { } TEST_P(EnvoyQuicClientStreamTest, WatermarkSendBuffer) { + // Bump connection flow control window large enough not to cause connection + // level flow control blocked. + quic::QuicWindowUpdateFrame window_update( + quic::kInvalidControlFrameId, + quic::QuicUtils::GetInvalidStreamId(quic_version_.transport_version), 1024 * 1024); + quic_session_.OnWindowUpdateFrame(window_update); + request_headers_.addCopy(":content-length", "32770"); // 32KB + 2 byte quic_stream_->encodeHeaders(request_headers_, /*end_stream=*/false); // Encode 32kB request body. first 16KB should be written out right away. The @@ -274,12 +281,6 @@ TEST_P(EnvoyQuicClientStreamTest, WatermarkSendBuffer) { EXPECT_EQ(0u, buffer.length()); EXPECT_TRUE(quic_stream_->flow_controller()->IsBlocked()); - // Bump connection flow control window large enough not to cause connection - // level flow control blocked. - quic::QuicWindowUpdateFrame window_update( - quic::kInvalidControlFrameId, - quic::QuicUtils::GetInvalidStreamId(quic_version_.transport_version), 1024 * 1024); - quic_session_.OnWindowUpdateFrame(window_update); // Receive a WINDOW_UPDATE frame not large enough to drain half of the send // buffer. diff --git a/test/extensions/quic_listeners/quiche/envoy_quic_server_stream_test.cc b/test/extensions/quic_listeners/quiche/envoy_quic_server_stream_test.cc index f7ed0651ff8f8..fc68a426db888 100644 --- a/test/extensions/quic_listeners/quiche/envoy_quic_server_stream_test.cc +++ b/test/extensions/quic_listeners/quiche/envoy_quic_server_stream_test.cc @@ -52,8 +52,8 @@ class EnvoyQuicServerStreamTest : public testing::TestWithParam { quic_session_.ActivateStream(std::unique_ptr(quic_stream_)); EXPECT_CALL(quic_session_, WritevData(_, _, _, _, _)) .WillRepeatedly(Invoke([](quic::QuicStream*, quic::QuicStreamId, size_t write_length, - quic::QuicStreamOffset, quic::StreamSendingState) { - return quic::QuicConsumedData{write_length, true}; + quic::QuicStreamOffset, quic::StreamSendingState state) { + return quic::QuicConsumedData{write_length, state != quic::NO_FIN}; })); EXPECT_CALL(writer_, WritePacket(_, _, _, _, _)) .WillRepeatedly(Invoke([](const char*, size_t buf_len, const quic::QuicIpAddress&, @@ -281,6 +281,13 @@ TEST_P(EnvoyQuicServerStreamTest, WatermarkSendBuffer) { quic::QuicStreamFrame frame(stream_id_, true, 0, data); quic_stream_->OnStreamFrame(frame); + // Bump connection flow control window large enough not to cause connection + // level flow control blocked. + quic::QuicWindowUpdateFrame window_update( + quic::kInvalidControlFrameId, + quic::QuicUtils::GetInvalidStreamId(quic_version_.transport_version), 1024 * 1024); + quic_session_.OnWindowUpdateFrame(window_update); + response_headers_.addCopy(":content-length", "32770"); // 32KB + 2 byte quic_stream_->encodeHeaders(response_headers_, /*end_stream=*/false); // Encode 32kB response body. first 16KB should be written out right away. The @@ -293,12 +300,6 @@ TEST_P(EnvoyQuicServerStreamTest, WatermarkSendBuffer) { EXPECT_EQ(0u, buffer.length()); EXPECT_TRUE(quic_stream_->flow_controller()->IsBlocked()); - // Bump connection flow control window large enough not to cause connection - // level flow control blocked. - quic::QuicWindowUpdateFrame window_update( - quic::kInvalidControlFrameId, - quic::QuicUtils::GetInvalidStreamId(quic_version_.transport_version), 1024 * 1024); - quic_session_.OnWindowUpdateFrame(window_update); // Receive a WINDOW_UPDATE frame not large enough to drain half of the send // buffer. From 9b722103cd0fb10a8ab9a2ad1b0ee8fa73621c70 Mon Sep 17 00:00:00 2001 From: Dan Zhang Date: Mon, 28 Oct 2019 13:27:17 -0400 Subject: [PATCH 40/76] remove AtMost EXPECT_CALL Signed-off-by: Dan Zhang --- .../quiche/envoy_quic_server_session_test.cc | 9 --------- 1 file changed, 9 deletions(-) diff --git a/test/extensions/quic_listeners/quiche/envoy_quic_server_session_test.cc b/test/extensions/quic_listeners/quiche/envoy_quic_server_session_test.cc index b4d2f4f3ae3bc..290e44cb43e8b 100644 --- a/test/extensions/quic_listeners/quiche/envoy_quic_server_session_test.cc +++ b/test/extensions/quic_listeners/quiche/envoy_quic_server_session_test.cc @@ -209,9 +209,6 @@ TEST_P(EnvoyQuicServerSessionTest, NewStream) { EXPECT_EQ(Http::Headers::get().MethodValues.Get, decoded_headers->Method()->value().getStringView()); })); - EXPECT_CALL(request_decoder, decodeData(_, true)) - .Times(testing::AtMost(1)) - .WillOnce(Invoke([](Buffer::Instance& buffer, bool) { EXPECT_EQ(0, buffer.length()); })); stream->OnStreamHeaderList(/*fin=*/true, headers.uncompressed_header_bytes(), headers); } @@ -479,9 +476,6 @@ TEST_P(EnvoyQuicServerSessionTest, SendBufferWatermark) { EXPECT_EQ(Http::Headers::get().MethodValues.Get, decoded_headers->Method()->value().getStringView()); })); - EXPECT_CALL(request_decoder, decodeData(_, true)) - .Times(testing::AtMost(1)) - .WillOnce(Invoke([](Buffer::Instance& buffer, bool) { EXPECT_EQ(0, buffer.length()); })); stream1->OnStreamHeaderList(/*fin=*/true, request_headers.uncompressed_header_bytes(), request_headers); @@ -547,9 +541,6 @@ TEST_P(EnvoyQuicServerSessionTest, SendBufferWatermark) { EXPECT_EQ(Http::Headers::get().MethodValues.Get, decoded_headers->Method()->value().getStringView()); })); - EXPECT_CALL(request_decoder3, decodeData(_, true)) - .Times(testing::AtMost(1)) - .WillOnce(Invoke([](Buffer::Instance& buffer, bool) { EXPECT_EQ(0, buffer.length()); })); stream3->OnStreamHeaderList(/*fin=*/true, request_headers.uncompressed_header_bytes(), request_headers); From 6c034888c5154396e0334e06464c5c732002e93f Mon Sep 17 00:00:00 2001 From: Dan Zhang Date: Mon, 28 Oct 2019 14:50:25 -0400 Subject: [PATCH 41/76] comment in watermark buffer Signed-off-by: Dan Zhang --- .../quiche/envoy_quic_simulated_watermark_buffer.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_simulated_watermark_buffer.h b/source/extensions/quic_listeners/quiche/envoy_quic_simulated_watermark_buffer.h index eca6706800a3d..1faa89d82d07c 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_simulated_watermark_buffer.h +++ b/source/extensions/quic_listeners/quiche/envoy_quic_simulated_watermark_buffer.h @@ -52,9 +52,9 @@ class EnvoyQuicSimulatedWatermarkBuffer { // watermarks. bool isAboveHighWatermark() const { return is_full_; } - // True after the buffer goes below low watermark and hasn't come up above high - // watermark yet, even though the buffered data might be between high and low - // watermarks. + // True before the buffer crosses the high watermark for the first time and after the buffer goes + // below low watermark and hasn't come up above high watermark yet, even though the buffered data + // might be between high and low watermarks. bool isBelowLowWatermark() const { return !is_full_; } private: From 86c8ec1bd8a66474850c2c977f0a4b03d6a4edf4 Mon Sep 17 00:00:00 2001 From: Dan Zhang Date: Tue, 29 Oct 2019 19:46:34 -0400 Subject: [PATCH 42/76] comment about buffer limit Signed-off-by: Dan Zhang --- {docs => source/docs}/quiche_integration.md | 0 .../quic_listeners/quiche/envoy_quic_dispatcher.cc | 5 ++++- .../quic_listeners/quiche/envoy_quic_server_stream.cc | 9 +++++++++ .../extensions/quic_listeners/quiche/envoy_quic_stream.h | 2 +- 4 files changed, 14 insertions(+), 2 deletions(-) rename {docs => source/docs}/quiche_integration.md (100%) diff --git a/docs/quiche_integration.md b/source/docs/quiche_integration.md similarity index 100% rename from docs/quiche_integration.md rename to source/docs/quiche_integration.md diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_dispatcher.cc b/source/extensions/quic_listeners/quiche/envoy_quic_dispatcher.cc index 7d05fc250c399..673008a8250d8 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_dispatcher.cc +++ b/source/extensions/quic_listeners/quiche/envoy_quic_dispatcher.cc @@ -31,7 +31,10 @@ EnvoyQuicDispatcher::EnvoyQuicDispatcher( // flow control window of upstream. The per-stream high watermark should be // smaller than max flow control window to make sure upper stream can be flow // control blocked early enough not to send more than the threshold allows. - SetQuicFlag(FLAGS_quic_buffered_data_threshold, 2 * 16 * 1024 * 1024); // 32MB + // TODO(danzh) Ideally we should use the negotiated value from upstream which is not accessible + // for now. 512MB is way to large, but the actual bytes buffered should be bound by the negotiated + // upstream flow control window. + SetQuicFlag(FLAGS_quic_buffered_data_threshold, 2 * DEFAULT_INITIAL_STREAM_WINDOW_SIZE); // 512MB } void EnvoyQuicDispatcher::OnConnectionClosed(quic::QuicConnectionId connection_id, diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_server_stream.cc b/source/extensions/quic_listeners/quiche/envoy_quic_server_stream.cc index ec50a92cd8ef8..73187d2d5ddf4 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_server_stream.cc +++ b/source/extensions/quic_listeners/quiche/envoy_quic_server_stream.cc @@ -56,6 +56,15 @@ void EnvoyQuicServerStream::encode100ContinueHeaders(const Http::HeaderMap& head void EnvoyQuicServerStream::encodeHeaders(const Http::HeaderMap& headers, bool end_stream) { ENVOY_STREAM_LOG(debug, "encodeHeaders (end_stream={}) {}.", *this, end_stream, headers); + // QUICHE guarantees to take all the headers. This could cause inifit data to + // be bufferred on headers stream in Google QUIC implementation because + // headers stream doesn't have upper bound for its send buffer. But in IETF + // QUIC implementation this is safe as headers are sent on data stream which + // is bounded by max concurrent streams limited. + // Same vulnerability exists in crypto stream which can inifinitly buffer data + // if handshake implementation goes wrong. + // TODO(danzh) Modify QUICHE to have an upper bound for header stream send + // buffer. WriteHeaders(envoyHeadersToSpdyHeaderBlock(headers), end_stream, nullptr); local_end_stream_ = end_stream; } diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_stream.h b/source/extensions/quic_listeners/quiche/envoy_quic_stream.h index 3262373c4b5bb..228d0cf645926 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_stream.h +++ b/source/extensions/quic_listeners/quiche/envoy_quic_stream.h @@ -93,7 +93,7 @@ class EnvoyQuicStream : public Http::StreamEncoder, // notified more than once about end of stream. So once this is true, no need // to set it in the callback to Envoy stream any more. bool end_stream_decoded_{false}; - int32_t read_disable_counter_{0}; + uint32_t read_disable_counter_{0u}; // If true, switchStreamBlockState() should be deferred till this variable // becomes false. bool in_decode_data_callstack_{false}; From a23de7f7738cd03a5d10d8aa27172063ab0cb380 Mon Sep 17 00:00:00 2001 From: Dan Zhang Date: Wed, 30 Oct 2019 11:40:28 -0400 Subject: [PATCH 43/76] fix namespace Signed-off-by: Dan Zhang --- .../extensions/quic_listeners/quiche/envoy_quic_dispatcher.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_dispatcher.cc b/source/extensions/quic_listeners/quiche/envoy_quic_dispatcher.cc index 673008a8250d8..01358d453f43a 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_dispatcher.cc +++ b/source/extensions/quic_listeners/quiche/envoy_quic_dispatcher.cc @@ -34,7 +34,7 @@ EnvoyQuicDispatcher::EnvoyQuicDispatcher( // TODO(danzh) Ideally we should use the negotiated value from upstream which is not accessible // for now. 512MB is way to large, but the actual bytes buffered should be bound by the negotiated // upstream flow control window. - SetQuicFlag(FLAGS_quic_buffered_data_threshold, 2 * DEFAULT_INITIAL_STREAM_WINDOW_SIZE); // 512MB + SetQuicFlag(FLAGS_quic_buffered_data_threshold, 2 * Http::Http2Settings::DEFAULT_INITIAL_STREAM_WINDOW_SIZE); // 512MB } void EnvoyQuicDispatcher::OnConnectionClosed(quic::QuicConnectionId connection_id, From 6f33c267f619cb1a7a20d32df0d9e3ca5e2ef79c Mon Sep 17 00:00:00 2001 From: Dan Zhang Date: Wed, 30 Oct 2019 12:50:18 -0400 Subject: [PATCH 44/76] format Signed-off-by: Dan Zhang --- .../extensions/quic_listeners/quiche/envoy_quic_dispatcher.cc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_dispatcher.cc b/source/extensions/quic_listeners/quiche/envoy_quic_dispatcher.cc index 01358d453f43a..de4b5b740d384 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_dispatcher.cc +++ b/source/extensions/quic_listeners/quiche/envoy_quic_dispatcher.cc @@ -34,7 +34,8 @@ EnvoyQuicDispatcher::EnvoyQuicDispatcher( // TODO(danzh) Ideally we should use the negotiated value from upstream which is not accessible // for now. 512MB is way to large, but the actual bytes buffered should be bound by the negotiated // upstream flow control window. - SetQuicFlag(FLAGS_quic_buffered_data_threshold, 2 * Http::Http2Settings::DEFAULT_INITIAL_STREAM_WINDOW_SIZE); // 512MB + SetQuicFlag(FLAGS_quic_buffered_data_threshold, + 2 * Http::Http2Settings::DEFAULT_INITIAL_STREAM_WINDOW_SIZE); // 512MB } void EnvoyQuicDispatcher::OnConnectionClosed(quic::QuicConnectionId connection_id, From e1e9fa439c884ed1826552537c81b059b95e0027 Mon Sep 17 00:00:00 2001 From: Dan Zhang Date: Thu, 31 Oct 2019 12:36:15 -0400 Subject: [PATCH 45/76] add tracking issue Signed-off-by: Dan Zhang --- .../quic_listeners/quiche/envoy_quic_dispatcher.cc | 2 +- .../quic_listeners/quiche/envoy_quic_server_stream.cc | 9 ++++----- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_dispatcher.cc b/source/extensions/quic_listeners/quiche/envoy_quic_dispatcher.cc index de4b5b740d384..eab481cbe8c50 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_dispatcher.cc +++ b/source/extensions/quic_listeners/quiche/envoy_quic_dispatcher.cc @@ -31,7 +31,7 @@ EnvoyQuicDispatcher::EnvoyQuicDispatcher( // flow control window of upstream. The per-stream high watermark should be // smaller than max flow control window to make sure upper stream can be flow // control blocked early enough not to send more than the threshold allows. - // TODO(danzh) Ideally we should use the negotiated value from upstream which is not accessible + // TODO(#8826) Ideally we should use the negotiated value from upstream which is not accessible // for now. 512MB is way to large, but the actual bytes buffered should be bound by the negotiated // upstream flow control window. SetQuicFlag(FLAGS_quic_buffered_data_threshold, diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_server_stream.cc b/source/extensions/quic_listeners/quiche/envoy_quic_server_stream.cc index 73187d2d5ddf4..ff585282c7c83 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_server_stream.cc +++ b/source/extensions/quic_listeners/quiche/envoy_quic_server_stream.cc @@ -56,15 +56,14 @@ void EnvoyQuicServerStream::encode100ContinueHeaders(const Http::HeaderMap& head void EnvoyQuicServerStream::encodeHeaders(const Http::HeaderMap& headers, bool end_stream) { ENVOY_STREAM_LOG(debug, "encodeHeaders (end_stream={}) {}.", *this, end_stream, headers); - // QUICHE guarantees to take all the headers. This could cause inifit data to - // be bufferred on headers stream in Google QUIC implementation because + // QUICHE guarantees to take all the headers. This could cause infinite data to + // be buffered on headers stream in Google QUIC implementation because // headers stream doesn't have upper bound for its send buffer. But in IETF // QUIC implementation this is safe as headers are sent on data stream which // is bounded by max concurrent streams limited. - // Same vulnerability exists in crypto stream which can inifinitly buffer data + // Same vulnerability exists in crypto stream which can infinitely buffer data // if handshake implementation goes wrong. - // TODO(danzh) Modify QUICHE to have an upper bound for header stream send - // buffer. + // TODO(#8826) Modify QUICHE to have an upper bound for header stream send buffer. WriteHeaders(envoyHeadersToSpdyHeaderBlock(headers), end_stream, nullptr); local_end_stream_ = end_stream; } From 2d7f6acf34d5f81f0f8a1cb6802c767f43d76652 Mon Sep 17 00:00:00 2001 From: Dan Zhang Date: Mon, 4 Nov 2019 12:32:12 -0500 Subject: [PATCH 46/76] comments Signed-off-by: Dan Zhang --- source/extensions/quic_listeners/quiche/codec_impl.cc | 2 ++ .../quic_listeners/quiche/envoy_quic_server_stream.cc | 3 ++- .../quiche/envoy_quic_simulated_watermark_buffer.h | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/source/extensions/quic_listeners/quiche/codec_impl.cc b/source/extensions/quic_listeners/quiche/codec_impl.cc index 6c9ae799b51f3..cd082427b0ffe 100644 --- a/source/extensions/quic_listeners/quiche/codec_impl.cc +++ b/source/extensions/quic_listeners/quiche/codec_impl.cc @@ -16,6 +16,8 @@ QuicHttpServerConnectionImpl::QuicHttpServerConnectionImpl( void QuicHttpServerConnectionImpl::onUnderlyingConnectionAboveWriteBufferHighWatermark() { for (auto& it : quic_server_session_.stream_map()) { if (!it.second->is_static()) { + // Only call watermark callbacks on non QUIC static streams which are + // crypto stream and Google QUIC headers stream. ENVOY_LOG(debug, "runHighWatermarkCallbacks on stream {}", it.first); dynamic_cast(it.second.get())->runHighWatermarkCallbacks(); } diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_server_stream.cc b/source/extensions/quic_listeners/quiche/envoy_quic_server_stream.cc index ff585282c7c83..b4ac61223b178 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_server_stream.cc +++ b/source/extensions/quic_listeners/quiche/envoy_quic_server_stream.cc @@ -104,7 +104,8 @@ void EnvoyQuicServerStream::resetStream(Http::StreamResetReason reason) { runResetCallbacks(reason); if (local_end_stream_ && !reading_stopped()) { // This is after 200 early response. Reset with QUIC_STREAM_NO_ERROR instead - // of propagating original reset reason. + // of propagating original reset reason. In QUICHE if a stream stops reading + // before FIN or RESET received, it resets the steam with QUIC_STREAM_NO_ERROR. StopReading(); } else { Reset(envoyResetReasonToQuicRstError(reason)); diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_simulated_watermark_buffer.h b/source/extensions/quic_listeners/quiche/envoy_quic_simulated_watermark_buffer.h index 1faa89d82d07c..ef795cd57aaa4 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_simulated_watermark_buffer.h +++ b/source/extensions/quic_listeners/quiche/envoy_quic_simulated_watermark_buffer.h @@ -11,7 +11,7 @@ namespace Quic { // A class, together with a stand alone buffer, used to achieve the purpose of WatermarkBuffer. // Itself doesn't have buffer or bookkeep buffered bytes. But provided with buffered_bytes, -// it re-acts upon crossing high/low watermarks. +// it reacts upon crossing high/low watermarks. // It's no-op if provided low and high watermark are 0. class EnvoyQuicSimulatedWatermarkBuffer { public: From c3b2133924c663c8b62a8d4d7bab14ca47f655f9 Mon Sep 17 00:00:00 2001 From: Dan Zhang Date: Wed, 6 Nov 2019 14:26:53 -0500 Subject: [PATCH 47/76] fix readDisable test Signed-off-by: Dan Zhang --- .../quic_listeners/quiche/codec_impl.cc | 8 ++--- .../quiche/envoy_quic_client_session.cc | 3 +- .../quiche/envoy_quic_client_stream.cc | 14 ++++----- .../quiche/envoy_quic_client_stream.h | 4 +-- .../quic_listeners/quiche/envoy_quic_stream.h | 30 ++++++++++++++++++- test/config/utility.h | 2 +- .../integration/quic_http_integration_test.cc | 4 +-- 7 files changed, 46 insertions(+), 19 deletions(-) diff --git a/source/extensions/quic_listeners/quiche/codec_impl.cc b/source/extensions/quic_listeners/quiche/codec_impl.cc index a6e139027bee2..c1e82032b42b1 100644 --- a/source/extensions/quic_listeners/quiche/codec_impl.cc +++ b/source/extensions/quic_listeners/quiche/codec_impl.cc @@ -15,12 +15,12 @@ void QuicHttpConnectionImplBase::runWatermarkCallbacksForEachStream( if (!it.second->is_static()) { auto stream = dynamic_cast(it.second.get()); if (high_watermark) { - // Only call watermark callbacks on non QUIC static streams which are - // crypto stream and Google QUIC headers stream. - ENVOY_LOG(debug, "runHighWatermarkCallbacks on stream {}", it.first); + // Only call watermark callbacks on non QUIC static streams which are + // crypto stream and Google QUIC headers stream. + ENVOY_LOG(debug, "runHighWatermarkCallbacks on stream {}", it.first); stream->runHighWatermarkCallbacks(); } else { - ENVOY_LOG(debug, "runLowWatermarkCallbacks on stream {}", it.first); + ENVOY_LOG(debug, "runLowWatermarkCallbacks on stream {}", it.first); stream->runLowWatermarkCallbacks(); } } diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_client_session.cc b/source/extensions/quic_listeners/quiche/envoy_quic_client_session.cc index 4c852b27f995b..c021ea71e4525 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_client_session.cc +++ b/source/extensions/quic_listeners/quiche/envoy_quic_client_session.cc @@ -71,7 +71,8 @@ quic::QuicSpdyStream* EnvoyQuicClientSession::CreateIncomingStream(quic::QuicStr NOT_REACHED_GCOVR_EXCL_LINE; } -quic::QuicSpdyStream* EnvoyQuicClientSession::CreateIncomingStream(quic::PendingStream* /*pending*/) { +quic::QuicSpdyStream* +EnvoyQuicClientSession::CreateIncomingStream(quic::PendingStream* /*pending*/) { // Disallow server initiated stream. NOT_REACHED_GCOVR_EXCL_LINE; } diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_client_stream.cc b/source/extensions/quic_listeners/quiche/envoy_quic_client_stream.cc index 1ea6ffbb2e82a..d47b94279eac2 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_client_stream.cc +++ b/source/extensions/quic_listeners/quiche/envoy_quic_client_stream.cc @@ -33,16 +33,16 @@ EnvoyQuicClientStream::EnvoyQuicClientStream(quic::QuicStreamId id, // the stream to buffer all the data. // Ideally this limit should also correlate to peer's receive window // but not fully depends on that. - 16 * 1024, - [this]() { runLowWatermarkCallbacks(); }, [this]() { runHighWatermarkCallbacks(); }) {} + 16 * 1024, [this]() { runLowWatermarkCallbacks(); }, + [this]() { runHighWatermarkCallbacks(); }) {} EnvoyQuicClientStream::EnvoyQuicClientStream(quic::PendingStream* pending, quic::QuicSpdyClientSession* client_session, quic::StreamType type) : quic::QuicSpdyClientStream(pending, client_session, type), EnvoyQuicStream( - 16 * 1024, - [this]() { runLowWatermarkCallbacks(); }, [this]() { runHighWatermarkCallbacks(); }) {} + 16 * 1024, [this]() { runLowWatermarkCallbacks(); }, + [this]() { runHighWatermarkCallbacks(); }) {} void EnvoyQuicClientStream::encode100ContinueHeaders(const Http::HeaderMap& headers) { ASSERT(headers.Status()->value() == "100"); @@ -82,7 +82,7 @@ void EnvoyQuicClientStream::encodeTrailers(const Http::HeaderMap& trailers) { } void EnvoyQuicClientStream::encodeMetadata(const Http::MetadataMapVector& /*metadata_map_vector*/) { - // Metadata Frame is not supported in QUIC. + // Metadata Frame is not supported in QUIC. NOT_IMPLEMENTED_GCOVR_EXCL_LINE; } @@ -225,9 +225,7 @@ void EnvoyQuicClientStream::OnCanWrite() { uint32_t EnvoyQuicClientStream::streamId() { return id(); } -Network::Connection* EnvoyQuicClientStream::connection() { - return filterManagerConnection(); -} +Network::Connection* EnvoyQuicClientStream::connection() { return filterManagerConnection(); } QuicFilterManagerConnectionImpl* EnvoyQuicClientStream::filterManagerConnection() { return dynamic_cast(session()); diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_client_stream.h b/source/extensions/quic_listeners/quiche/envoy_quic_client_stream.h index 8e2232084ac01..6f99a80980413 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_client_stream.h +++ b/source/extensions/quic_listeners/quiche/envoy_quic_client_stream.h @@ -51,8 +51,8 @@ class EnvoyQuicClientStream : public quic::QuicSpdyClientStream, public EnvoyQui const quic::QuicHeaderList& header_list) override; void OnTrailingHeadersComplete(bool fin, size_t frame_len, const quic::QuicHeaderList& header_list) override; - - private: + +private: QuicFilterManagerConnectionImpl* filterManagerConnection(); }; diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_stream.h b/source/extensions/quic_listeners/quiche/envoy_quic_stream.h index 228d0cf645926..59a7341b59341 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_stream.h +++ b/source/extensions/quic_listeners/quiche/envoy_quic_stream.h @@ -47,7 +47,26 @@ class EnvoyQuicStream : public Http::StreamEncoder, // Avoid calling this while decoding data because transient disabling and // enabling reading may trigger another decoding data inside the // callstack which messes up stream state. - switchStreamBlockState(disable); + if (disable) { + // Block QUIC stream right away. And if there are queued switching + // state callback, update the desired state as well. + switchStreamBlockState(true); + if (unblock_posted_) { + should_block_ = true; + } + } else { + should_block_ = false; + if (!unblock_posted_) { + // If this is the first time unblocking stream is desired, post a + // callback to do it in next loop. This is because unblocking QUIC + // stream can lead to immediate upstream encoding. + unblock_posted_ = true; + connection()->dispatcher().post([this] { + unblock_posted_ = false; + switchStreamBlockState(should_block_); + }); + } + } } } @@ -109,6 +128,15 @@ class EnvoyQuicStream : public Http::StreamEncoder, // OnBodyDataAvailable() hands all the ready-to-use request data from stream sequencer to HCM // directly and buffers them in filters if needed. Itself doesn't buffer request data. EnvoyQuicSimulatedWatermarkBuffer send_buffer_simulation_; + + // True if there is posted unblocking QUIC stream callback. There should be + // only one such callback no matter how many times readDisable() is called. + bool unblock_posted_{false}; + // The latest state an unblocking QUIC stream callback should look at. As + // more readDisable() calls may happen between the callback is posted and it's + // executed, the stream might be unblocked and blocked several times. Only the + // latest desired state should be considered by the callback. + bool should_block_{false}; }; } // namespace Quic diff --git a/test/config/utility.h b/test/config/utility.h index 96da6d6ed44c7..0f2207cc19402 100644 --- a/test/config/utility.h +++ b/test/config/utility.h @@ -78,7 +78,7 @@ class ConfigHelper { static const std::string TCP_PROXY_CONFIG; // A basic configuration for L7 proxying. static const std::string HTTP_PROXY_CONFIG; - // A basic configuration for L7 proxying with QUIC transport. + // A basic configuration for L7 proxying with QUIC transport. static const std::string QUIC_HTTP_PROXY_CONFIG; // A string for a basic buffer filter, which can be used with addFilter() static const std::string DEFAULT_BUFFER_FILTER; diff --git a/test/extensions/quic_listeners/quiche/integration/quic_http_integration_test.cc b/test/extensions/quic_listeners/quiche/integration/quic_http_integration_test.cc index a5ca054fea5c0..02f477186d986 100644 --- a/test/extensions/quic_listeners/quiche/integration/quic_http_integration_test.cc +++ b/test/extensions/quic_listeners/quiche/integration/quic_http_integration_test.cc @@ -172,12 +172,12 @@ TEST_P(QuicHttpIntegrationTest, RouterUpstreamResponseBeforeRequestComplete) { TEST_P(QuicHttpIntegrationTest, Retry) { testRetry(); } -TEST_P(QuicHttpIntegrationTest, UpstreamThrottlingOnGiantResponseBody) { +TEST_P(QuicHttpIntegrationTest, UpstreamReadDisabledOnGiantResponseBody) { config_helper_.setBufferLimits(/*upstream_buffer_limit=*/1024, /*downstream_buffer_limit=*/1024); testRouterRequestAndResponseWithBody(/*request_size=*/512, /*response_size=*/1024 * 1024, false); } -TEST_P(QuicHttpIntegrationTest, DownstreamThrottlingOnGiantPost) { +TEST_P(QuicHttpIntegrationTest, DownstreamReadDisabledOnGiantPost) { config_helper_.setBufferLimits(/*upstream_buffer_limit=*/1024, /*downstream_buffer_limit=*/1024); testRouterRequestAndResponseWithBody(/*request_size=*/1024 * 1024, /*response_size=*/1024, false); } From c37b29d83a9fb4736ffb8f6b667206c23c70c0e6 Mon Sep 17 00:00:00 2001 From: Dan Zhang Date: Wed, 6 Nov 2019 14:34:06 -0500 Subject: [PATCH 48/76] remove ConnectionPtr Signed-off-by: Dan Zhang --- include/envoy/http/codec.h | 2 -- 1 file changed, 2 deletions(-) diff --git a/include/envoy/http/codec.h b/include/envoy/http/codec.h index 34241c73b0bfa..4c456d2e296e5 100644 --- a/include/envoy/http/codec.h +++ b/include/envoy/http/codec.h @@ -363,8 +363,6 @@ class Connection { virtual void onUnderlyingConnectionBelowWriteBufferLowWatermark() PURE; }; -using ConnectionPtr = std::unique_ptr; - /** * Callbacks for downstream connection watermark limits. */ From 552c3bdd1aa23dfe1995422eac1c66ca2db44a62 Mon Sep 17 00:00:00 2001 From: Dan Zhang Date: Wed, 6 Nov 2019 15:30:54 -0500 Subject: [PATCH 49/76] code clean up Signed-off-by: Dan Zhang --- source/common/http/http3/quic_codec_factory.h | 2 ++ source/extensions/quic_listeners/quiche/codec_impl.h | 6 ++++-- .../quic_listeners/quiche/envoy_quic_client_connection.h | 2 +- test/extensions/quic_listeners/quiche/integration/BUILD | 4 ---- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/source/common/http/http3/quic_codec_factory.h b/source/common/http/http3/quic_codec_factory.h index cb4aff6627ebf..8b70eb23b39bf 100644 --- a/source/common/http/http3/quic_codec_factory.h +++ b/source/common/http/http3/quic_codec_factory.h @@ -8,6 +8,8 @@ namespace Envoy { namespace Http { +// A factory to create Http::Connection instance for QUIC. It has two registered +// subclasses QuicHttpClientConnectionFactory and QuicHttpServerConnectionFactory. class QuicHttpConnectionFactory { public: virtual ~QuicHttpConnectionFactory() {} diff --git a/source/extensions/quic_listeners/quiche/codec_impl.h b/source/extensions/quic_listeners/quiche/codec_impl.h index ccf89a815de53..fa3067d6cd45a 100644 --- a/source/extensions/quic_listeners/quiche/codec_impl.h +++ b/source/extensions/quic_listeners/quiche/codec_impl.h @@ -76,20 +76,22 @@ class QuicHttpClientConnectionImpl : public QuicHttpConnectionImplBase, EnvoyQuicClientSession& quic_client_session_; }; +// A factory to create QuicHttpClientConnection. class QuicHttpClientConnectionFactory : public Http::QuicHttpConnectionFactory { public: Http::Connection* createQuicHttpConnection(Network::Connection& connection, Http::ConnectionCallbacks& callbacks) override; - std::string name() const override { return "client_codec"; } + std::string name() const override { return Http::QuicCodecNames::get().Client; } }; +// A factory to create QuicHttpServerConnection. class QuicHttpServerConnectionFactory : public Http::QuicHttpConnectionFactory { public: Http::Connection* createQuicHttpConnection(Network::Connection& connection, Http::ConnectionCallbacks& callbacks) override; - std::string name() const override { return "server_codec"; } + std::string name() const override { return Http::QuicCodecNames::get().Server; } }; DECLARE_FACTORY(QuicHttpClientConnectionFactory); diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_client_connection.h b/source/extensions/quic_listeners/quiche/envoy_quic_client_connection.h index 32e952fdb9df0..985a07c5c5d77 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_client_connection.h +++ b/source/extensions/quic_listeners/quiche/envoy_quic_client_connection.h @@ -9,7 +9,7 @@ namespace Envoy { namespace Quic { -// A client QuicConnection instance manages its own I/O events. +// A client QuicConnection instance managing its own file events. class EnvoyQuicClientConnection : public EnvoyQuicConnection, public Network::UdpPacketProcessor { public: // A connection socket will be created with given |local_addr|. If binding diff --git a/test/extensions/quic_listeners/quiche/integration/BUILD b/test/extensions/quic_listeners/quiche/integration/BUILD index 984bfd686fe75..2f2a48b28ab88 100644 --- a/test/extensions/quic_listeners/quiche/integration/BUILD +++ b/test/extensions/quic_listeners/quiche/integration/BUILD @@ -2,12 +2,8 @@ licenses(["notice"]) # Apache 2 load( "//bazel:envoy_build_system.bzl", - "envoy_cc_fuzz_test", "envoy_cc_test", - "envoy_cc_test_binary", - "envoy_cc_test_library", "envoy_package", - "envoy_proto_library", ) envoy_package() From 3a713db31cd1d2790f3939e49cfb9e10c36c95e3 Mon Sep 17 00:00:00 2001 From: Dan Zhang Date: Mon, 11 Nov 2019 12:07:21 -0500 Subject: [PATCH 50/76] fix typo Signed-off-by: Dan Zhang --- .../quiche/integration/quic_http_integration_test.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/extensions/quic_listeners/quiche/integration/quic_http_integration_test.cc b/test/extensions/quic_listeners/quiche/integration/quic_http_integration_test.cc index 02f477186d986..423e0d87ff4de 100644 --- a/test/extensions/quic_listeners/quiche/integration/quic_http_integration_test.cc +++ b/test/extensions/quic_listeners/quiche/integration/quic_http_integration_test.cc @@ -67,7 +67,7 @@ class QuicHttpIntegrationTest : public testing::TestWithParam Date: Mon, 11 Nov 2019 15:46:14 -0500 Subject: [PATCH 51/76] add category Signed-off-by: Dan Zhang --- source/common/http/http3/quic_codec_factory.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/source/common/http/http3/quic_codec_factory.h b/source/common/http/http3/quic_codec_factory.h index 8b70eb23b39bf..44cec3829041e 100644 --- a/source/common/http/http3/quic_codec_factory.h +++ b/source/common/http/http3/quic_codec_factory.h @@ -18,6 +18,8 @@ class QuicHttpConnectionFactory { virtual Connection* createQuicHttpConnection(Network::Connection& connection, ConnectionCallbacks& callbacks) PURE; + + static std::string category() { return "quiche_codec"; } }; } // namespace Http From 2be9d326d04e322badc266d29f37bb3dc630cf3d Mon Sep 17 00:00:00 2001 From: Dan Zhang Date: Tue, 12 Nov 2019 18:56:25 -0500 Subject: [PATCH 52/76] use readPacketsFromSocket Signed-off-by: Dan Zhang --- source/common/http/http3/quic_codec_factory.h | 2 +- .../quic_listeners/quiche/codec_impl.cc | 12 +++-- .../quiche/envoy_quic_client_connection.cc | 46 ++++--------------- 3 files changed, 20 insertions(+), 40 deletions(-) diff --git a/source/common/http/http3/quic_codec_factory.h b/source/common/http/http3/quic_codec_factory.h index 44cec3829041e..bb7649801e08f 100644 --- a/source/common/http/http3/quic_codec_factory.h +++ b/source/common/http/http3/quic_codec_factory.h @@ -19,7 +19,7 @@ class QuicHttpConnectionFactory { virtual Connection* createQuicHttpConnection(Network::Connection& connection, ConnectionCallbacks& callbacks) PURE; - static std::string category() { return "quiche_codec"; } + static std::string category() { return "quic_codec"; } }; } // namespace Http diff --git a/source/extensions/quic_listeners/quiche/codec_impl.cc b/source/extensions/quic_listeners/quiche/codec_impl.cc index c1e82032b42b1..3ca45f0692b43 100644 --- a/source/extensions/quic_listeners/quiche/codec_impl.cc +++ b/source/extensions/quic_listeners/quiche/codec_impl.cc @@ -6,6 +6,10 @@ namespace Envoy { namespace Quic { +EnvoyQuicStream* quicStreamToEnvoyStream(quic::QuicStream* stream) { + return dynamic_cast(stream); +} + bool QuicHttpConnectionImplBase::wantsToWrite() { return quic_session_.HasDataToWrite(); } void QuicHttpConnectionImplBase::runWatermarkCallbacksForEachStream( @@ -13,7 +17,7 @@ void QuicHttpConnectionImplBase::runWatermarkCallbacksForEachStream( bool high_watermark) { for (auto& it : stream_map) { if (!it.second->is_static()) { - auto stream = dynamic_cast(it.second.get()); + auto stream = quicStreamToEnvoyStream(it.second.get()); if (high_watermark) { // Only call watermark callbacks on non QUIC static streams which are // crypto stream and Google QUIC headers stream. @@ -53,8 +57,10 @@ QuicHttpClientConnectionImpl::QuicHttpClientConnectionImpl(EnvoyQuicClientSessio Http::StreamEncoder& QuicHttpClientConnectionImpl::newStream(Http::StreamDecoder& response_decoder) { - auto stream = dynamic_cast( - quic_client_session_.CreateOutgoingBidirectionalStream()); + auto stream = quicStreamToEnvoyStream(quic_client_session_.CreateOutgoingBidirectionalStream()); + // TODO(danzh) handle stream creation failure gracefully. This can happen when + // there are already 100 open streams. In such case, caller should hold back + // the stream creation till an existing stream is closed. ASSERT(stream != nullptr, "Fail to create QUIC stream."); stream->setDecoder(response_decoder); if (quic_client_session_.aboveHighWatermark()) { diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_client_connection.cc b/source/extensions/quic_listeners/quiche/envoy_quic_client_connection.cc index c8ab991a630a6..3c4c573efe4bf 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_client_connection.cc +++ b/source/extensions/quic_listeners/quiche/envoy_quic_client_connection.cc @@ -48,6 +48,9 @@ void EnvoyQuicClientConnection::processPacket( Network::Address::InstanceConstSharedPtr local_address, Network::Address::InstanceConstSharedPtr peer_address, Buffer::InstancePtr buffer, MonotonicTime receive_time) { + if (!connected()) { + return; + } quic::QuicTime timestamp = quic::QuicTime::Zero() + quic::QuicTime::Delta::FromMicroseconds( @@ -100,42 +103,13 @@ void EnvoyQuicClientConnection::onFileEvent(uint32_t events) { // It's possible for a write event callback to close the connection, in such case ignore read // event processing. if (connected() && (events & Event::FileReadyType::Read)) { - uint32_t old_packets_dropped = packets_dropped_; - while (connected()) { - // Read till socket is drained. - // TODO(danzh): limit read times here. - MonotonicTime receive_time = dispatcher_.timeSource().monotonicTime(); - Api::IoCallUint64Result result = Network::Utility::readFromSocket( - *connectionSocket(), *this, receive_time, &packets_dropped_); - if (!result.ok()) { - if (result.err_->getErrorCode() != Api::IoError::IoErrorCode::Again) { - ENVOY_CONN_LOG(error, "recvmsg result {}: {}", *this, - static_cast(result.err_->getErrorCode()), - result.err_->getErrorDetails()); - } - // Stop reading. - break; - } - - if (result.rc_ == 0) { - // TODO(conqerAtapple): Is zero length packet interesting? If so add stats - // for it. Otherwise remove the warning log below. - ENVOY_CONN_LOG(trace, "received 0-length packet", *this); - } - - if (packets_dropped_ != old_packets_dropped) { - // The kernel tracks SO_RXQ_OVFL as a uint32 which can overflow to a smaller - // value. So as long as this count differs from previously recorded value, - // more packets are dropped by kernel. - uint32_t delta = (packets_dropped_ > old_packets_dropped) - ? (packets_dropped_ - old_packets_dropped) - : (packets_dropped_ + - (std::numeric_limits::max() - old_packets_dropped) + 1); - // TODO(danzh) add stats for this. - ENVOY_CONN_LOG(debug, - "Kernel dropped {} more packets. Consider increase receive buffer size.", - *this, delta); - } + Api::IoErrorPtr err = Network::Utility::readPacketsFromSocket( + connectionSocket()->ioHandle(), *connectionSocket()->localAddress(), *this, + dispatcher_.timeSource(), packets_dropped_); + // TODO(danzh): Handle no error when we limit the number of packets read. + if (err->getErrorCode() != Api::IoError::IoErrorCode::Again) { + ENVOY_CONN_LOG(error, "recvmsg result {}: {}", *this, static_cast(err->getErrorCode()), + err->getErrorDetails()); } } } From adeb9b48872f3f37dea87bfec30ed949e0539fbf Mon Sep 17 00:00:00 2001 From: Dan Zhang Date: Wed, 13 Nov 2019 16:09:49 -0500 Subject: [PATCH 53/76] seperate codec factory Signed-off-by: Dan Zhang --- source/common/http/codec_client.cc | 4 ++-- source/common/http/http3/quic_codec_factory.h | 24 ++++++++++++++----- .../network/http_connection_manager/config.cc | 4 ++-- .../quic_listeners/quiche/codec_impl.cc | 17 ++++++------- .../quic_listeners/quiche/codec_impl.h | 16 ++++++------- 5 files changed, 39 insertions(+), 26 deletions(-) diff --git a/source/common/http/codec_client.cc b/source/common/http/codec_client.cc index 4f59210e4c773..9aa9097bf796e 100644 --- a/source/common/http/codec_client.cc +++ b/source/common/http/codec_client.cc @@ -162,9 +162,9 @@ CodecClientProd::CodecClientProd(Type type, Network::ClientConnectionPtr&& conne } case Type::HTTP3: { codec_ = std::unique_ptr(dynamic_cast( - Config::Utility::getAndCheckFactory( + Config::Utility::getAndCheckFactory( Http::QuicCodecNames::get().Client) - .createQuicHttpConnection(*connection_, *this))); + .createQuicClientConnection(*connection_, *this))); } } } diff --git a/source/common/http/http3/quic_codec_factory.h b/source/common/http/http3/quic_codec_factory.h index bb7649801e08f..590a725108f9f 100644 --- a/source/common/http/http3/quic_codec_factory.h +++ b/source/common/http/http3/quic_codec_factory.h @@ -8,16 +8,28 @@ namespace Envoy { namespace Http { -// A factory to create Http::Connection instance for QUIC. It has two registered -// subclasses QuicHttpClientConnectionFactory and QuicHttpServerConnectionFactory. -class QuicHttpConnectionFactory { +// A factory to create Http::ServerConnection instance for QUIC. +class QuicHttpServerConnectionFactory { public: - virtual ~QuicHttpConnectionFactory() {} + virtual ~QuicHttpServerConnectionFactory() {} virtual std::string name() const PURE; - virtual Connection* createQuicHttpConnection(Network::Connection& connection, - ConnectionCallbacks& callbacks) PURE; + virtual ServerConnection* createQuicServerConnection(Network::Connection& connection, + ConnectionCallbacks& callbacks) PURE; + + static std::string category() { return "quic_codec"; } +}; + +// A factory to create Http::ClientConnection instance for QUIC. +class QuicHttpClientConnectionFactory { +public: + virtual ~QuicHttpClientConnectionFactory() {} + + virtual std::string name() const PURE; + + virtual ClientConnection* createQuicClientConnection(Network::Connection& connection, + ConnectionCallbacks& callbacks) PURE; static std::string category() { return "quic_codec"; } }; diff --git a/source/extensions/filters/network/http_connection_manager/config.cc b/source/extensions/filters/network/http_connection_manager/config.cc index 9bdbaa43bedc8..ad1741774d821 100644 --- a/source/extensions/filters/network/http_connection_manager/config.cc +++ b/source/extensions/filters/network/http_connection_manager/config.cc @@ -420,9 +420,9 @@ HttpConnectionManagerConfig::createCodec(Network::Connection& connection, maxRequestHeadersCount()); case CodecType::HTTP3: return std::unique_ptr(dynamic_cast( - Config::Utility::getAndCheckFactory( + Config::Utility::getAndCheckFactory( Http::QuicCodecNames::get().Server) - .createQuicHttpConnection(connection, callbacks))); + .createQuicServerConnection(connection, callbacks))); case CodecType::AUTO: return Http::ConnectionManagerUtility::autoCreateCodec( connection, data, callbacks, context_.scope(), http1_settings_, http2_settings_, diff --git a/source/extensions/quic_listeners/quiche/codec_impl.cc b/source/extensions/quic_listeners/quiche/codec_impl.cc index 3ca45f0692b43..5c28691718c47 100644 --- a/source/extensions/quic_listeners/quiche/codec_impl.cc +++ b/source/extensions/quic_listeners/quiche/codec_impl.cc @@ -6,6 +6,9 @@ namespace Envoy { namespace Quic { +// Converts a QuicStream instance to EnvoyQuicStream instance. The current stream implementation +// inherits from these two interfaces, with the former one providing Quic interface and the latter +// providing Envoy interface. EnvoyQuicStream* quicStreamToEnvoyStream(quic::QuicStream* stream) { return dynamic_cast(stream); } @@ -77,23 +80,21 @@ void QuicHttpClientConnectionImpl::onUnderlyingConnectionBelowWriteBufferLowWate runWatermarkCallbacksForEachStream(quic_client_session_.stream_map(), false); } -Http::Connection* -QuicHttpClientConnectionFactory::createQuicHttpConnection(Network::Connection& connection, - Http::ConnectionCallbacks& callbacks) { +Http::ClientConnection* QuicHttpClientConnectionFactoryImpl::createQuicClientConnection( + Network::Connection& connection, Http::ConnectionCallbacks& callbacks) { return new Quic::QuicHttpClientConnectionImpl( dynamic_cast(connection), callbacks); } -Http::Connection* -QuicHttpServerConnectionFactory::createQuicHttpConnection(Network::Connection& connection, - Http::ConnectionCallbacks& callbacks) { +Http::ServerConnection* QuicHttpServerConnectionFactoryImpl::createQuicServerConnection( + Network::Connection& connection, Http::ConnectionCallbacks& callbacks) { return new Quic::QuicHttpServerConnectionImpl( dynamic_cast(connection), dynamic_cast(callbacks)); } -REGISTER_FACTORY(QuicHttpClientConnectionFactory, Http::QuicHttpConnectionFactory); -REGISTER_FACTORY(QuicHttpServerConnectionFactory, Http::QuicHttpConnectionFactory); +REGISTER_FACTORY(QuicHttpClientConnectionFactoryImpl, Http::QuicHttpClientConnectionFactory); +REGISTER_FACTORY(QuicHttpServerConnectionFactoryImpl, Http::QuicHttpServerConnectionFactory); } // namespace Quic } // namespace Envoy diff --git a/source/extensions/quic_listeners/quiche/codec_impl.h b/source/extensions/quic_listeners/quiche/codec_impl.h index fa3067d6cd45a..78694b96898b9 100644 --- a/source/extensions/quic_listeners/quiche/codec_impl.h +++ b/source/extensions/quic_listeners/quiche/codec_impl.h @@ -77,25 +77,25 @@ class QuicHttpClientConnectionImpl : public QuicHttpConnectionImplBase, }; // A factory to create QuicHttpClientConnection. -class QuicHttpClientConnectionFactory : public Http::QuicHttpConnectionFactory { +class QuicHttpClientConnectionFactoryImpl : public Http::QuicHttpClientConnectionFactory { public: - Http::Connection* createQuicHttpConnection(Network::Connection& connection, - Http::ConnectionCallbacks& callbacks) override; + Http::ClientConnection* createQuicClientConnection(Network::Connection& connection, + Http::ConnectionCallbacks& callbacks) override; std::string name() const override { return Http::QuicCodecNames::get().Client; } }; // A factory to create QuicHttpServerConnection. -class QuicHttpServerConnectionFactory : public Http::QuicHttpConnectionFactory { +class QuicHttpServerConnectionFactoryImpl : public Http::QuicHttpServerConnectionFactory { public: - Http::Connection* createQuicHttpConnection(Network::Connection& connection, - Http::ConnectionCallbacks& callbacks) override; + Http::ServerConnection* createQuicServerConnection(Network::Connection& connection, + Http::ConnectionCallbacks& callbacks) override; std::string name() const override { return Http::QuicCodecNames::get().Server; } }; -DECLARE_FACTORY(QuicHttpClientConnectionFactory); -DECLARE_FACTORY(QuicHttpServerConnectionFactory); +DECLARE_FACTORY(QuicHttpClientConnectionFactoryImpl); +DECLARE_FACTORY(QuicHttpServerConnectionFactoryImpl); } // namespace Quic } // namespace Envoy From afb85f5b84be2ec4c3900e5a21b6818ee05546bd Mon Sep 17 00:00:00 2001 From: Dan Zhang Date: Wed, 13 Nov 2019 17:44:10 -0500 Subject: [PATCH 54/76] add alarm test Signed-off-by: Dan Zhang --- .../extensions/quic_listeners/quiche/codec_impl.cc | 4 ++-- .../quic_listeners/quiche/envoy_quic_alarm.h | 7 ++----- .../quic_listeners/quiche/envoy_quic_alarm_test.cc | 12 ++++++++++++ 3 files changed, 16 insertions(+), 7 deletions(-) diff --git a/source/extensions/quic_listeners/quiche/codec_impl.cc b/source/extensions/quic_listeners/quiche/codec_impl.cc index 5c28691718c47..1bd460a646d6c 100644 --- a/source/extensions/quic_listeners/quiche/codec_impl.cc +++ b/source/extensions/quic_listeners/quiche/codec_impl.cc @@ -20,10 +20,10 @@ void QuicHttpConnectionImplBase::runWatermarkCallbacksForEachStream( bool high_watermark) { for (auto& it : stream_map) { if (!it.second->is_static()) { + // Only call watermark callbacks on non QUIC static streams which are + // crypto stream and Google QUIC headers stream. auto stream = quicStreamToEnvoyStream(it.second.get()); if (high_watermark) { - // Only call watermark callbacks on non QUIC static streams which are - // crypto stream and Google QUIC headers stream. ENVOY_LOG(debug, "runHighWatermarkCallbacks on stream {}", it.first); stream->runHighWatermarkCallbacks(); } else { diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_alarm.h b/source/extensions/quic_listeners/quiche/envoy_quic_alarm.h index 22010987c4def..a3bb0ee8a1623 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_alarm.h +++ b/source/extensions/quic_listeners/quiche/envoy_quic_alarm.h @@ -20,11 +20,8 @@ class EnvoyQuicAlarm : public quic::QuicAlarm { EnvoyQuicAlarm(Event::Dispatcher& dispatcher, const quic::QuicClock& clock, quic::QuicArenaScopedPtr delegate); - ~EnvoyQuicAlarm() override { - if (IsSet()) { - Cancel(); - } - }; + // TimerImpl destruction deletes in-flight alarm firing event. + ~EnvoyQuicAlarm() override{}; // quic::QuicAlarm void CancelImpl() override; diff --git a/test/extensions/quic_listeners/quiche/envoy_quic_alarm_test.cc b/test/extensions/quic_listeners/quiche/envoy_quic_alarm_test.cc index 9ab3753ec8552..80578bd22f130 100644 --- a/test/extensions/quic_listeners/quiche/envoy_quic_alarm_test.cc +++ b/test/extensions/quic_listeners/quiche/envoy_quic_alarm_test.cc @@ -189,5 +189,17 @@ TEST_F(EnvoyQuicAlarmTest, CancelActiveAlarm) { EXPECT_FALSE(unowned_delegate->fired()); } +TEST_F(EnvoyQuicAlarmTest, CancelUponDestruction) { + auto unowned_delegate = new TestDelegate(); + quic::QuicAlarm* alarm = alarm_factory_.CreateAlarm(unowned_delegate); + // alarm becomes active upon Set(). + alarm->Set(clock_.Now() + QuicTime::Delta::FromMilliseconds(10)); + // delegate should be destroyed with alarm. + delete alarm; + // alarm firing callback should have been cancelled, otherwise the delegate + // would be used after free. + advanceMsAndLoop(10); +} + } // namespace Quic } // namespace Envoy From b77973bebcda158ff6e33766a7ffd565c5a7f8f5 Mon Sep 17 00:00:00 2001 From: Dan Zhang Date: Thu, 14 Nov 2019 16:57:31 -0500 Subject: [PATCH 55/76] move blocking event loop to IntegrationCodecClient Signed-off-by: Dan Zhang --- .../quiche/envoy_quic_client_session.cc | 11 ++++------- .../quiche/envoy_quic_client_session.h | 8 +------- .../integration/quic_http_integration_test.cc | 2 -- test/integration/http_integration.cc | 14 ++++++++------ 4 files changed, 13 insertions(+), 22 deletions(-) diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_client_session.cc b/source/extensions/quic_listeners/quiche/envoy_quic_client_session.cc index c021ea71e4525..98ed298d81e12 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_client_session.cc +++ b/source/extensions/quic_listeners/quiche/envoy_quic_client_session.cc @@ -24,6 +24,10 @@ absl::string_view EnvoyQuicClientSession::requestedServerName() const { void EnvoyQuicClientSession::connect() { dynamic_cast(quic_connection_)->setUpConnectionSocket(); + // Start version negotiation and crypto handshake during which the connection may fail if server + // doesn't support the one and only supported version. + CryptoConnect(); + set_max_allowed_push_id(0u); } void EnvoyQuicClientSession::OnConnectionClosed(const quic::QuicConnectionCloseFrame& frame, @@ -53,13 +57,6 @@ void EnvoyQuicClientSession::OnCryptoHandshakeEvent(CryptoHandshakeEvent event) } } -void EnvoyQuicClientSession::cryptoConnect() { - CryptoConnect(); - set_max_allowed_push_id(0u); - // Wait for finishing handshake with server. - dispatcher_.run(Event::Dispatcher::RunType::Block); -} - std::unique_ptr EnvoyQuicClientSession::CreateClientStream() { auto stream = std::make_unique(GetNextOutgoingBidirectionalStreamId(), this, quic::BIDIRECTIONAL); diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_client_session.h b/source/extensions/quic_listeners/quiche/envoy_quic_client_session.h index e5dd9862173db..7fc9db45d5068 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_client_session.h +++ b/source/extensions/quic_listeners/quiche/envoy_quic_client_session.h @@ -46,7 +46,7 @@ class EnvoyQuicClientSession : public QuicFilterManagerConnectionImpl, absl::string_view requestedServerName() const override; // Network::ClientConnection - // Only register socket and set socket options. + // Setup socket and start handshake. void connect() override; // quic::QuicSession @@ -57,12 +57,6 @@ class EnvoyQuicClientSession : public QuicFilterManagerConnectionImpl, // quic::QuicSpdyClientSessionBase void OnCryptoHandshakeEvent(CryptoHandshakeEvent event) override; - // Do version negotiation and crypto handshake. Fail the connection if server - // doesn't support the one and only supported version. - // This call will block till the handshake finished with either success to - // failure. - void cryptoConnect(); - using quic::QuicSpdyClientSession::stream_map; protected: diff --git a/test/extensions/quic_listeners/quiche/integration/quic_http_integration_test.cc b/test/extensions/quic_listeners/quiche/integration/quic_http_integration_test.cc index 423e0d87ff4de..2c84e2b6e1219 100644 --- a/test/extensions/quic_listeners/quiche/integration/quic_http_integration_test.cc +++ b/test/extensions/quic_listeners/quiche/integration/quic_http_integration_test.cc @@ -72,8 +72,6 @@ class QuicHttpIntegrationTest : public testing::TestWithParamconnected()); - dynamic_cast(codec->connection())->cryptoConnect(); if (codec->disconnected()) { // Connection may get closed during version negotiation or handshake. ENVOY_LOG(error, "Fail to connect to server with error: {}", diff --git a/test/integration/http_integration.cc b/test/integration/http_integration.cc index 16953d18a84b9..f242756e19a48 100644 --- a/test/integration/http_integration.cc +++ b/test/integration/http_integration.cc @@ -63,12 +63,7 @@ IntegrationCodecClient::IntegrationCodecClient( callbacks_(*this), codec_callbacks_(*this) { connection_->addConnectionCallbacks(callbacks_); setCodecConnectionCallbacks(codec_callbacks_); - if (type != CodecClient::Type::HTTP3) { - // Only expect to have IO event if it's not QUIC. Because call to connect() in CodecClientProd - // for QUIC doesn't send anything to server, but just register file event. QUIC connection won't - // have any IO event till cryptoConnect() is called later. - dispatcher.run(Event::Dispatcher::RunType::Block); - } + dispatcher.run(Event::Dispatcher::RunType::Block); } void IntegrationCodecClient::flushWrite() { @@ -188,6 +183,13 @@ void IntegrationCodecClient::ConnectionCallbacks::onEvent(Network::ConnectionEve parent_.disconnected_ = true; parent_.connection_->dispatcher().exit(); } else { + if (parent_.type() == CodecClient::Type::HTTP3 && !parent_.connected_) { + // A QUIC connection may fail of INVALID_VERSION if both this client + // doesn't support any of the versions the server advertised before + // handshake established. In this case the connection is closed locally + // and this is in a blocking event loop. + parent_.connection_->dispatcher().exit(); + } parent_.disconnected_ = true; } } From e193ebbf6af270d12a6c55f96f0e25bfb094b7fd Mon Sep 17 00:00:00 2001 From: Dan Zhang Date: Thu, 14 Nov 2019 18:53:13 -0500 Subject: [PATCH 56/76] metadata no-op Signed-off-by: Dan Zhang --- .../quiche/active_quic_listener.cc | 2 +- .../quic_listeners/quiche/codec_impl.cc | 3 +- .../quiche/envoy_quic_client_connection.cc | 8 ++- .../quiche/envoy_quic_client_stream.cc | 2 +- .../quiche/envoy_quic_server_stream.cc | 2 +- .../quiche/envoy_quic_client_session_test.cc | 1 - .../quiche/envoy_quic_client_stream_test.cc | 49 +------------------ .../quic_listeners/quiche/test_utils.h | 38 ++++++++++++++ 8 files changed, 50 insertions(+), 55 deletions(-) diff --git a/source/extensions/quic_listeners/quiche/active_quic_listener.cc b/source/extensions/quic_listeners/quiche/active_quic_listener.cc index febb9c1d014ca..0ee5258ab14ac 100644 --- a/source/extensions/quic_listeners/quiche/active_quic_listener.cc +++ b/source/extensions/quic_listeners/quiche/active_quic_listener.cc @@ -77,7 +77,7 @@ void ActiveQuicListener::onData(Network::UdpRecvData& data) { data.buffer_->getRawSlices(&slice, 1); // TODO(danzh): pass in TTL and UDP header. quic::QuicReceivedPacket packet(reinterpret_cast(slice.mem_), slice.len_, timestamp, - /*owns_buffer=*/false, /*ttl=*/0, /*ttl_valid=*/true, + /*owns_buffer=*/false, /*ttl=*/0, /*ttl_valid=*/false, /*packet_headers=*/nullptr, /*headers_length=*/0, /*owns_header_buffer*/ false); quic_dispatcher_->ProcessPacket(self_address, peer_address, packet); diff --git a/source/extensions/quic_listeners/quiche/codec_impl.cc b/source/extensions/quic_listeners/quiche/codec_impl.cc index 1bd460a646d6c..7af9a9639810f 100644 --- a/source/extensions/quic_listeners/quiche/codec_impl.cc +++ b/source/extensions/quic_listeners/quiche/codec_impl.cc @@ -60,7 +60,8 @@ QuicHttpClientConnectionImpl::QuicHttpClientConnectionImpl(EnvoyQuicClientSessio Http::StreamEncoder& QuicHttpClientConnectionImpl::newStream(Http::StreamDecoder& response_decoder) { - auto stream = quicStreamToEnvoyStream(quic_client_session_.CreateOutgoingBidirectionalStream()); + EnvoyQuicStream* stream = + quicStreamToEnvoyStream(quic_client_session_.CreateOutgoingBidirectionalStream()); // TODO(danzh) handle stream creation failure gracefully. This can happen when // there are already 100 open streams. In such case, caller should hold back // the stream creation till an existing stream is closed. diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_client_connection.cc b/source/extensions/quic_listeners/quiche/envoy_quic_client_connection.cc index 3c4c573efe4bf..a00cb73cce062 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_client_connection.cc +++ b/source/extensions/quic_listeners/quiche/envoy_quic_client_connection.cc @@ -42,7 +42,11 @@ EnvoyQuicClientConnection::EnvoyQuicClientConnection( std::move(connection_socket)), dispatcher_(dispatcher) {} -EnvoyQuicClientConnection::~EnvoyQuicClientConnection() { file_event_->setEnabled(0); } +EnvoyQuicClientConnection::~EnvoyQuicClientConnection() { + if (file_event_ != nullptr) { + file_event_->setEnabled(0); + } +} void EnvoyQuicClientConnection::processPacket( Network::Address::InstanceConstSharedPtr local_address, @@ -61,7 +65,7 @@ void EnvoyQuicClientConnection::processPacket( Buffer::RawSlice slice; buffer->getRawSlices(&slice, 1); quic::QuicReceivedPacket packet(reinterpret_cast(slice.mem_), slice.len_, timestamp, - /*owns_buffer=*/false, /*ttl=*/0, /*ttl_valid=*/true, + /*owns_buffer=*/false, /*ttl=*/0, /*ttl_valid=*/false, /*packet_headers=*/nullptr, /*headers_length=*/0, /*owns_header_buffer*/ false); ProcessUdpPacket(envoyAddressInstanceToQuicSocketAddress(local_address), diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_client_stream.cc b/source/extensions/quic_listeners/quiche/envoy_quic_client_stream.cc index d47b94279eac2..397f8529f3a83 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_client_stream.cc +++ b/source/extensions/quic_listeners/quiche/envoy_quic_client_stream.cc @@ -83,7 +83,7 @@ void EnvoyQuicClientStream::encodeTrailers(const Http::HeaderMap& trailers) { void EnvoyQuicClientStream::encodeMetadata(const Http::MetadataMapVector& /*metadata_map_vector*/) { // Metadata Frame is not supported in QUIC. - NOT_IMPLEMENTED_GCOVR_EXCL_LINE; + // TODO(danzh): add stats for metadata not supported error. } void EnvoyQuicClientStream::resetStream(Http::StreamResetReason reason) { diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_server_stream.cc b/source/extensions/quic_listeners/quiche/envoy_quic_server_stream.cc index b4ac61223b178..361fe73ac861f 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_server_stream.cc +++ b/source/extensions/quic_listeners/quiche/envoy_quic_server_stream.cc @@ -96,7 +96,7 @@ void EnvoyQuicServerStream::encodeTrailers(const Http::HeaderMap& trailers) { void EnvoyQuicServerStream::encodeMetadata(const Http::MetadataMapVector& /*metadata_map_vector*/) { // Metadata Frame is not supported in QUIC. - NOT_IMPLEMENTED_GCOVR_EXCL_LINE; + // TODO(danzh): add stats for metadata not supported error. } void EnvoyQuicServerStream::resetStream(Http::StreamResetReason reason) { diff --git a/test/extensions/quic_listeners/quiche/envoy_quic_client_session_test.cc b/test/extensions/quic_listeners/quiche/envoy_quic_client_session_test.cc index 1db825459108a..359beddee247b 100644 --- a/test/extensions/quic_listeners/quiche/envoy_quic_client_session_test.cc +++ b/test/extensions/quic_listeners/quiche/envoy_quic_client_session_test.cc @@ -128,7 +128,6 @@ class EnvoyQuicClientSessionTest : public testing::TestWithParam { envoy_quic_session_.setConnectionStats( {read_total_, read_current_, write_total_, write_current_, nullptr, nullptr}); EXPECT_EQ(&read_total_, &quic_connection_->connectionStats().read_total_); - envoy_quic_session_.connect(); } void TearDown() override { diff --git a/test/extensions/quic_listeners/quiche/envoy_quic_client_stream_test.cc b/test/extensions/quic_listeners/quiche/envoy_quic_client_stream_test.cc index 8cb57ce1af239..71ec4ddac48fb 100644 --- a/test/extensions/quic_listeners/quiche/envoy_quic_client_stream_test.cc +++ b/test/extensions/quic_listeners/quiche/envoy_quic_client_stream_test.cc @@ -1,18 +1,7 @@ -#pragma GCC diagnostic push -// QUICHE allows unused parameters. -#pragma GCC diagnostic ignored "-Wunused-parameter" -// QUICHE uses offsetof(). -#pragma GCC diagnostic ignored "-Winvalid-offsetof" - -#include "quiche/quic/core/http/quic_spdy_client_session.h" -#include "quiche/quic/test_tools/crypto_test_utils.h" - -#pragma GCC diagnostic pop - #include "extensions/quic_listeners/quiche/envoy_quic_alarm_factory.h" -#include "extensions/quic_listeners/quiche/envoy_quic_connection_helper.h" #include "extensions/quic_listeners/quiche/envoy_quic_client_connection.h" #include "extensions/quic_listeners/quiche/envoy_quic_client_stream.h" +#include "extensions/quic_listeners/quiche/envoy_quic_connection_helper.h" #include "extensions/quic_listeners/quiche/envoy_quic_utils.h" #include "test/extensions/quic_listeners/quiche/test_utils.h" @@ -31,42 +20,6 @@ using testing::_; using testing::Invoke; using testing::Return; -class MockEnvoyQuicClientSession : public quic::QuicSpdyClientSession, - public QuicFilterManagerConnectionImpl { -public: - MockEnvoyQuicClientSession(const quic::QuicConfig& config, - const quic::ParsedQuicVersionVector& supported_versions, - EnvoyQuicConnection* connection, Event::Dispatcher& dispatcher, - uint32_t send_buffer_limit) - : quic::QuicSpdyClientSession(config, supported_versions, connection, - quic::QuicServerId("example.com", 443, false), &crypto_config_, - nullptr), - QuicFilterManagerConnectionImpl(*connection, dispatcher, send_buffer_limit), - crypto_config_(quic::test::crypto_test_utils::ProofVerifierForTesting()) {} - - // From QuicSession. - MOCK_METHOD1(CreateIncomingStream, quic::QuicSpdyClientStream*(quic::QuicStreamId id)); - MOCK_METHOD1(CreateIncomingStream, quic::QuicSpdyClientStream*(quic::PendingStream* pending)); - MOCK_METHOD0(CreateOutgoingBidirectionalStream, quic::QuicSpdyClientStream*()); - MOCK_METHOD0(CreateOutgoingUnidirectionalStream, quic::QuicSpdyClientStream*()); - MOCK_METHOD1(ShouldCreateIncomingStream, bool(quic::QuicStreamId id)); - MOCK_METHOD0(ShouldCreateOutgoingBidirectionalStream, bool()); - MOCK_METHOD0(ShouldCreateOutgoingUnidirectionalStream, bool()); - MOCK_METHOD5(WritevData, - quic::QuicConsumedData(quic::QuicStream* stream, quic::QuicStreamId id, - size_t write_length, quic::QuicStreamOffset offset, - quic::StreamSendingState state)); - - absl::string_view requestedServerName() const override { - return {GetCryptoStream()->crypto_negotiated_params().sni}; - } - - using quic::QuicSpdySession::ActivateStream; - -private: - quic::QuicCryptoClientConfig crypto_config_; -}; - class EnvoyQuicClientStreamTest : public testing::TestWithParam { public: EnvoyQuicClientStreamTest() diff --git a/test/extensions/quic_listeners/quiche/test_utils.h b/test/extensions/quic_listeners/quiche/test_utils.h index 1b8489f4a6c3b..690191eda8c13 100644 --- a/test/extensions/quic_listeners/quiche/test_utils.h +++ b/test/extensions/quic_listeners/quiche/test_utils.h @@ -7,8 +7,10 @@ #pragma GCC diagnostic ignored "-Winvalid-offsetof" #include "quiche/quic/core/http/quic_spdy_session.h" +#include "quiche/quic/core/http/quic_spdy_client_session.h" #include "quiche/quic/test_tools/quic_test_utils.h" #include "quiche/quic/core/quic_utils.h" +#include "quiche/quic/test_tools/crypto_test_utils.h" #pragma GCC diagnostic pop @@ -53,5 +55,41 @@ class MockEnvoyQuicSession : public quic::QuicSpdySession, public QuicFilterMana std::unique_ptr crypto_stream_; }; +class MockEnvoyQuicClientSession : public quic::QuicSpdyClientSession, + public QuicFilterManagerConnectionImpl { +public: + MockEnvoyQuicClientSession(const quic::QuicConfig& config, + const quic::ParsedQuicVersionVector& supported_versions, + EnvoyQuicConnection* connection, Event::Dispatcher& dispatcher, + uint32_t send_buffer_limit) + : quic::QuicSpdyClientSession(config, supported_versions, connection, + quic::QuicServerId("example.com", 443, false), &crypto_config_, + nullptr), + QuicFilterManagerConnectionImpl(*connection, dispatcher, send_buffer_limit), + crypto_config_(quic::test::crypto_test_utils::ProofVerifierForTesting()) {} + + // From QuicSession. + MOCK_METHOD1(CreateIncomingStream, quic::QuicSpdyClientStream*(quic::QuicStreamId id)); + MOCK_METHOD1(CreateIncomingStream, quic::QuicSpdyClientStream*(quic::PendingStream* pending)); + MOCK_METHOD0(CreateOutgoingBidirectionalStream, quic::QuicSpdyClientStream*()); + MOCK_METHOD0(CreateOutgoingUnidirectionalStream, quic::QuicSpdyClientStream*()); + MOCK_METHOD1(ShouldCreateIncomingStream, bool(quic::QuicStreamId id)); + MOCK_METHOD0(ShouldCreateOutgoingBidirectionalStream, bool()); + MOCK_METHOD0(ShouldCreateOutgoingUnidirectionalStream, bool()); + MOCK_METHOD5(WritevData, + quic::QuicConsumedData(quic::QuicStream* stream, quic::QuicStreamId id, + size_t write_length, quic::QuicStreamOffset offset, + quic::StreamSendingState state)); + + absl::string_view requestedServerName() const override { + return {GetCryptoStream()->crypto_negotiated_params().sni}; + } + + using quic::QuicSpdySession::ActivateStream; + +private: + quic::QuicCryptoClientConfig crypto_config_; +}; + } // namespace Quic } // namespace Envoy From 8033b1e6cdd5e5c17a74a963366da1d7f246b02d Mon Sep 17 00:00:00 2001 From: Dan Zhang Date: Fri, 15 Nov 2019 13:01:19 -0500 Subject: [PATCH 57/76] remove dynamic_cast Signed-off-by: Dan Zhang --- source/common/http/codec_client.cc | 4 ++-- .../filters/network/http_connection_manager/config.cc | 4 ++-- test/integration/http_integration.cc | 10 +++++----- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/source/common/http/codec_client.cc b/source/common/http/codec_client.cc index 9aa9097bf796e..936af0c209210 100644 --- a/source/common/http/codec_client.cc +++ b/source/common/http/codec_client.cc @@ -161,10 +161,10 @@ CodecClientProd::CodecClientProd(Type type, Network::ClientConnectionPtr&& conne break; } case Type::HTTP3: { - codec_ = std::unique_ptr(dynamic_cast( + codec_ = std::unique_ptr( Config::Utility::getAndCheckFactory( Http::QuicCodecNames::get().Client) - .createQuicClientConnection(*connection_, *this))); + .createQuicClientConnection(*connection_, *this)); } } } diff --git a/source/extensions/filters/network/http_connection_manager/config.cc b/source/extensions/filters/network/http_connection_manager/config.cc index ad1741774d821..e02a432b9a368 100644 --- a/source/extensions/filters/network/http_connection_manager/config.cc +++ b/source/extensions/filters/network/http_connection_manager/config.cc @@ -419,10 +419,10 @@ HttpConnectionManagerConfig::createCodec(Network::Connection& connection, connection, callbacks, context_.scope(), http2_settings_, maxRequestHeadersKb(), maxRequestHeadersCount()); case CodecType::HTTP3: - return std::unique_ptr(dynamic_cast( + return std::unique_ptr( Config::Utility::getAndCheckFactory( Http::QuicCodecNames::get().Server) - .createQuicServerConnection(connection, callbacks))); + .createQuicServerConnection(connection, callbacks)); case CodecType::AUTO: return Http::ConnectionManagerUtility::autoCreateCodec( connection, data, callbacks, context_.scope(), http1_settings_, http2_settings_, diff --git a/test/integration/http_integration.cc b/test/integration/http_integration.cc index f242756e19a48..95764e2721e58 100644 --- a/test/integration/http_integration.cc +++ b/test/integration/http_integration.cc @@ -184,10 +184,10 @@ void IntegrationCodecClient::ConnectionCallbacks::onEvent(Network::ConnectionEve parent_.connection_->dispatcher().exit(); } else { if (parent_.type() == CodecClient::Type::HTTP3 && !parent_.connected_) { - // A QUIC connection may fail of INVALID_VERSION if both this client - // doesn't support any of the versions the server advertised before - // handshake established. In this case the connection is closed locally - // and this is in a blocking event loop. + // Before handshake establision, any connection failure should exit the loop. I.e. a QUIC + // connection may fail of INVALID_VERSION if both this client doesn't support any of the + // versions the server advertised before handshake established. In this case the connection is + // closed locally and this is in a blocking event loop. parent_.connection_->dispatcher().exit(); } parent_.disconnected_ = true; @@ -213,7 +213,7 @@ HttpIntegrationTest::makeRawHttpConnection(Network::ClientConnectionPtr&& conn) IntegrationCodecClientPtr HttpIntegrationTest::makeHttpConnection(Network::ClientConnectionPtr&& conn) { auto codec = makeRawHttpConnection(std::move(conn)); - EXPECT_TRUE(codec->connected()); + EXPECT_TRUE(codec->connected()) << codec->connection()->transportFailureReason(); return codec; } From 34bdc2d5e6e24cd7267a51706c15bacfece434e6 Mon Sep 17 00:00:00 2001 From: Dan Zhang Date: Fri, 15 Nov 2019 13:42:13 -0500 Subject: [PATCH 58/76] typo Signed-off-by: Dan Zhang --- test/integration/http_integration.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/integration/http_integration.cc b/test/integration/http_integration.cc index 95764e2721e58..97974a44fce7a 100644 --- a/test/integration/http_integration.cc +++ b/test/integration/http_integration.cc @@ -184,7 +184,7 @@ void IntegrationCodecClient::ConnectionCallbacks::onEvent(Network::ConnectionEve parent_.connection_->dispatcher().exit(); } else { if (parent_.type() == CodecClient::Type::HTTP3 && !parent_.connected_) { - // Before handshake establision, any connection failure should exit the loop. I.e. a QUIC + // Before handshake gets established, any connection failure should exit the loop. I.e. a QUIC // connection may fail of INVALID_VERSION if both this client doesn't support any of the // versions the server advertised before handshake established. In this case the connection is // closed locally and this is in a blocking event loop. From 391704ca224dcbe19bb2b8513ba9ae42002346b0 Mon Sep 17 00:00:00 2001 From: Dan Zhang Date: Wed, 20 Nov 2019 13:24:56 -0500 Subject: [PATCH 59/76] udp listener test Signed-off-by: Dan Zhang --- test/common/network/udp_listener_impl_test.cc | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/test/common/network/udp_listener_impl_test.cc b/test/common/network/udp_listener_impl_test.cc index 062f0ebc933b8..67caf089d7cbd 100644 --- a/test/common/network/udp_listener_impl_test.cc +++ b/test/common/network/udp_listener_impl_test.cc @@ -395,6 +395,7 @@ TEST_P(UdpListenerImplTest, SendData) { server_socket_->localAddress()->ip()->port())); } + std::cerr << "========== send_from_addr " << send_from_addr->asString() << "\n"; UdpSendData send_data{send_from_addr->ip(), *client_socket_->localAddress(), *buffer}; auto send_result = listener_->send(send_data); @@ -411,12 +412,12 @@ TEST_P(UdpListenerImplTest, SendData) { client_socket_->ioHandle(), *client_socket_->localAddress(), data); bytes_read = result.rc_; - EXPECT_EQ(send_from_addr->asString(), data.addresses_.peer_->asString()); if (bytes_read >= bytes_to_read || retry == 10 || - result.err_->getErrorCode() != Api::IoError::IoErrorCode::Again) { + (!result.ok() && result.err_->getErrorCode() != Api::IoError::IoErrorCode::Again)) { break; } + EXPECT_EQ(send_from_addr->asString(), data.addresses_.peer_->asString()); retry++; ::usleep(10000); From 183a1f9a717a1c7e26e6538ef3b5442702649b3e Mon Sep 17 00:00:00 2001 From: Dan Zhang Date: Wed, 20 Nov 2019 16:44:23 -0500 Subject: [PATCH 60/76] debug udp listener test Signed-off-by: Dan Zhang --- test/common/network/udp_listener_impl_test.cc | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/test/common/network/udp_listener_impl_test.cc b/test/common/network/udp_listener_impl_test.cc index 67caf089d7cbd..f18d1395f333e 100644 --- a/test/common/network/udp_listener_impl_test.cc +++ b/test/common/network/udp_listener_impl_test.cc @@ -412,12 +412,15 @@ TEST_P(UdpListenerImplTest, SendData) { client_socket_->ioHandle(), *client_socket_->localAddress(), data); bytes_read = result.rc_; + std::cerr << "=========== client readFromSocket bytes_read: " << bytes_read + << " retry: " << retry << " result is ok: " << result.ok() + << " peer address ptr : " << data.addresses_.peer_.get(); + EXPECT_EQ(send_from_addr->asString(), data.addresses_.peer_->asString()); if (bytes_read >= bytes_to_read || retry == 10 || - (!result.ok() && result.err_->getErrorCode() != Api::IoError::IoErrorCode::Again)) { + result.err_->getErrorCode() != Api::IoError::IoErrorCode::Again) { break; } - EXPECT_EQ(send_from_addr->asString(), data.addresses_.peer_->asString()); retry++; ::usleep(10000); From 476e52fe57533cd88fcc0b056a7d0798841b5020 Mon Sep 17 00:00:00 2001 From: Dan Zhang Date: Thu, 21 Nov 2019 11:47:39 -0500 Subject: [PATCH 61/76] debug coverage test Signed-off-by: Dan Zhang --- .../extensions/quic_listeners/quiche/active_quic_listener.h | 1 + test/common/network/udp_listener_impl_test.cc | 4 ---- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/source/extensions/quic_listeners/quiche/active_quic_listener.h b/source/extensions/quic_listeners/quiche/active_quic_listener.h index c31e4672c6596..3de759b78b92c 100644 --- a/source/extensions/quic_listeners/quiche/active_quic_listener.h +++ b/source/extensions/quic_listeners/quiche/active_quic_listener.h @@ -81,6 +81,7 @@ class ActiveQuicListenerFactory : public Network::ActiveUdpListenerFactory { int32_t max_streams = PROTOBUF_GET_WRAPPED_OR_DEFAULT(config, max_concurrent_streams, 100); quic_config_.SetMaxIncomingBidirectionalStreamsToSend(max_streams); quic_config_.SetMaxIncomingUnidirectionalStreamsToSend(max_streams); + quic_config_.set_max_idle_time_before_crypto_handshake(QuicTime::Delta::FromSeconds(10)); } // Network::ActiveUdpListenerFactory. diff --git a/test/common/network/udp_listener_impl_test.cc b/test/common/network/udp_listener_impl_test.cc index f18d1395f333e..062f0ebc933b8 100644 --- a/test/common/network/udp_listener_impl_test.cc +++ b/test/common/network/udp_listener_impl_test.cc @@ -395,7 +395,6 @@ TEST_P(UdpListenerImplTest, SendData) { server_socket_->localAddress()->ip()->port())); } - std::cerr << "========== send_from_addr " << send_from_addr->asString() << "\n"; UdpSendData send_data{send_from_addr->ip(), *client_socket_->localAddress(), *buffer}; auto send_result = listener_->send(send_data); @@ -412,9 +411,6 @@ TEST_P(UdpListenerImplTest, SendData) { client_socket_->ioHandle(), *client_socket_->localAddress(), data); bytes_read = result.rc_; - std::cerr << "=========== client readFromSocket bytes_read: " << bytes_read - << " retry: " << retry << " result is ok: " << result.ok() - << " peer address ptr : " << data.addresses_.peer_.get(); EXPECT_EQ(send_from_addr->asString(), data.addresses_.peer_->asString()); if (bytes_read >= bytes_to_read || retry == 10 || From 9c762c6765e14aa07d60582a0316ebb0d88fb291 Mon Sep 17 00:00:00 2001 From: Dan Zhang Date: Thu, 21 Nov 2019 12:42:04 -0500 Subject: [PATCH 62/76] fix quic namespace Signed-off-by: Dan Zhang --- source/extensions/quic_listeners/quiche/active_quic_listener.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/extensions/quic_listeners/quiche/active_quic_listener.h b/source/extensions/quic_listeners/quiche/active_quic_listener.h index 3de759b78b92c..e0d861c605273 100644 --- a/source/extensions/quic_listeners/quiche/active_quic_listener.h +++ b/source/extensions/quic_listeners/quiche/active_quic_listener.h @@ -81,7 +81,7 @@ class ActiveQuicListenerFactory : public Network::ActiveUdpListenerFactory { int32_t max_streams = PROTOBUF_GET_WRAPPED_OR_DEFAULT(config, max_concurrent_streams, 100); quic_config_.SetMaxIncomingBidirectionalStreamsToSend(max_streams); quic_config_.SetMaxIncomingUnidirectionalStreamsToSend(max_streams); - quic_config_.set_max_idle_time_before_crypto_handshake(QuicTime::Delta::FromSeconds(10)); + quic_config_.set_max_idle_time_before_crypto_handshake(quic::QuicTime::Delta::FromSeconds(10)); } // Network::ActiveUdpListenerFactory. From e77097238b8277d79be37c1fc3456e7191a3807d Mon Sep 17 00:00:00 2001 From: Dan Zhang Date: Thu, 21 Nov 2019 14:02:39 -0500 Subject: [PATCH 63/76] adjust client handshake idle timeout Signed-off-by: Dan Zhang --- .../quiche/integration/quic_http_integration_test.cc | 1 + 1 file changed, 1 insertion(+) diff --git a/test/extensions/quic_listeners/quiche/integration/quic_http_integration_test.cc b/test/extensions/quic_listeners/quiche/integration/quic_http_integration_test.cc index 2c84e2b6e1219..c0583c326b73f 100644 --- a/test/extensions/quic_listeners/quiche/integration/quic_http_integration_test.cc +++ b/test/extensions/quic_listeners/quiche/integration/quic_http_integration_test.cc @@ -44,6 +44,7 @@ class QuicHttpIntegrationTest : public testing::TestWithParam()), conn_helper_(*dispatcher_), alarm_factory_(*dispatcher_, *conn_helper_.GetClock()) { quic::SetVerbosityLogThreshold(1); + quic_config_.set_max_idle_time_before_crypto_handshake(quic::QuicTime::Delta::FromSeconds(10)); } Network::ClientConnectionPtr makeClientConnection(uint32_t port) override { From 503629681146d587bee20d41d8e2b24c1def5662 Mon Sep 17 00:00:00 2001 From: Dan Zhang Date: Thu, 21 Nov 2019 17:22:49 -0500 Subject: [PATCH 64/76] debug coverage test Signed-off-by: Dan Zhang --- ci/do_ci.sh | 2 +- test/run_envoy_bazel_coverage.sh | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/ci/do_ci.sh b/ci/do_ci.sh index 78a1a78d1dc6e..224be874194a1 100755 --- a/ci/do_ci.sh +++ b/ci/do_ci.sh @@ -241,7 +241,7 @@ elif [[ "$CI_TARGET" == "bazel.coverage" ]]; then # https://github.com/envoyproxy/envoy/pull/5611. [ -z "$CIRCLECI" ] || export BAZEL_BUILD_OPTIONS="${BAZEL_BUILD_OPTIONS} --local_ram_resources=12288" - test/run_envoy_bazel_coverage.sh ${TEST_TARGETS} + test/run_envoy_bazel_coverage.sh //test/extensions/quic_listeners/quiche/integration/... collect_build_profile coverage exit 0 elif [[ "$CI_TARGET" == "bazel.clang_tidy" ]]; then diff --git a/test/run_envoy_bazel_coverage.sh b/test/run_envoy_bazel_coverage.sh index 4c4af655bbf0b..5161a94270409 100755 --- a/test/run_envoy_bazel_coverage.sh +++ b/test/run_envoy_bazel_coverage.sh @@ -24,11 +24,12 @@ fi # Make sure //test/coverage:coverage_tests is up-to-date. SCRIPT_DIR="$(realpath "$(dirname "$0")")" "${SCRIPT_DIR}"/coverage/gen_build.sh ${COVERAGE_TARGETS} +echo "=========== ${COVERAGE_TARGETS}\n============ ${BAZEL_BUILD_OPTIONS}" BAZEL_USE_LLVM_NATIVE_COVERAGE=1 GCOV=llvm-profdata bazel coverage ${BAZEL_BUILD_OPTIONS} \ -c fastbuild --copt=-DNDEBUG --instrumentation_filter=//source/...,//include/... \ - --test_timeout=2000 --cxxopt="-DENVOY_CONFIG_COVERAGE=1" --test_output=errors \ - --test_arg="--log-path /dev/null" --test_arg="-l trace" --test_env=HEAPCHECK= \ + --test_timeout=2000 --cxxopt="-DENVOY_CONFIG_COVERAGE=1" --test_output=all \ + --test_arg="-l trace" --test_env=HEAPCHECK= \ //test/coverage:coverage_tests COVERAGE_DIR="${SRCDIR}"/generated/coverage From 0850f1a15555ab8421b884c71ddd2d34420c71aa Mon Sep 17 00:00:00 2001 From: Dan Zhang Date: Thu, 21 Nov 2019 18:16:02 -0500 Subject: [PATCH 65/76] coverage test Signed-off-by: Dan Zhang --- ci/do_ci.sh | 2 +- test/run_envoy_bazel_coverage.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ci/do_ci.sh b/ci/do_ci.sh index 224be874194a1..78a1a78d1dc6e 100755 --- a/ci/do_ci.sh +++ b/ci/do_ci.sh @@ -241,7 +241,7 @@ elif [[ "$CI_TARGET" == "bazel.coverage" ]]; then # https://github.com/envoyproxy/envoy/pull/5611. [ -z "$CIRCLECI" ] || export BAZEL_BUILD_OPTIONS="${BAZEL_BUILD_OPTIONS} --local_ram_resources=12288" - test/run_envoy_bazel_coverage.sh //test/extensions/quic_listeners/quiche/integration/... + test/run_envoy_bazel_coverage.sh ${TEST_TARGETS} collect_build_profile coverage exit 0 elif [[ "$CI_TARGET" == "bazel.clang_tidy" ]]; then diff --git a/test/run_envoy_bazel_coverage.sh b/test/run_envoy_bazel_coverage.sh index 5161a94270409..d8a1c8cffaf4c 100755 --- a/test/run_envoy_bazel_coverage.sh +++ b/test/run_envoy_bazel_coverage.sh @@ -29,7 +29,7 @@ echo "=========== ${COVERAGE_TARGETS}\n============ ${BAZEL_BUILD_OPTIONS}" BAZEL_USE_LLVM_NATIVE_COVERAGE=1 GCOV=llvm-profdata bazel coverage ${BAZEL_BUILD_OPTIONS} \ -c fastbuild --copt=-DNDEBUG --instrumentation_filter=//source/...,//include/... \ --test_timeout=2000 --cxxopt="-DENVOY_CONFIG_COVERAGE=1" --test_output=all \ - --test_arg="-l trace" --test_env=HEAPCHECK= \ + --test_arg="-l info" --test_env=HEAPCHECK= \ //test/coverage:coverage_tests COVERAGE_DIR="${SRCDIR}"/generated/coverage From 05f33c4c132f914ffff898663072e87f5771607d Mon Sep 17 00:00:00 2001 From: Dan Zhang Date: Thu, 21 Nov 2019 18:33:09 -0500 Subject: [PATCH 66/76] fix client side Signed-off-by: Dan Zhang --- .../quic_listeners/quiche/envoy_quic_client_stream.cc | 4 ++-- .../quic_listeners/quiche/envoy_quic_client_stream_test.cc | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_client_stream.cc b/source/extensions/quic_listeners/quiche/envoy_quic_client_stream.cc index 397f8529f3a83..4995937ce3d9b 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_client_stream.cc +++ b/source/extensions/quic_listeners/quiche/envoy_quic_client_stream.cc @@ -167,7 +167,7 @@ void EnvoyQuicClientStream::OnBodyAvailable() { return; } - if (!quic::VersionUsesQpack(transport_version()) && !FinishedReadingTrailers()) { + if (!quic::VersionUsesHttp3(transport_version()) && !FinishedReadingTrailers()) { // For Google QUIC implementation, trailers may arrived earlier and wait to // be consumed after reading all the body. Consume it here. // IETF QUIC shouldn't reach here because trailers are sent on same stream. @@ -183,7 +183,7 @@ void EnvoyQuicClientStream::OnTrailingHeadersComplete(bool fin, size_t frame_len quic::QuicSpdyStream::OnTrailingHeadersComplete(fin, frame_len, header_list); ASSERT(trailers_decompressed()); if (session()->connection()->connected() && - (quic::VersionUsesQpack(transport_version()) || sequencer()->IsClosed()) && + (quic::VersionUsesHttp3(transport_version()) || sequencer()->IsClosed()) && !FinishedReadingTrailers()) { // Before QPack, trailers can arrive before body. Only decode trailers after finishing decoding // body. diff --git a/test/extensions/quic_listeners/quiche/envoy_quic_client_stream_test.cc b/test/extensions/quic_listeners/quiche/envoy_quic_client_stream_test.cc index 71ec4ddac48fb..4bf8c21e208ae 100644 --- a/test/extensions/quic_listeners/quiche/envoy_quic_client_stream_test.cc +++ b/test/extensions/quic_listeners/quiche/envoy_quic_client_stream_test.cc @@ -161,7 +161,7 @@ TEST_P(EnvoyQuicClientStreamTest, PostRequestAndResponse) { } TEST_P(EnvoyQuicClientStreamTest, OutOfOrderTrailers) { - if (quic::VersionUsesQpack(quic_version_.transport_version)) { + if (quic::VersionUsesHttp3(quic_version_.transport_version)) { EXPECT_CALL(stream_callbacks_, onResetStream(_, _)); return; } From 3840dce01c45f3128eea92b3946d168a6e68f8d4 Mon Sep 17 00:00:00 2001 From: Dan Zhang Date: Thu, 21 Nov 2019 18:46:46 -0500 Subject: [PATCH 67/76] fix interface change Signed-off-by: Dan Zhang --- .../quic_listeners/quiche/envoy_quic_client_session.cc | 2 +- .../quic_listeners/quiche/envoy_quic_client_session_test.cc | 3 ++- .../quic_listeners/quiche/envoy_quic_client_stream_test.cc | 6 ++---- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_client_session.cc b/source/extensions/quic_listeners/quiche/envoy_quic_client_session.cc index 98ed298d81e12..0d9ad53430ebc 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_client_session.cc +++ b/source/extensions/quic_listeners/quiche/envoy_quic_client_session.cc @@ -27,7 +27,7 @@ void EnvoyQuicClientSession::connect() { // Start version negotiation and crypto handshake during which the connection may fail if server // doesn't support the one and only supported version. CryptoConnect(); - set_max_allowed_push_id(0u); + SetMaxAllowedPushId(0u); } void EnvoyQuicClientSession::OnConnectionClosed(const quic::QuicConnectionCloseFrame& frame, diff --git a/test/extensions/quic_listeners/quiche/envoy_quic_client_session_test.cc b/test/extensions/quic_listeners/quiche/envoy_quic_client_session_test.cc index 359beddee247b..302079db987f3 100644 --- a/test/extensions/quic_listeners/quiche/envoy_quic_client_session_test.cc +++ b/test/extensions/quic_listeners/quiche/envoy_quic_client_session_test.cc @@ -211,7 +211,8 @@ TEST_P(EnvoyQuicClientSessionTest, OnResetFrame) { TEST_P(EnvoyQuicClientSessionTest, ConnectionClose) { std::string error_details("dummy details"); quic::QuicErrorCode error(quic::QUIC_INVALID_FRAME_DATA); - quic::QuicConnectionCloseFrame frame(error, error_details); + quic::QuicConnectionCloseFrame frame(quic_version_[0].transport_version, error, error_details, + /* transport_close_frame_type = */ 0); EXPECT_CALL(network_connection_callbacks_, onEvent(Network::ConnectionEvent::RemoteClose)); quic_connection_->OnConnectionCloseFrame(frame); EXPECT_EQ(absl::StrCat(quic::QuicErrorCodeToString(error), " with details: ", error_details), diff --git a/test/extensions/quic_listeners/quiche/envoy_quic_client_stream_test.cc b/test/extensions/quic_listeners/quiche/envoy_quic_client_stream_test.cc index 4bf8c21e208ae..43b68e8219257 100644 --- a/test/extensions/quic_listeners/quiche/envoy_quic_client_stream_test.cc +++ b/test/extensions/quic_listeners/quiche/envoy_quic_client_stream_test.cc @@ -141,9 +141,8 @@ TEST_P(EnvoyQuicClientStreamTest, PostRequestAndResponse) { std::string data = response_body_; if (quic_version_.transport_version == quic::QUIC_VERSION_99) { std::unique_ptr data_buffer; - quic::HttpEncoder encoder; quic::QuicByteCount data_frame_header_length = - encoder.SerializeDataFrameHeader(response_body_.length(), &data_buffer); + quic::HttpEncoder::SerializeDataFrameHeader(response_body_.length(), &data_buffer); quic::QuicStringPiece data_frame_header(data_buffer.get(), data_frame_header_length); data = absl::StrCat(data_frame_header, response_body_); } @@ -184,9 +183,8 @@ TEST_P(EnvoyQuicClientStreamTest, OutOfOrderTrailers) { std::string data = response_body_; if (quic_version_.transport_version == quic::QUIC_VERSION_99) { std::unique_ptr data_buffer; - quic::HttpEncoder encoder; quic::QuicByteCount data_frame_header_length = - encoder.SerializeDataFrameHeader(response_body_.length(), &data_buffer); + quic::HttpEncoder::SerializeDataFrameHeader(response_body_.length(), &data_buffer); quic::QuicStringPiece data_frame_header(data_buffer.get(), data_frame_header_length); data = absl::StrCat(data_frame_header, response_body_); } From b52be056c406faaf7285839cef64fd4325f0a24e Mon Sep 17 00:00:00 2001 From: Dan Zhang Date: Fri, 22 Nov 2019 12:08:51 -0500 Subject: [PATCH 68/76] adjust coverage log level Signed-off-by: Dan Zhang --- test/run_envoy_bazel_coverage.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/run_envoy_bazel_coverage.sh b/test/run_envoy_bazel_coverage.sh index d8a1c8cffaf4c..93df9f6245666 100755 --- a/test/run_envoy_bazel_coverage.sh +++ b/test/run_envoy_bazel_coverage.sh @@ -28,8 +28,8 @@ echo "=========== ${COVERAGE_TARGETS}\n============ ${BAZEL_BUILD_OPTIONS}" BAZEL_USE_LLVM_NATIVE_COVERAGE=1 GCOV=llvm-profdata bazel coverage ${BAZEL_BUILD_OPTIONS} \ -c fastbuild --copt=-DNDEBUG --instrumentation_filter=//source/...,//include/... \ - --test_timeout=2000 --cxxopt="-DENVOY_CONFIG_COVERAGE=1" --test_output=all \ - --test_arg="-l info" --test_env=HEAPCHECK= \ + --test_timeout=2000 --cxxopt="-DENVOY_CONFIG_COVERAGE=1" --test_output=errors \ + --test_arg="--log-path /dev/null" --test_arg="-l info" --test_env=HEAPCHECK= \ //test/coverage:coverage_tests COVERAGE_DIR="${SRCDIR}"/generated/coverage From bcbc1477a40587dc462a1fe6a842093b6d11cfec Mon Sep 17 00:00:00 2001 From: Dan Zhang Date: Fri, 22 Nov 2019 13:35:28 -0500 Subject: [PATCH 69/76] re-enable trace logging in coverage test Signed-off-by: Dan Zhang --- test/run_envoy_bazel_coverage.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/run_envoy_bazel_coverage.sh b/test/run_envoy_bazel_coverage.sh index 93df9f6245666..f05fedd196144 100755 --- a/test/run_envoy_bazel_coverage.sh +++ b/test/run_envoy_bazel_coverage.sh @@ -29,7 +29,7 @@ echo "=========== ${COVERAGE_TARGETS}\n============ ${BAZEL_BUILD_OPTIONS}" BAZEL_USE_LLVM_NATIVE_COVERAGE=1 GCOV=llvm-profdata bazel coverage ${BAZEL_BUILD_OPTIONS} \ -c fastbuild --copt=-DNDEBUG --instrumentation_filter=//source/...,//include/... \ --test_timeout=2000 --cxxopt="-DENVOY_CONFIG_COVERAGE=1" --test_output=errors \ - --test_arg="--log-path /dev/null" --test_arg="-l info" --test_env=HEAPCHECK= \ + --test_arg="--log-path /dev/null" --test_arg="-l trace" --test_env=HEAPCHECK= \ //test/coverage:coverage_tests COVERAGE_DIR="${SRCDIR}"/generated/coverage From b10639f6996272642573663a3b40b96af43aaca1 Mon Sep 17 00:00:00 2001 From: Dan Zhang Date: Fri, 22 Nov 2019 14:29:35 -0500 Subject: [PATCH 70/76] revert bazel coverage script Signed-off-by: Dan Zhang --- test/run_envoy_bazel_coverage.sh | 1 - 1 file changed, 1 deletion(-) diff --git a/test/run_envoy_bazel_coverage.sh b/test/run_envoy_bazel_coverage.sh index f05fedd196144..4c4af655bbf0b 100755 --- a/test/run_envoy_bazel_coverage.sh +++ b/test/run_envoy_bazel_coverage.sh @@ -24,7 +24,6 @@ fi # Make sure //test/coverage:coverage_tests is up-to-date. SCRIPT_DIR="$(realpath "$(dirname "$0")")" "${SCRIPT_DIR}"/coverage/gen_build.sh ${COVERAGE_TARGETS} -echo "=========== ${COVERAGE_TARGETS}\n============ ${BAZEL_BUILD_OPTIONS}" BAZEL_USE_LLVM_NATIVE_COVERAGE=1 GCOV=llvm-profdata bazel coverage ${BAZEL_BUILD_OPTIONS} \ -c fastbuild --copt=-DNDEBUG --instrumentation_filter=//source/...,//include/... \ From 2855ae9de0c9a3846ae21868d5f8fc6aa7763e50 Mon Sep 17 00:00:00 2001 From: Dan Zhang Date: Fri, 22 Nov 2019 15:22:26 -0500 Subject: [PATCH 71/76] disable quic logging Signed-off-by: Dan Zhang --- .../quic_listeners/quiche/envoy_quic_client_session_test.cc | 1 - .../quic_listeners/quiche/envoy_quic_client_stream_test.cc | 1 - .../quiche/integration/quic_http_integration_test.cc | 1 - 3 files changed, 3 deletions(-) diff --git a/test/extensions/quic_listeners/quiche/envoy_quic_client_session_test.cc b/test/extensions/quic_listeners/quiche/envoy_quic_client_session_test.cc index 302079db987f3..ea2c111e0a582 100644 --- a/test/extensions/quic_listeners/quiche/envoy_quic_client_session_test.cc +++ b/test/extensions/quic_listeners/quiche/envoy_quic_client_session_test.cc @@ -50,7 +50,6 @@ class TestEnvoyQuicClientConnection : public EnvoyQuicClientConnection { SetDefaultEncryptionLevel(quic::ENCRYPTION_FORWARD_SECURE); SetEncrypter(quic::ENCRYPTION_FORWARD_SECURE, std::make_unique(quic::Perspective::IS_CLIENT)); - quic::SetVerbosityLogThreshold(3); } MOCK_METHOD2(SendConnectionClosePacket, void(quic::QuicErrorCode, const std::string&)); diff --git a/test/extensions/quic_listeners/quiche/envoy_quic_client_stream_test.cc b/test/extensions/quic_listeners/quiche/envoy_quic_client_stream_test.cc index 43b68e8219257..bb56c21926d05 100644 --- a/test/extensions/quic_listeners/quiche/envoy_quic_client_stream_test.cc +++ b/test/extensions/quic_listeners/quiche/envoy_quic_client_stream_test.cc @@ -42,7 +42,6 @@ class EnvoyQuicClientStreamTest : public testing::TestWithParam { stream_id_(quic_version_.transport_version == quic::QUIC_VERSION_99 ? 4u : 5u), quic_stream_(new EnvoyQuicClientStream(stream_id_, &quic_session_, quic::BIDIRECTIONAL)), request_headers_{{":authority", host_}, {":method", "POST"}, {":path", "/"}} { - quic::SetVerbosityLogThreshold(3); quic_stream_->setDecoder(stream_decoder_); quic_stream_->addCallbacks(stream_callbacks_); quic_session_.ActivateStream(std::unique_ptr(quic_stream_)); diff --git a/test/extensions/quic_listeners/quiche/integration/quic_http_integration_test.cc b/test/extensions/quic_listeners/quiche/integration/quic_http_integration_test.cc index c0583c326b73f..10a0eb81f3d58 100644 --- a/test/extensions/quic_listeners/quiche/integration/quic_http_integration_test.cc +++ b/test/extensions/quic_listeners/quiche/integration/quic_http_integration_test.cc @@ -43,7 +43,6 @@ class QuicHttpIntegrationTest : public testing::TestWithParam()), conn_helper_(*dispatcher_), alarm_factory_(*dispatcher_, *conn_helper_.GetClock()) { - quic::SetVerbosityLogThreshold(1); quic_config_.set_max_idle_time_before_crypto_handshake(quic::QuicTime::Delta::FromSeconds(10)); } From a2282a4f9d7b8c285537da7306e33f80b70b0c30 Mon Sep 17 00:00:00 2001 From: Dan Zhang Date: Mon, 25 Nov 2019 11:24:44 -0500 Subject: [PATCH 72/76] revert handshake timeout to 5s Signed-off-by: Dan Zhang --- .../extensions/quic_listeners/quiche/active_quic_listener.h | 1 - .../quiche/integration/quic_http_integration_test.cc | 4 +--- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/source/extensions/quic_listeners/quiche/active_quic_listener.h b/source/extensions/quic_listeners/quiche/active_quic_listener.h index b5f8e7c08c208..1e2fc306135b4 100644 --- a/source/extensions/quic_listeners/quiche/active_quic_listener.h +++ b/source/extensions/quic_listeners/quiche/active_quic_listener.h @@ -76,7 +76,6 @@ class ActiveQuicListenerFactory : public Network::ActiveUdpListenerFactory { int32_t max_streams = PROTOBUF_GET_WRAPPED_OR_DEFAULT(config, max_concurrent_streams, 100); quic_config_.SetMaxIncomingBidirectionalStreamsToSend(max_streams); quic_config_.SetMaxIncomingUnidirectionalStreamsToSend(max_streams); - quic_config_.set_max_idle_time_before_crypto_handshake(quic::QuicTime::Delta::FromSeconds(10)); } // Network::ActiveUdpListenerFactory. diff --git a/test/extensions/quic_listeners/quiche/integration/quic_http_integration_test.cc b/test/extensions/quic_listeners/quiche/integration/quic_http_integration_test.cc index 10a0eb81f3d58..d65792479b980 100644 --- a/test/extensions/quic_listeners/quiche/integration/quic_http_integration_test.cc +++ b/test/extensions/quic_listeners/quiche/integration/quic_http_integration_test.cc @@ -42,9 +42,7 @@ class QuicHttpIntegrationTest : public testing::TestWithParam()), conn_helper_(*dispatcher_), - alarm_factory_(*dispatcher_, *conn_helper_.GetClock()) { - quic_config_.set_max_idle_time_before_crypto_handshake(quic::QuicTime::Delta::FromSeconds(10)); - } + alarm_factory_(*dispatcher_, *conn_helper_.GetClock()) {} Network::ClientConnectionPtr makeClientConnection(uint32_t port) override { Network::Address::InstanceConstSharedPtr server_addr = Network::Utility::resolveUrl( From 88780bb433918e764b44adea85cfccc94c7a6f10 Mon Sep 17 00:00:00 2001 From: Dan Zhang Date: Tue, 3 Dec 2019 19:55:00 -0500 Subject: [PATCH 73/76] address comments Signed-off-by: Dan Zhang --- .bazelrc | 1 - source/common/http/http3/quic_codec_factory.h | 12 ++++++------ source/common/http/http3/well_known_names.h | 4 ++-- .../extensions/quic_listeners/quiche/codec_impl.cc | 10 ++++++---- source/extensions/quic_listeners/quiche/codec_impl.h | 10 ++++++---- .../quic_listeners/quiche/envoy_quic_alarm.h | 2 +- .../quiche/envoy_quic_client_connection.cc | 8 +------- .../quiche/envoy_quic_client_connection.h | 3 --- .../quiche/envoy_quic_client_session.cc | 5 ++--- .../quiche/envoy_quic_client_session.h | 4 ++-- .../quiche/envoy_quic_client_stream.cc | 1 - .../quic_listeners/quiche/envoy_quic_connection.cc | 5 +---- 12 files changed, 27 insertions(+), 38 deletions(-) diff --git a/.bazelrc b/.bazelrc index 9661c979514d9..86aec484c3dbf 100644 --- a/.bazelrc +++ b/.bazelrc @@ -19,7 +19,6 @@ build --action_env=BAZEL_LINKLIBS=-l%:libstdc++.a build --action_env=BAZEL_LINKOPTS=-lm build --host_javabase=@bazel_tools//tools/jdk:remote_jdk11 build --javabase=@bazel_tools//tools/jdk:remote_jdk11 -build --enable_platform_specific_config # Enable position independent code, this option is not supported on Windows and default on on macOS. build:linux --copt=-fPIC diff --git a/source/common/http/http3/quic_codec_factory.h b/source/common/http/http3/quic_codec_factory.h index 590a725108f9f..21ca7cab87047 100644 --- a/source/common/http/http3/quic_codec_factory.h +++ b/source/common/http/http3/quic_codec_factory.h @@ -15,10 +15,10 @@ class QuicHttpServerConnectionFactory { virtual std::string name() const PURE; - virtual ServerConnection* createQuicServerConnection(Network::Connection& connection, - ConnectionCallbacks& callbacks) PURE; + virtual std::unique_ptr + createQuicServerConnection(Network::Connection& connection, ConnectionCallbacks& callbacks) PURE; - static std::string category() { return "quic_codec"; } + static std::string category() { return "quic_client_codec"; } }; // A factory to create Http::ClientConnection instance for QUIC. @@ -28,10 +28,10 @@ class QuicHttpClientConnectionFactory { virtual std::string name() const PURE; - virtual ClientConnection* createQuicClientConnection(Network::Connection& connection, - ConnectionCallbacks& callbacks) PURE; + virtual std::unique_ptr + createQuicClientConnection(Network::Connection& connection, ConnectionCallbacks& callbacks) PURE; - static std::string category() { return "quic_codec"; } + static std::string category() { return "quic_server_codec"; } }; } // namespace Http diff --git a/source/common/http/http3/well_known_names.h b/source/common/http/http3/well_known_names.h index 6bb0b88d4c951..b89d9d1384679 100644 --- a/source/common/http/http3/well_known_names.h +++ b/source/common/http/http3/well_known_names.h @@ -9,8 +9,8 @@ namespace Http { class QuicCodecNameValues { public: - const std::string Client = "client_codec"; - const std::string Server = "server_codec"; + const std::string Client = "quiche_client"; + const std::string Server = "quiche_server"; }; using QuicCodecNames = ConstSingleton; diff --git a/source/extensions/quic_listeners/quiche/codec_impl.cc b/source/extensions/quic_listeners/quiche/codec_impl.cc index 7af9a9639810f..4af18bd949b3f 100644 --- a/source/extensions/quic_listeners/quiche/codec_impl.cc +++ b/source/extensions/quic_listeners/quiche/codec_impl.cc @@ -81,15 +81,17 @@ void QuicHttpClientConnectionImpl::onUnderlyingConnectionBelowWriteBufferLowWate runWatermarkCallbacksForEachStream(quic_client_session_.stream_map(), false); } -Http::ClientConnection* QuicHttpClientConnectionFactoryImpl::createQuicClientConnection( +std::unique_ptr +QuicHttpClientConnectionFactoryImpl::createQuicClientConnection( Network::Connection& connection, Http::ConnectionCallbacks& callbacks) { - return new Quic::QuicHttpClientConnectionImpl( + return std::make_unique( dynamic_cast(connection), callbacks); } -Http::ServerConnection* QuicHttpServerConnectionFactoryImpl::createQuicServerConnection( +std::unique_ptr +QuicHttpServerConnectionFactoryImpl::createQuicServerConnection( Network::Connection& connection, Http::ConnectionCallbacks& callbacks) { - return new Quic::QuicHttpServerConnectionImpl( + return std::make_unique( dynamic_cast(connection), dynamic_cast(callbacks)); } diff --git a/source/extensions/quic_listeners/quiche/codec_impl.h b/source/extensions/quic_listeners/quiche/codec_impl.h index 78694b96898b9..a4734599d3e53 100644 --- a/source/extensions/quic_listeners/quiche/codec_impl.h +++ b/source/extensions/quic_listeners/quiche/codec_impl.h @@ -79,8 +79,9 @@ class QuicHttpClientConnectionImpl : public QuicHttpConnectionImplBase, // A factory to create QuicHttpClientConnection. class QuicHttpClientConnectionFactoryImpl : public Http::QuicHttpClientConnectionFactory { public: - Http::ClientConnection* createQuicClientConnection(Network::Connection& connection, - Http::ConnectionCallbacks& callbacks) override; + std::unique_ptr + createQuicClientConnection(Network::Connection& connection, + Http::ConnectionCallbacks& callbacks) override; std::string name() const override { return Http::QuicCodecNames::get().Client; } }; @@ -88,8 +89,9 @@ class QuicHttpClientConnectionFactoryImpl : public Http::QuicHttpClientConnectio // A factory to create QuicHttpServerConnection. class QuicHttpServerConnectionFactoryImpl : public Http::QuicHttpServerConnectionFactory { public: - Http::ServerConnection* createQuicServerConnection(Network::Connection& connection, - Http::ConnectionCallbacks& callbacks) override; + std::unique_ptr + createQuicServerConnection(Network::Connection& connection, + Http::ConnectionCallbacks& callbacks) override; std::string name() const override { return Http::QuicCodecNames::get().Server; } }; diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_alarm.h b/source/extensions/quic_listeners/quiche/envoy_quic_alarm.h index a3bb0ee8a1623..115e68d1f882e 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_alarm.h +++ b/source/extensions/quic_listeners/quiche/envoy_quic_alarm.h @@ -21,7 +21,7 @@ class EnvoyQuicAlarm : public quic::QuicAlarm { quic::QuicArenaScopedPtr delegate); // TimerImpl destruction deletes in-flight alarm firing event. - ~EnvoyQuicAlarm() override{}; + ~EnvoyQuicAlarm() override {} // quic::QuicAlarm void CancelImpl() override; diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_client_connection.cc b/source/extensions/quic_listeners/quiche/envoy_quic_client_connection.cc index a00cb73cce062..a3bd4a92d394c 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_client_connection.cc +++ b/source/extensions/quic_listeners/quiche/envoy_quic_client_connection.cc @@ -42,12 +42,6 @@ EnvoyQuicClientConnection::EnvoyQuicClientConnection( std::move(connection_socket)), dispatcher_(dispatcher) {} -EnvoyQuicClientConnection::~EnvoyQuicClientConnection() { - if (file_event_ != nullptr) { - file_event_->setEnabled(0); - } -} - void EnvoyQuicClientConnection::processPacket( Network::Address::InstanceConstSharedPtr local_address, Network::Address::InstanceConstSharedPtr peer_address, Buffer::InstancePtr buffer, @@ -91,7 +85,7 @@ void EnvoyQuicClientConnection::setUpConnectionSocket() { } } if (!connectionSocket()->ioHandle().isOpen()) { - CloseConnection(quic::QUIC_CONNECTION_CANCELLED, "Fail to setup connection socket.", + CloseConnection(quic::QUIC_CONNECTION_CANCELLED, "Fail to set up connection socket.", quic::ConnectionCloseBehavior::SILENT_CLOSE); } } diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_client_connection.h b/source/extensions/quic_listeners/quiche/envoy_quic_client_connection.h index 985a07c5c5d77..8ee2f22dad2e1 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_client_connection.h +++ b/source/extensions/quic_listeners/quiche/envoy_quic_client_connection.h @@ -31,9 +31,6 @@ class EnvoyQuicClientConnection : public EnvoyQuicConnection, public Network::Ud Event::Dispatcher& dispatcher, Network::ConnectionSocketPtr&& connection_socket); - // Overridden to un-register all file events. - ~EnvoyQuicClientConnection() override; - // Network::UdpPacketProcessor void processPacket(Network::Address::InstanceConstSharedPtr local_address, Network::Address::InstanceConstSharedPtr peer_address, diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_client_session.cc b/source/extensions/quic_listeners/quiche/envoy_quic_client_session.cc index f4bc7a1e11a74..d600055c5c8b0 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_client_session.cc +++ b/source/extensions/quic_listeners/quiche/envoy_quic_client_session.cc @@ -58,9 +58,8 @@ void EnvoyQuicClientSession::OnCryptoHandshakeEvent(CryptoHandshakeEvent event) } std::unique_ptr EnvoyQuicClientSession::CreateClientStream() { - auto stream = std::make_unique(GetNextOutgoingBidirectionalStreamId(), - this, quic::BIDIRECTIONAL); - return stream; + return std::make_unique(GetNextOutgoingBidirectionalStreamId(), this, + quic::BIDIRECTIONAL); } quic::QuicSpdyStream* EnvoyQuicClientSession::CreateIncomingStream(quic::QuicStreamId /*id*/) { diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_client_session.h b/source/extensions/quic_listeners/quiche/envoy_quic_client_session.h index 7fc9db45d5068..1bebce79ed2b9 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_client_session.h +++ b/source/extensions/quic_listeners/quiche/envoy_quic_client_session.h @@ -46,7 +46,7 @@ class EnvoyQuicClientSession : public QuicFilterManagerConnectionImpl, absl::string_view requestedServerName() const override; // Network::ClientConnection - // Setup socket and start handshake. + // Set up socket and start handshake. void connect() override; // quic::QuicSession @@ -67,7 +67,7 @@ class EnvoyQuicClientSession : public QuicFilterManagerConnectionImpl, quic::QuicSpdyStream* CreateIncomingStream(quic::PendingStream* pending) override; private: - // These callbacks are owned by network filters and quic session should out live + // These callbacks are owned by network filters and quic session should outlive // them. Http::ConnectionCallbacks* http_connection_callbacks_{nullptr}; }; diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_client_stream.cc b/source/extensions/quic_listeners/quiche/envoy_quic_client_stream.cc index 4995937ce3d9b..b6fed6f10833f 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_client_stream.cc +++ b/source/extensions/quic_listeners/quiche/envoy_quic_client_stream.cc @@ -150,7 +150,6 @@ void EnvoyQuicClientStream::OnBodyAvailable() { // already delivered it or decodeTrailers will be called. bool skip_decoding = empty_payload_with_fin && (end_stream_decoded_ || !finished_reading); if (!skip_decoding) { - ASSERT(decoder() != nullptr); decoder()->decodeData(*buffer, finished_reading); if (finished_reading) { end_stream_decoded_ = true; diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_connection.cc b/source/extensions/quic_listeners/quiche/envoy_quic_connection.cc index f2459bf79a190..dcc311a6eaac6 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_connection.cc +++ b/source/extensions/quic_listeners/quiche/envoy_quic_connection.cc @@ -19,10 +19,7 @@ EnvoyQuicConnection::EnvoyQuicConnection(const quic::QuicConnectionId& server_co EnvoyQuicConnection::~EnvoyQuicConnection() { connection_socket_->close(); } -uint64_t EnvoyQuicConnection::id() const { - ASSERT(envoy_connection_ != nullptr); - return envoy_connection_->id(); -} +uint64_t EnvoyQuicConnection::id() const { return envoy_connection_->id(); } } // namespace Quic } // namespace Envoy From 88530b20c52c966d1a6caafbece482a1a8108e5f Mon Sep 17 00:00:00 2001 From: Dan Zhang Date: Tue, 3 Dec 2019 19:55:51 -0500 Subject: [PATCH 74/76] revert bazelrc Signed-off-by: Dan Zhang --- .bazelrc | 1 + 1 file changed, 1 insertion(+) diff --git a/.bazelrc b/.bazelrc index 86aec484c3dbf..9661c979514d9 100644 --- a/.bazelrc +++ b/.bazelrc @@ -19,6 +19,7 @@ build --action_env=BAZEL_LINKLIBS=-l%:libstdc++.a build --action_env=BAZEL_LINKOPTS=-lm build --host_javabase=@bazel_tools//tools/jdk:remote_jdk11 build --javabase=@bazel_tools//tools/jdk:remote_jdk11 +build --enable_platform_specific_config # Enable position independent code, this option is not supported on Windows and default on on macOS. build:linux --copt=-fPIC From 1a42c05df4e7758399bfe36606a8365ee6e81bea Mon Sep 17 00:00:00 2001 From: Dan Zhang Date: Wed, 4 Dec 2019 17:37:18 -0500 Subject: [PATCH 75/76] factory rename Signed-off-by: Dan Zhang --- source/common/http/codec_client.cc | 2 +- source/common/http/http3/well_known_names.h | 4 ++-- .../filters/network/http_connection_manager/config.cc | 6 +++++- source/extensions/quic_listeners/quiche/codec_impl.h | 4 ++-- test/config/utility.cc | 3 +++ 5 files changed, 13 insertions(+), 6 deletions(-) diff --git a/source/common/http/codec_client.cc b/source/common/http/codec_client.cc index 936af0c209210..673212d3e69b7 100644 --- a/source/common/http/codec_client.cc +++ b/source/common/http/codec_client.cc @@ -163,7 +163,7 @@ CodecClientProd::CodecClientProd(Type type, Network::ClientConnectionPtr&& conne case Type::HTTP3: { codec_ = std::unique_ptr( Config::Utility::getAndCheckFactory( - Http::QuicCodecNames::get().Client) + Http::QuicCodecNames::get().Quiche) .createQuicClientConnection(*connection_, *this)); } } diff --git a/source/common/http/http3/well_known_names.h b/source/common/http/http3/well_known_names.h index b89d9d1384679..aace82a76d55f 100644 --- a/source/common/http/http3/well_known_names.h +++ b/source/common/http/http3/well_known_names.h @@ -9,8 +9,8 @@ namespace Http { class QuicCodecNameValues { public: - const std::string Client = "quiche_client"; - const std::string Server = "quiche_server"; + // QUICHE is the only QUIC implementation for now. + const std::string Quiche = "quiche"; }; using QuicCodecNames = ConstSingleton; diff --git a/source/extensions/filters/network/http_connection_manager/config.cc b/source/extensions/filters/network/http_connection_manager/config.cc index 07cf5e2d51136..591f35c1870f9 100644 --- a/source/extensions/filters/network/http_connection_manager/config.cc +++ b/source/extensions/filters/network/http_connection_manager/config.cc @@ -417,9 +417,13 @@ HttpConnectionManagerConfig::createCodec(Network::Connection& connection, connection, callbacks, context_.scope(), http2_settings_, maxRequestHeadersKb(), maxRequestHeadersCount()); case CodecType::HTTP3: + // Hard code Quiche factory name here to instantiate a QUIC codec implmeneted. + // TODO(danzh) Add support to get the factory name from config, possibly + // from HttpConnectionManager protobuf. This is not essential till there are multiple + // implementations of QUIC. return std::unique_ptr( Config::Utility::getAndCheckFactory( - Http::QuicCodecNames::get().Server) + Http::QuicCodecNames::get().Quiche) .createQuicServerConnection(connection, callbacks)); case CodecType::AUTO: return Http::ConnectionManagerUtility::autoCreateCodec( diff --git a/source/extensions/quic_listeners/quiche/codec_impl.h b/source/extensions/quic_listeners/quiche/codec_impl.h index a4734599d3e53..9394ec11e1985 100644 --- a/source/extensions/quic_listeners/quiche/codec_impl.h +++ b/source/extensions/quic_listeners/quiche/codec_impl.h @@ -83,7 +83,7 @@ class QuicHttpClientConnectionFactoryImpl : public Http::QuicHttpClientConnectio createQuicClientConnection(Network::Connection& connection, Http::ConnectionCallbacks& callbacks) override; - std::string name() const override { return Http::QuicCodecNames::get().Client; } + std::string name() const override { return Http::QuicCodecNames::get().Quiche; } }; // A factory to create QuicHttpServerConnection. @@ -93,7 +93,7 @@ class QuicHttpServerConnectionFactoryImpl : public Http::QuicHttpServerConnectio createQuicServerConnection(Network::Connection& connection, Http::ConnectionCallbacks& callbacks) override; - std::string name() const override { return Http::QuicCodecNames::get().Server; } + std::string name() const override { return Http::QuicCodecNames::get().Quiche; } }; DECLARE_FACTORY(QuicHttpClientConnectionFactoryImpl); diff --git a/test/config/utility.cc b/test/config/utility.cc index 3c8423e668aa1..a7f75450ab0d6 100644 --- a/test/config/utility.cc +++ b/test/config/utility.cc @@ -116,6 +116,9 @@ const std::string ConfigHelper::HTTP_PROXY_CONFIG = BASE_CONFIG + R"EOF( name: route_config_0 )EOF"; +// TODO(danzh): For better compatibility with HTTP integration test framework, +// it's better to combine with HTTP_PROXY_CONFIG, and use config modifiers to +// specify quic specific things. const std::string ConfigHelper::QUIC_HTTP_PROXY_CONFIG = BASE_UDP_LISTENER_CONFIG + R"EOF( filter_chains: transport_socket: From cd58540403b104cf6677a0e31e6a5fb4684198bb Mon Sep 17 00:00:00 2001 From: Dan Zhang Date: Wed, 4 Dec 2019 17:52:34 -0500 Subject: [PATCH 76/76] typo Signed-off-by: Dan Zhang --- .../filters/network/http_connection_manager/config.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/extensions/filters/network/http_connection_manager/config.cc b/source/extensions/filters/network/http_connection_manager/config.cc index 591f35c1870f9..f7eed2e932aaf 100644 --- a/source/extensions/filters/network/http_connection_manager/config.cc +++ b/source/extensions/filters/network/http_connection_manager/config.cc @@ -417,7 +417,7 @@ HttpConnectionManagerConfig::createCodec(Network::Connection& connection, connection, callbacks, context_.scope(), http2_settings_, maxRequestHeadersKb(), maxRequestHeadersCount()); case CodecType::HTTP3: - // Hard code Quiche factory name here to instantiate a QUIC codec implmeneted. + // Hard code Quiche factory name here to instantiate a QUIC codec implemented. // TODO(danzh) Add support to get the factory name from config, possibly // from HttpConnectionManager protobuf. This is not essential till there are multiple // implementations of QUIC.