From 1a84aa2b815efc8920157f5cc5df63cb3a5c162b Mon Sep 17 00:00:00 2001 From: danzh Date: Mon, 16 Sep 2019 14:40:40 -0400 Subject: [PATCH] quiche: implement ActiveQuicListener (#7896) Signed-off-by: Dan Zhang --- api/envoy/api/v2/listener/BUILD | 6 + api/envoy/api/v2/listener/quic_config.proto | 28 +++ docs/root/api-v2/listeners/listeners.rst | 1 + include/envoy/event/dispatcher.h | 4 +- include/envoy/network/connection_handler.h | 13 ++ include/envoy/network/listener.h | 2 + .../envoy/server/active_udp_listener_config.h | 4 + source/common/event/dispatcher_impl.cc | 6 +- source/common/event/dispatcher_impl.h | 4 +- source/extensions/quic_listeners/quiche/BUILD | 31 +++ .../quiche/active_quic_listener.cc | 84 ++++++++ .../quiche/active_quic_listener.h | 107 ++++++++++ .../quiche/active_quic_listener_config.cc | 25 +++ .../quiche/active_quic_listener_config.h | 27 +++ .../quiche/envoy_quic_dispatcher.cc | 19 +- .../quiche/envoy_quic_dispatcher.h | 10 +- .../server/active_raw_udp_listener_config.cc | 4 + .../server/active_raw_udp_listener_config.h | 2 + source/server/connection_handler_impl.cc | 7 + source/server/connection_handler_impl.h | 2 + source/server/listener_manager_impl.cc | 12 ++ test/extensions/quic_listeners/quiche/BUILD | 28 +++ .../active_quic_listener_config_test.cc | 48 +++++ .../quiche/active_quic_listener_test.cc | 192 ++++++++++++++++++ .../quiche/envoy_quic_dispatcher_test.cc | 5 +- test/mocks/event/mocks.h | 8 +- test/mocks/network/mocks.h | 2 + test/server/BUILD | 33 ++- test/server/connection_handler_test.cc | 7 +- .../listener_manager_impl_quic_only_test.cc | 46 +++++ test/server/listener_manager_impl_test.cc | 112 +--------- test/server/listener_manager_impl_test.h | 119 +++++++++++ 32 files changed, 852 insertions(+), 146 deletions(-) create mode 100644 api/envoy/api/v2/listener/quic_config.proto create mode 100644 source/extensions/quic_listeners/quiche/active_quic_listener.cc create mode 100644 source/extensions/quic_listeners/quiche/active_quic_listener.h create mode 100644 source/extensions/quic_listeners/quiche/active_quic_listener_config.cc create mode 100644 source/extensions/quic_listeners/quiche/active_quic_listener_config.h create mode 100644 test/extensions/quic_listeners/quiche/active_quic_listener_config_test.cc create mode 100644 test/extensions/quic_listeners/quiche/active_quic_listener_test.cc create mode 100644 test/server/listener_manager_impl_quic_only_test.cc create mode 100644 test/server/listener_manager_impl_test.h diff --git a/api/envoy/api/v2/listener/BUILD b/api/envoy/api/v2/listener/BUILD index 42c79fe45483..99a82254d165 100644 --- a/api/envoy/api/v2/listener/BUILD +++ b/api/envoy/api/v2/listener/BUILD @@ -28,3 +28,9 @@ api_proto_library_internal( "//envoy/api/v2/core:base", ], ) + +api_proto_library_internal( + name = "quic_config", + srcs = ["quic_config.proto"], + visibility = ["//envoy/api/v2:friends"], +) diff --git a/api/envoy/api/v2/listener/quic_config.proto b/api/envoy/api/v2/listener/quic_config.proto new file mode 100644 index 000000000000..95ffc3cdf319 --- /dev/null +++ b/api/envoy/api/v2/listener/quic_config.proto @@ -0,0 +1,28 @@ +syntax = "proto3"; + +package envoy.api.v2.listener; + +option java_outer_classname = "ListenerProto"; +option java_multiple_files = true; +option java_package = "io.envoyproxy.envoy.api.v2.listener"; +option csharp_namespace = "Envoy.Api.V2.ListenerNS"; +option ruby_package = "Envoy::Api::V2::ListenerNS"; + +import "google/protobuf/duration.proto"; +import "google/protobuf/wrappers.proto"; + +// Configuration specific to the QUIC protocol. +// Next id: 4 +message QuicProtocolOptions { + // Maximum number of streams that the client can negotiate per connection. 100 + // if not specified. + google.protobuf.UInt32Value max_concurrent_streams = 1; + + // Maximum number of milliseconds that connection will be alive when there is + // no network activity. 300000ms if not specified. + google.protobuf.Duration idle_timeout = 2; + + // Connection timeout in milliseconds before the crypto handshake is finished. + // 20000ms if not specified. + google.protobuf.Duration crypto_handshake_timeout = 3; +} diff --git a/docs/root/api-v2/listeners/listeners.rst b/docs/root/api-v2/listeners/listeners.rst index 6ed0279da7de..47d92c85cd46 100644 --- a/docs/root/api-v2/listeners/listeners.rst +++ b/docs/root/api-v2/listeners/listeners.rst @@ -8,3 +8,4 @@ Listeners ../api/v2/lds.proto ../api/v2/listener/listener.proto ../api/v2/listener/udp_listener_config.proto + ../api/v2/listener/quic_config.proto diff --git a/include/envoy/event/dispatcher.h b/include/envoy/event/dispatcher.h index b9ae70960df2..a29f7e9005c9 100644 --- a/include/envoy/event/dispatcher.h +++ b/include/envoy/event/dispatcher.h @@ -145,8 +145,8 @@ class Dispatcher { * @param cb supplies the udp listener callbacks to invoke for listener events. * @return Network::ListenerPtr a new listener that is owned by the caller. */ - virtual Network::ListenerPtr createUdpListener(Network::Socket& socket, - Network::UdpListenerCallbacks& cb) PURE; + virtual Network::UdpListenerPtr createUdpListener(Network::Socket& socket, + Network::UdpListenerCallbacks& cb) PURE; /** * Allocates a timer. @see Timer for docs on how to use the timer. * @param cb supplies the callback to invoke when the timer fires. diff --git a/include/envoy/network/connection_handler.h b/include/envoy/network/connection_handler.h index f26c514d5ce2..dfa0e3b13e09 100644 --- a/include/envoy/network/connection_handler.h +++ b/include/envoy/network/connection_handler.h @@ -26,6 +26,19 @@ class ConnectionHandler { */ virtual uint64_t numConnections() PURE; + /** + * Increment the return value of numConnections() by one. + * TODO(mattklein123): re-visit the connection accounting interface. Make TCP + * listener to do accounting through these interfaces instead of directly + * access the counter. + */ + virtual void incNumConnections() PURE; + + /** + * Decrement the return value of numConnections() by one. + */ + virtual void decNumConnections() PURE; + /** * Adds a listener to the handler. * @param config listener configuration options. diff --git a/include/envoy/network/listener.h b/include/envoy/network/listener.h index 451a76508581..2c1a1e4f16d5 100644 --- a/include/envoy/network/listener.h +++ b/include/envoy/network/listener.h @@ -242,6 +242,8 @@ class UdpListener : public virtual Listener { virtual Api::IoCallUint64Result send(const UdpSendData& data) PURE; }; +using UdpListenerPtr = std::unique_ptr; + /** * Thrown when there is a runtime error creating/binding a listener. */ diff --git a/include/envoy/server/active_udp_listener_config.h b/include/envoy/server/active_udp_listener_config.h index 810d25add389..e17c314d60b7 100644 --- a/include/envoy/server/active_udp_listener_config.h +++ b/include/envoy/server/active_udp_listener_config.h @@ -2,6 +2,8 @@ #include "envoy/network/connection_handler.h" +#include "common/protobuf/protobuf.h" + namespace Envoy { namespace Server { @@ -13,6 +15,8 @@ class ActiveUdpListenerConfigFactory { public: virtual ~ActiveUdpListenerConfigFactory() = default; + virtual ProtobufTypes::MessagePtr createEmptyConfigProto() PURE; + /** * Create an ActiveUdpListenerFactory object according to given message. */ diff --git a/source/common/event/dispatcher_impl.cc b/source/common/event/dispatcher_impl.cc index 28fc0f6b8312..26bd33a8a68d 100644 --- a/source/common/event/dispatcher_impl.cc +++ b/source/common/event/dispatcher_impl.cc @@ -139,10 +139,10 @@ DispatcherImpl::createListener(Network::Socket& socket, Network::ListenerCallbac hand_off_restored_destination_connections)}; } -Network::ListenerPtr DispatcherImpl::createUdpListener(Network::Socket& socket, - Network::UdpListenerCallbacks& cb) { +Network::UdpListenerPtr DispatcherImpl::createUdpListener(Network::Socket& socket, + Network::UdpListenerCallbacks& cb) { ASSERT(isThreadSafe()); - return Network::ListenerPtr{new Network::UdpListenerImpl(*this, socket, cb, timeSource())}; + return std::make_unique(*this, socket, cb, timeSource()); } TimerPtr DispatcherImpl::createTimer(TimerCb cb) { return createTimerInternal(cb); } diff --git a/source/common/event/dispatcher_impl.h b/source/common/event/dispatcher_impl.h index 8108b3249dc0..650801c2f848 100644 --- a/source/common/event/dispatcher_impl.h +++ b/source/common/event/dispatcher_impl.h @@ -60,8 +60,8 @@ class DispatcherImpl : Logger::Loggable, Network::ListenerPtr createListener(Network::Socket& socket, Network::ListenerCallbacks& cb, bool bind_to_port, bool hand_off_restored_destination_connections) override; - Network::ListenerPtr createUdpListener(Network::Socket& socket, - Network::UdpListenerCallbacks& cb) override; + Network::UdpListenerPtr createUdpListener(Network::Socket& socket, + Network::UdpListenerCallbacks& cb) override; TimerPtr createTimer(TimerCb cb) override; void deferredDelete(DeferredDeletablePtr&& to_delete) override; void exit() override; diff --git a/source/extensions/quic_listeners/quiche/BUILD b/source/extensions/quic_listeners/quiche/BUILD index e3ad298bbcaa..38bf08411223 100644 --- a/source/extensions/quic_listeners/quiche/BUILD +++ b/source/extensions/quic_listeners/quiche/BUILD @@ -192,6 +192,37 @@ envoy_cc_library( hdrs = ["envoy_quic_simulated_watermark_buffer.h"], ) +envoy_cc_library( + name = "active_quic_listener_lib", + srcs = ["active_quic_listener.cc"], + hdrs = ["active_quic_listener.h"], + tags = ["nofips"], + deps = [ + ":envoy_quic_alarm_factory_lib", + ":envoy_quic_connection_helper_lib", + ":envoy_quic_dispatcher_lib", + ":envoy_quic_packet_writer_lib", + ":envoy_quic_proof_source_lib", + ":envoy_quic_utils_lib", + "//include/envoy/network:listener_interface", + "//source/common/network:listener_lib", + "//source/common/protobuf:utility_lib", + "//source/server:connection_handler_lib", + "@envoy_api//envoy/api/v2/listener:quic_config_cc", + ], +) + +envoy_cc_library( + name = "active_quic_listener_config_lib", + srcs = ["active_quic_listener_config.cc"], + hdrs = ["active_quic_listener_config.h"], + tags = ["nofips"], + deps = [ + ":active_quic_listener_lib", + "//include/envoy/registry", + ], +) + envoy_cc_library( name = "envoy_quic_utils_lib", srcs = ["envoy_quic_utils.cc"], diff --git a/source/extensions/quic_listeners/quiche/active_quic_listener.cc b/source/extensions/quic_listeners/quiche/active_quic_listener.cc new file mode 100644 index 000000000000..5f6b5ff71a43 --- /dev/null +++ b/source/extensions/quic_listeners/quiche/active_quic_listener.cc @@ -0,0 +1,84 @@ +#include "extensions/quic_listeners/quiche/active_quic_listener.h" + +#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_dispatcher.h" +#include "extensions/quic_listeners/quiche/envoy_quic_fake_proof_source.h" +#include "extensions/quic_listeners/quiche/envoy_quic_packet_writer.h" +#include "extensions/quic_listeners/quiche/envoy_quic_utils.h" + +namespace Envoy { +namespace Quic { + +ActiveQuicListener::ActiveQuicListener(Event::Dispatcher& dispatcher, + Network::ConnectionHandler& parent, spdlog::logger& logger, + Network::ListenerConfig& listener_config, + const quic::QuicConfig& quic_config) + : ActiveQuicListener(dispatcher, parent, + dispatcher.createUdpListener(listener_config.socket(), *this), logger, + listener_config, quic_config) {} + +ActiveQuicListener::ActiveQuicListener(Event::Dispatcher& dispatcher, + Network::ConnectionHandler& parent, + Network::UdpListenerPtr&& listener, spdlog::logger& logger, + Network::ListenerConfig& listener_config, + const quic::QuicConfig& quic_config) + : ActiveQuicListener(dispatcher, parent, std::make_unique(*listener), + std::move(listener), logger, listener_config, quic_config) {} + +ActiveQuicListener::ActiveQuicListener(Event::Dispatcher& dispatcher, + Network::ConnectionHandler& parent, + std::unique_ptr writer, + Network::UdpListenerPtr&& listener, spdlog::logger& logger, + Network::ListenerConfig& listener_config, + const quic::QuicConfig& quic_config) + : Server::ConnectionHandlerImpl::ActiveListenerImplBase(std::move(listener), listener_config), + logger_(logger), dispatcher_(dispatcher), version_manager_(quic::CurrentSupportedVersions()) { + quic::QuicRandom* const random = quic::QuicRandom::GetInstance(); + random->RandBytes(random_seed_, sizeof(random_seed_)); + crypto_config_ = std::make_unique( + quic::QuicStringPiece(reinterpret_cast(random_seed_), sizeof(random_seed_)), + quic::QuicRandom::GetInstance(), std::make_unique(), + quic::KeyExchangeSource::Default()); + auto connection_helper = std::make_unique(dispatcher_); + auto alarm_factory = + std::make_unique(dispatcher_, *connection_helper->GetClock()); + quic_dispatcher_ = std::make_unique( + crypto_config_.get(), quic_config, &version_manager_, std::move(connection_helper), + std::move(alarm_factory), quic::kQuicDefaultConnectionIdLength, parent, config_, stats_, + dispatcher); + quic_dispatcher_->InitializeWithWriter(writer.release()); +} + +void ActiveQuicListener::onListenerShutdown() { + ENVOY_LOG_TO_LOGGER(logger_, info, "Quic listener {} shutdown.", config_.name()); + quic_dispatcher_->Shutdown(); +} + +void ActiveQuicListener::onData(Network::UdpRecvData& data) { + quic::QuicSocketAddress peer_address(envoyAddressInstanceToQuicSocketAddress(data.peer_address_)); + quic::QuicSocketAddress self_address( + envoyAddressInstanceToQuicSocketAddress(data.local_address_)); + quic::QuicTime timestamp = + quic::QuicTime::Zero() + + quic::QuicTime::Delta::FromMilliseconds(std::chrono::duration_cast( + data.receive_time_.time_since_epoch()) + .count()); + uint64_t num_slice = data.buffer_->getRawSlices(nullptr, 0); + ASSERT(num_slice == 1); + Buffer::RawSlice slice; + 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, + /*packet_headers=*/nullptr, /*headers_length=*/0, + /*owns_header_buffer*/ false); + quic_dispatcher_->ProcessPacket(self_address, peer_address, packet); +} + +void ActiveQuicListener::onWriteReady(const Network::Socket& /*socket*/) { + quic_dispatcher_->OnCanWrite(); +} + +} // namespace Quic +} // namespace Envoy diff --git a/source/extensions/quic_listeners/quiche/active_quic_listener.h b/source/extensions/quic_listeners/quiche/active_quic_listener.h new file mode 100644 index 000000000000..bb327d12fd60 --- /dev/null +++ b/source/extensions/quic_listeners/quiche/active_quic_listener.h @@ -0,0 +1,107 @@ +#pragma once + +#include "envoy/api/v2/listener/quic_config.pb.h" +#include "envoy/network/connection_handler.h" +#include "envoy/network/listener.h" + +#include "common/protobuf/utility.h" + +#include "server/connection_handler_impl.h" + +#include "extensions/quic_listeners/quiche/envoy_quic_dispatcher.h" + +namespace Envoy { +namespace Quic { + +// QUIC specific UdpListenerCallbacks implemention which delegates incoming +// packets, write signals and listener errors to QuicDispatcher. +class ActiveQuicListener : public Network::UdpListenerCallbacks, + public Server::ConnectionHandlerImpl::ActiveListenerImplBase, + // Inherits below two interfaces just to have common + // interfaces. Not expected to support listener + // filter. + // TODO(danzh): clean up meaningless inheritance. + public Network::UdpListenerFilterManager, + public Network::UdpReadFilterCallbacks { +public: + ActiveQuicListener(Event::Dispatcher& dispatcher, Network::ConnectionHandler& parent, + spdlog::logger& logger, Network::ListenerConfig& listener_config, + const quic::QuicConfig& quic_config); + + ActiveQuicListener(Event::Dispatcher& dispatcher, Network::ConnectionHandler& parent, + Network::UdpListenerPtr&& listener, spdlog::logger& logger, + Network::ListenerConfig& listener_config, const quic::QuicConfig& quic_config); + + // TODO(#7465): Make this a callback. + void onListenerShutdown(); + + // Network::UdpListenerCallbacks + void onData(Network::UdpRecvData& data) override; + void onWriteReady(const Network::Socket& socket) override; + void onReceiveError(const Network::UdpListenerCallbacks::ErrorCode& /*error_code*/, + Api::IoError::IoErrorCode /*err*/) override { + // No-op. Quic can't do anything upon listener error. + } + + // Network::UdpListenerFilterManager + void addReadFilter(Network::UdpListenerReadFilterPtr&& /*filter*/) override { + // QUIC doesn't support listener filter. + NOT_REACHED_GCOVR_EXCL_LINE; + } + + // Network::UdpReadFilterCallbacks + Network::UdpListener& udpListener() override { NOT_REACHED_GCOVR_EXCL_LINE; } + +private: + friend class ActiveQuicListenerPeer; + + ActiveQuicListener(Event::Dispatcher& dispatcher, Network::ConnectionHandler& parent, + std::unique_ptr writer, + Network::UdpListenerPtr&& listener, spdlog::logger& logger, + Network::ListenerConfig& listener_config, const quic::QuicConfig& quic_config); + + uint8_t random_seed_[16]; + std::unique_ptr crypto_config_; + spdlog::logger& logger_; + Event::Dispatcher& dispatcher_; + quic::QuicVersionManager version_manager_; + std::unique_ptr quic_dispatcher_; +}; + +using ActiveQuicListenerPtr = std::unique_ptr; + +// A factory to create ActiveQuicListener based on given config. +class ActiveQuicListenerFactory : public Network::ActiveUdpListenerFactory { +public: + ActiveQuicListenerFactory(const envoy::api::v2::listener::QuicProtocolOptions& config) { + uint64_t idle_network_timeout_ms = + config.has_idle_timeout() ? DurationUtil::durationToMilliseconds(config.idle_timeout()) + : 300000; + quic_config_.SetIdleNetworkTimeout( + quic::QuicTime::Delta::FromMilliseconds(idle_network_timeout_ms), + quic::QuicTime::Delta::FromMilliseconds(idle_network_timeout_ms)); + int32_t max_time_before_crypto_handshake_ms = + config.has_crypto_handshake_timeout() + ? DurationUtil::durationToMilliseconds(config.crypto_handshake_timeout()) + : 20000; + quic_config_.set_max_time_before_crypto_handshake( + quic::QuicTime::Delta::FromMilliseconds(max_time_before_crypto_handshake_ms)); + int32_t max_streams = PROTOBUF_GET_WRAPPED_OR_DEFAULT(config, max_concurrent_streams, 100); + quic_config_.SetMaxIncomingBidirectionalStreamsToSend(max_streams); + quic_config_.SetMaxIncomingUnidirectionalStreamsToSend(max_streams); + } + + Network::ConnectionHandler::ActiveListenerPtr + createActiveUdpListener(Network::ConnectionHandler& parent, Event::Dispatcher& disptacher, + spdlog::logger& logger, Network::ListenerConfig& config) const override { + return std::make_unique(disptacher, parent, logger, config, quic_config_); + } + +private: + friend class ActiveQuicListenerFactoryPeer; + + quic::QuicConfig quic_config_; +}; + +} // namespace Quic +} // namespace Envoy diff --git a/source/extensions/quic_listeners/quiche/active_quic_listener_config.cc b/source/extensions/quic_listeners/quiche/active_quic_listener_config.cc new file mode 100644 index 000000000000..3cdadd8e0ae0 --- /dev/null +++ b/source/extensions/quic_listeners/quiche/active_quic_listener_config.cc @@ -0,0 +1,25 @@ +#include "extensions/quic_listeners/quiche/active_quic_listener_config.h" + +#include "envoy/api/v2/listener/quic_config.pb.h" + +#include "extensions/quic_listeners/quiche/active_quic_listener.h" + +namespace Envoy { +namespace Quic { + +ProtobufTypes::MessagePtr ActiveQuicListenerConfigFactory::createEmptyConfigProto() { + return std::make_unique(); +} + +Network::ActiveUdpListenerFactoryPtr +ActiveQuicListenerConfigFactory::createActiveUdpListenerFactory(const Protobuf::Message& message) { + auto& config = dynamic_cast(message); + return std::make_unique(config); +} + +std::string ActiveQuicListenerConfigFactory::name() { return QuicListenerName; } + +REGISTER_FACTORY(ActiveQuicListenerConfigFactory, Server::ActiveUdpListenerConfigFactory); + +} // namespace Quic +} // namespace Envoy diff --git a/source/extensions/quic_listeners/quiche/active_quic_listener_config.h b/source/extensions/quic_listeners/quiche/active_quic_listener_config.h new file mode 100644 index 000000000000..78d6e7bb88c0 --- /dev/null +++ b/source/extensions/quic_listeners/quiche/active_quic_listener_config.h @@ -0,0 +1,27 @@ +#pragma once + +#include + +#include "envoy/registry/registry.h" +#include "envoy/server/active_udp_listener_config.h" + +namespace Envoy { +namespace Quic { + +const std::string QuicListenerName{"quiche_quic_listener"}; + +// A factory to create ActiveQuicListenerFactory based on given protobuf. +class ActiveQuicListenerConfigFactory : public Server::ActiveUdpListenerConfigFactory { +public: + ProtobufTypes::MessagePtr createEmptyConfigProto() override; + + Network::ActiveUdpListenerFactoryPtr + createActiveUdpListenerFactory(const Protobuf::Message&) override; + + std::string name() override; +}; + +DECLARE_FACTORY(ActiveQuicListenerConfigFactory); + +} // 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 25f652f458ae..f9c503171032 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_dispatcher.cc +++ b/source/extensions/quic_listeners/quiche/envoy_quic_dispatcher.cc @@ -6,16 +6,18 @@ namespace Envoy { namespace Quic { EnvoyQuicDispatcher::EnvoyQuicDispatcher( - const quic::QuicCryptoServerConfig* crypto_config, quic::QuicVersionManager* version_manager, + const quic::QuicCryptoServerConfig* crypto_config, const quic::QuicConfig& quic_config, + quic::QuicVersionManager* version_manager, std::unique_ptr helper, std::unique_ptr alarm_factory, - uint8_t expected_server_connection_id_length, Server::ConnectionHandlerImpl& connection_handler, - Network::ListenerConfig& listener_config, Server::ListenerStats& listener_stats) - : quic::QuicDispatcher(&quic_config_, crypto_config, version_manager, std::move(helper), + uint8_t expected_server_connection_id_length, Network::ConnectionHandler& connection_handler, + Network::ListenerConfig& listener_config, Server::ListenerStats& listener_stats, + Event::Dispatcher& dispatcher) + : quic::QuicDispatcher(&quic_config, crypto_config, version_manager, std::move(helper), std::make_unique(), std::move(alarm_factory), expected_server_connection_id_length), connection_handler_(connection_handler), listener_config_(listener_config), - listener_stats_(listener_stats) { + listener_stats_(listener_stats), dispatcher_(dispatcher) { // Turn off chlo buffering in QuicDispatcher because per event loop clean // up is not implemented. // TODO(danzh): Add a per event loop callback to @@ -29,8 +31,7 @@ void EnvoyQuicDispatcher::OnConnectionClosed(quic::QuicConnectionId connection_i const std::string& error_details, quic::ConnectionCloseSource source) { quic::QuicDispatcher::OnConnectionClosed(connection_id, error, error_details, source); - ASSERT(connection_handler_.num_connections_ > 0); - --connection_handler_.num_connections_; + connection_handler_.decNumConnections(); } quic::QuicSession* EnvoyQuicDispatcher::CreateQuicSession( @@ -42,7 +43,7 @@ quic::QuicSession* EnvoyQuicDispatcher::CreateQuicSession( listener_config_, listener_stats_); auto quic_session = new EnvoyQuicServerSession( config(), quic::ParsedQuicVersionVector{version}, std::move(quic_connection), this, - session_helper(), crypto_config(), compressed_certs_cache(), connection_handler_.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 @@ -51,7 +52,7 @@ quic::QuicSession* EnvoyQuicDispatcher::CreateQuicSession( // known. In this way, filter chain can be retrieved at this point. But one // thing to pay attention is that if the retrival fails, connection needs to // be closed, and it should be added to time wait list instead of session map. - ++connection_handler_.num_connections_; + connection_handler_.incNumConnections(); return quic_session; } diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_dispatcher.h b/source/extensions/quic_listeners/quiche/envoy_quic_dispatcher.h index 5797ca544755..bdc580802632 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_dispatcher.h +++ b/source/extensions/quic_listeners/quiche/envoy_quic_dispatcher.h @@ -48,13 +48,14 @@ class EnvoyQuicCryptoServerStreamHelper : public quic::QuicCryptoServerStream::H class EnvoyQuicDispatcher : public quic::QuicDispatcher { public: EnvoyQuicDispatcher(const quic::QuicCryptoServerConfig* crypto_config, + const quic::QuicConfig& quic_config, quic::QuicVersionManager* version_manager, std::unique_ptr helper, std::unique_ptr alarm_factory, uint8_t expected_server_connection_id_length, - Server::ConnectionHandlerImpl& connection_handler, + Network::ConnectionHandler& connection_handler, Network::ListenerConfig& listener_config, - Server::ListenerStats& listener_stats); + Server::ListenerStats& listener_stats, Event::Dispatcher& dispatcher); void OnConnectionClosed(quic::QuicConnectionId connection_id, quic::QuicErrorCode error, const std::string& error_details, @@ -67,11 +68,10 @@ class EnvoyQuicDispatcher : public quic::QuicDispatcher { const quic::ParsedQuicVersion& version) override; private: - // TODO(danzh): initialize from Envoy config. - quic::QuicConfig quic_config_; - Server::ConnectionHandlerImpl& connection_handler_; + Network::ConnectionHandler& connection_handler_; Network::ListenerConfig& listener_config_; Server::ListenerStats& listener_stats_; + Event::Dispatcher& dispatcher_; }; } // namespace Quic diff --git a/source/server/active_raw_udp_listener_config.cc b/source/server/active_raw_udp_listener_config.cc index f60074dd4ea0..0d6cbb196cf7 100644 --- a/source/server/active_raw_udp_listener_config.cc +++ b/source/server/active_raw_udp_listener_config.cc @@ -12,6 +12,10 @@ Network::ConnectionHandler::ActiveListenerPtr ActiveRawUdpListenerFactory::creat return std::make_unique(dispatcher, config); } +ProtobufTypes::MessagePtr ActiveRawUdpListenerConfigFactory::createEmptyConfigProto() { + return std::make_unique(); +} + Network::ActiveUdpListenerFactoryPtr ActiveRawUdpListenerConfigFactory::createActiveUdpListenerFactory( const Protobuf::Message& /*message*/) { diff --git a/source/server/active_raw_udp_listener_config.h b/source/server/active_raw_udp_listener_config.h index ca4ce869fb56..aae07425a7d9 100644 --- a/source/server/active_raw_udp_listener_config.h +++ b/source/server/active_raw_udp_listener_config.h @@ -21,6 +21,8 @@ class ActiveRawUdpListenerFactory : public Network::ActiveUdpListenerFactory { // This is the default UDP listener if not specified in config. class ActiveRawUdpListenerConfigFactory : public ActiveUdpListenerConfigFactory { public: + ProtobufTypes::MessagePtr createEmptyConfigProto() override; + Network::ActiveUdpListenerFactoryPtr createActiveUdpListenerFactory(const Protobuf::Message&) override; diff --git a/source/server/connection_handler_impl.cc b/source/server/connection_handler_impl.cc index 4fc18b6f98e0..4701541581ac 100644 --- a/source/server/connection_handler_impl.cc +++ b/source/server/connection_handler_impl.cc @@ -17,6 +17,13 @@ namespace Server { ConnectionHandlerImpl::ConnectionHandlerImpl(spdlog::logger& logger, Event::Dispatcher& dispatcher) : logger_(logger), dispatcher_(dispatcher), disable_listeners_(false) {} +void ConnectionHandlerImpl::incNumConnections() { ++num_connections_; } + +void ConnectionHandlerImpl::decNumConnections() { + ASSERT(num_connections_ > 0); + --num_connections_; +} + void ConnectionHandlerImpl::addListener(Network::ListenerConfig& config) { Network::ConnectionHandler::ActiveListenerPtr listener; Network::Address::SocketType socket_type = config.socket().socketType(); diff --git a/source/server/connection_handler_impl.h b/source/server/connection_handler_impl.h index 7e09608098e6..ecf1946bf234 100644 --- a/source/server/connection_handler_impl.h +++ b/source/server/connection_handler_impl.h @@ -57,6 +57,8 @@ class ConnectionHandlerImpl : public Network::ConnectionHandler, NonCopyable { // Network::ConnectionHandler uint64_t numConnections() override { return num_connections_; } + void incNumConnections() override; + void decNumConnections() override; void addListener(Network::ListenerConfig& config) override; void removeListeners(uint64_t listener_tag) override; void stopListeners(uint64_t listener_tag) override; diff --git a/source/server/listener_manager_impl.cc b/source/server/listener_manager_impl.cc index 79e0204c6010..455324ffb10d 100644 --- a/source/server/listener_manager_impl.cc +++ b/source/server/listener_manager_impl.cc @@ -228,6 +228,7 @@ ListenerImpl::ListenerImpl(const envoy::api::v2::Listener& config, const std::st addListenSocketOptions(Network::SocketOptionFactory::buildIpPacketInfoOptions()); // Needed to return receive buffer overflown indicator. addListenSocketOptions(Network::SocketOptionFactory::buildRxQueueOverFlowOptions()); +<<<<<<< HEAD const auto& udp_config = config.has_udp_listener_config() ? config.udp_listener_config() : envoy::api::v2::listener::UdpListenerConfig(); @@ -239,6 +240,17 @@ ListenerImpl::ListenerImpl(const envoy::api::v2::Listener& config, const std::st .createActiveUdpListenerFactory(config.has_udp_listener_config() ? config.udp_listener_config() : envoy::api::v2::listener::UdpListenerConfig()); +======= + auto udp_config = config.udp_listener_config(); + if (udp_config.udp_listener_name().empty()) { + udp_config.set_udp_listener_name(UdpListenerNames::get().RawUdp); + } + auto& config_factory = Config::Utility::getAndCheckFactory( + udp_config.udp_listener_name()); + ProtobufTypes::MessagePtr message = + Config::Utility::translateToFactoryConfig(udp_config, validation_visitor_, config_factory); + udp_listener_factory_ = config_factory.createActiveUdpListenerFactory(*message); +>>>>>>> quiche: implement ActiveQuicListener (#7896) } if (!config.listener_filters().empty()) { diff --git a/test/extensions/quic_listeners/quiche/BUILD b/test/extensions/quic_listeners/quiche/BUILD index 781ef58744ca..e8779cc623b3 100644 --- a/test/extensions/quic_listeners/quiche/BUILD +++ b/test/extensions/quic_listeners/quiche/BUILD @@ -92,6 +92,22 @@ envoy_cc_test( ], ) +envoy_cc_test( + name = "active_quic_listener_test", + srcs = ["active_quic_listener_test.cc"], + tags = ["nofips"], + deps = [ + ":quic_test_utils_for_envoy_lib", + "//source/extensions/quic_listeners/quiche:active_quic_listener_lib", + "//source/extensions/quic_listeners/quiche:envoy_quic_utils_lib", + "//source/server:configuration_lib", + "//test/mocks/network:network_mocks", + "//test/test_common:environment_lib", + "//test/test_common:network_utility_lib", + "//test/test_common:simulated_time_system_lib", + ], +) + envoy_cc_test( name = "envoy_quic_dispatcher_test", srcs = ["envoy_quic_dispatcher_test.cc"], @@ -149,3 +165,15 @@ envoy_cc_test( "//test/test_common:threadsafe_singleton_injector_lib", ], ) + +envoy_cc_test( + name = "active_quic_listener_config_test", + srcs = ["active_quic_listener_config_test.cc"], + tags = ["nofips"], + deps = [ + "//source/common/config:utility_lib", + "//source/extensions/quic_listeners/quiche:active_quic_listener_config_lib", + "//test/test_common:utility_lib", + "@envoy_api//envoy/api/v2/listener:quic_config_cc", + ], +) diff --git a/test/extensions/quic_listeners/quiche/active_quic_listener_config_test.cc b/test/extensions/quic_listeners/quiche/active_quic_listener_config_test.cc new file mode 100644 index 000000000000..6f0c0e4696ec --- /dev/null +++ b/test/extensions/quic_listeners/quiche/active_quic_listener_config_test.cc @@ -0,0 +1,48 @@ +#include "envoy/api/v2/listener/quic_config.pb.h" + +#include "common/config/utility.h" + +#include "extensions/quic_listeners/quiche/active_quic_listener.h" +#include "extensions/quic_listeners/quiche/active_quic_listener_config.h" + +#include "test/test_common/utility.h" + +#include "gtest/gtest.h" + +namespace Envoy { +namespace Quic { + +class ActiveQuicListenerFactoryPeer { +public: + static quic::QuicConfig& quicConfig(ActiveQuicListenerFactory& factory) { + return factory.quic_config_; + } +}; + +TEST(ActiveQuicListenerConfigTest, CreateActiveQuicListenerFactory) { + std::string listener_name = QuicListenerName; + auto& config_factory = + Config::Utility::getAndCheckFactory(listener_name); + ProtobufTypes::MessagePtr config = config_factory.createEmptyConfigProto(); + + std::string yaml = R"EOF( + max_concurrent_streams: 10 + idle_timeout: { + seconds: 2 + } + )EOF"; + TestUtility::loadFromYaml(yaml, *config); + Network::ActiveUdpListenerFactoryPtr listener_factory = + config_factory.createActiveUdpListenerFactory(*config); + EXPECT_NE(nullptr, listener_factory); + quic::QuicConfig& quic_config = ActiveQuicListenerFactoryPeer::quicConfig( + dynamic_cast(*listener_factory)); + EXPECT_EQ(10u, quic_config.GetMaxIncomingBidirectionalStreamsToSend()); + EXPECT_EQ(10u, quic_config.GetMaxIncomingUnidirectionalStreamsToSend()); + EXPECT_EQ(2000u, quic_config.IdleNetworkTimeout().ToMilliseconds()); + // Default value if not present in config. + EXPECT_EQ(20000u, quic_config.max_time_before_crypto_handshake().ToMilliseconds()); +} + +} // namespace Quic +} // namespace Envoy diff --git a/test/extensions/quic_listeners/quiche/active_quic_listener_test.cc b/test/extensions/quic_listeners/quiche/active_quic_listener_test.cc new file mode 100644 index 000000000000..6c27bade7766 --- /dev/null +++ b/test/extensions/quic_listeners/quiche/active_quic_listener_test.cc @@ -0,0 +1,192 @@ +#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/crypto_protocol.h" +#include "quiche/quic/test_tools/crypto_test_utils.h" +#include "quiche/quic/test_tools/quic_test_utils.h" + +#pragma GCC diagnostic pop + +#include "server/configuration_impl.h" +#include "common/common/logger.h" +#include "common/network/listen_socket_impl.h" +#include "common/network/socket_option_factory.h" +#include "extensions/quic_listeners/quiche/active_quic_listener.h" +#include "test/test_common/simulated_time_system.h" +#include "test/test_common/environment.h" +#include "test/mocks/network/mocks.h" +#include "test/test_common/utility.h" +#include "test/test_common/network_utility.h" +#include "gtest/gtest.h" +#include "gmock/gmock.h" +#include "extensions/quic_listeners/quiche/platform/envoy_quic_clock.h" +#include "extensions/quic_listeners/quiche/envoy_quic_utils.h" + +using testing::Return; +using testing::ReturnRef; + +namespace Envoy { +namespace Quic { + +class ActiveQuicListenerPeer { +public: + static EnvoyQuicDispatcher* quic_dispatcher(ActiveQuicListener& listener) { + return listener.quic_dispatcher_.get(); + } + + static quic::QuicCryptoServerConfig& crypto_config(ActiveQuicListener& listener) { + return *listener.crypto_config_; + } +}; + +class ActiveQuicListenerTest : public testing::TestWithParam, + protected Logger::Loggable { +public: + ActiveQuicListenerTest() + : version_(GetParam()), api_(Api::createApiForTest(simulated_time_system_)), + dispatcher_(api_->allocateDispatcher()), read_filter_(new Network::MockReadFilter()), + filter_factory_({[this](Network::FilterManager& filter_manager) { + filter_manager.addReadFilter(read_filter_); + read_filter_->callbacks_->connection().addConnectionCallbacks( + network_connection_callbacks_); + }}), + connection_handler_(ENVOY_LOGGER(), *dispatcher_) { + EXPECT_CALL(listener_config_, listenerFiltersTimeout()); + EXPECT_CALL(listener_config_, continueOnListenerFiltersTimeout()); + EXPECT_CALL(listener_config_, listenerTag()); + } + + void SetUp() override { + listen_socket_ = std::make_unique>>( + Network::Test::getCanonicalLoopbackAddress(version_), nullptr, /*bind*/ true); + listen_socket_->addOptions(Network::SocketOptionFactory::buildIpPacketInfoOptions()); + listen_socket_->addOptions(Network::SocketOptionFactory::buildRxQueueOverFlowOptions()); + client_socket_ = std::make_unique>>( + Network::Test::getCanonicalLoopbackAddress(version_), nullptr, /*bind*/ false); + EXPECT_CALL(listener_config_, socket()).WillRepeatedly(ReturnRef(*listen_socket_)); + ON_CALL(listener_config_, filterChainManager()).WillByDefault(ReturnRef(filter_chain_manager_)); + ON_CALL(filter_chain_manager_, findFilterChain(_)).WillByDefault(Return(&filter_chain_)); + ON_CALL(filter_chain_, networkFilterFactories()).WillByDefault(ReturnRef(filter_factory_)); + ON_CALL(listener_config_.filter_chain_factory_, createNetworkFilterChain(_, _)) + .WillByDefault(Invoke([](Network::Connection& connection, + const std::vector& filter_factories) { + EXPECT_EQ(1u, filter_factories.size()); + Server::Configuration::FilterChainUtility::buildFilterChain(connection, filter_factories); + return true; + })); + + quic_listener_ = std::make_unique( + *dispatcher_, connection_handler_, ENVOY_LOGGER(), listener_config_, quic_config_); + simulated_time_system_.sleep(std::chrono::milliseconds(100)); + } + + void TearDown() override { + quic_listener_->onListenerShutdown(); + // Trigger alarm to fire before listener destruction. + dispatcher_->run(Event::Dispatcher::RunType::NonBlock); + } + +protected: + Network::Address::IpVersion version_; + Event::SimulatedTimeSystemHelper simulated_time_system_; + Api::ApiPtr api_; + Event::DispatcherPtr dispatcher_; + Network::SocketPtr listen_socket_; + Network::SocketPtr client_socket_; + std::shared_ptr read_filter_; + Network::MockConnectionCallbacks network_connection_callbacks_; + std::vector filter_factory_; + Network::MockFilterChain filter_chain_; + Network::MockFilterChainManager filter_chain_manager_; + NiceMock listener_config_; + quic::QuicConfig quic_config_; + Server::ConnectionHandlerImpl connection_handler_; + std::unique_ptr quic_listener_; +}; + +INSTANTIATE_TEST_SUITE_P(IpVersions, ActiveQuicListenerTest, + testing::ValuesIn(TestEnvironment::getIpVersionsForTest()), + TestUtility::ipTestParamsToString); + +TEST_P(ActiveQuicListenerTest, ReceiveFullQuicCHLO) { + quic::QuicConnectionId connection_id = quic::test::TestConnectionId(1); + EnvoyQuicClock clock(*dispatcher_); + quic::CryptoHandshakeMessage chlo = quic::test::crypto_test_utils::GenerateDefaultInchoateCHLO( + &clock, quic::AllSupportedVersions()[0].transport_version, + &ActiveQuicListenerPeer::crypto_config(*quic_listener_)); + chlo.SetVector(quic::kCOPT, quic::QuicTagVector{quic::kREJ}); + quic::CryptoHandshakeMessage full_chlo; + quic::QuicReferenceCountedPointer signed_config( + new quic::QuicSignedServerConfig); + quic::QuicCompressedCertsCache cache( + quic::QuicCompressedCertsCache::kQuicCompressedCertsCacheSize); + quic::test::crypto_test_utils::GenerateFullCHLO( + chlo, &ActiveQuicListenerPeer::crypto_config(*quic_listener_), + envoyAddressInstanceToQuicSocketAddress(listen_socket_->localAddress()), + envoyAddressInstanceToQuicSocketAddress(client_socket_->localAddress()), + quic::AllSupportedVersions()[0].transport_version, &clock, signed_config, &cache, &full_chlo); + // Overwrite version label to highest current supported version. + full_chlo.SetVersion(quic::kVER, quic::CurrentSupportedVersions()[0]); + quic::QuicConfig quic_config; + quic_config.ToHandshakeMessage(&full_chlo, quic::CurrentSupportedVersions()[0].transport_version); + + std::string packet_content(full_chlo.GetSerialized().AsStringPiece()); + auto encrypted_packet = + std::unique_ptr(quic::test::ConstructEncryptedPacket( + connection_id, quic::EmptyQuicConnectionId(), /*version_flag=*/true, /*reset_flag*/ false, + /*packet_number=*/1, packet_content)); + + Buffer::RawSlice first_slice{reinterpret_cast(const_cast(encrypted_packet->data())), + encrypted_packet->length()}; + // Send a full CHLO to finish 0-RTT handshake. + auto send_rc = + client_socket_->ioHandle().sendto(first_slice, /*flags=*/0, *listen_socket_->localAddress()); + ASSERT_EQ(encrypted_packet->length(), send_rc.rc_); + + EXPECT_CALL(listener_config_, filterChainManager()); + EXPECT_CALL(filter_chain_manager_, findFilterChain(_)); + EXPECT_CALL(filter_chain_, networkFilterFactories()); + EXPECT_CALL(listener_config_, filterChainFactory()); + EXPECT_CALL(listener_config_.filter_chain_factory_, createNetworkFilterChain(_, _)); + EXPECT_CALL(*read_filter_, onNewConnection()) + // Stop iteration to avoid calling getRead/WriteBuffer(). + .WillOnce(Invoke([this]() { + dispatcher_->exit(); + return Network::FilterStatus::StopIteration; + })); + + dispatcher_->run(Event::Dispatcher::RunType::Block); + + Buffer::InstancePtr result_buffer(new Buffer::OwnedImpl()); + const uint64_t bytes_to_read = 11; + uint64_t bytes_read = 0; + int retry = 0; + + do { + Api::IoCallUint64Result result = + result_buffer->read(client_socket_->ioHandle(), bytes_to_read - bytes_read); + + if (result.ok()) { + bytes_read += result.rc_; + } else if (retry == 10 || result.err_->getErrorCode() != Api::IoError::IoErrorCode::Again) { + break; + } + + if (bytes_read == bytes_to_read) { + break; + } + + retry++; + ::usleep(10000); + } while (true); + // TearDown() will close the connection. + EXPECT_CALL(network_connection_callbacks_, onEvent(Network::ConnectionEvent::LocalClose)); +} + +} // namespace Quic +} // namespace Envoy 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 17c70226899e..c39108fdbeb5 100644 --- a/test/extensions/quic_listeners/quiche/envoy_quic_dispatcher_test.cc +++ b/test/extensions/quic_listeners/quiche/envoy_quic_dispatcher_test.cc @@ -66,11 +66,11 @@ class EnvoyQuicDispatcherTest : public testing::TestWithParam(*dispatcher_), std::make_unique(*dispatcher_, *connection_helper_.GetClock()), quic::kQuicDefaultConnectionIdLength, connection_handler_, listener_config_, - listener_stats_) { + listener_stats_, *dispatcher_) { auto writer = new testing::NiceMock(); envoy_quic_dispatcher_.InitializeWithWriter(writer); EXPECT_CALL(*writer, WritePacket(_, _, _, _, _)) @@ -132,6 +132,7 @@ class EnvoyQuicDispatcherTest : public testing::TestWithParam listener_config_; diff --git a/test/mocks/event/mocks.h b/test/mocks/event/mocks.h index 81e133dd50d1..b52d8a378c30 100644 --- a/test/mocks/event/mocks.h +++ b/test/mocks/event/mocks.h @@ -65,9 +65,9 @@ class MockDispatcher : public Dispatcher { createListener_(socket, cb, bind_to_port, hand_off_restored_destination_connections)}; } - Network::ListenerPtr createUdpListener(Network::Socket& socket, - Network::UdpListenerCallbacks& cb) override { - return Network::ListenerPtr{createUdpListener_(socket, cb)}; + Network::UdpListenerPtr createUdpListener(Network::Socket& socket, + Network::UdpListenerCallbacks& cb) override { + return Network::UdpListenerPtr{createUdpListener_(socket, cb)}; } Event::TimerPtr createTimer(Event::TimerCb cb) override { @@ -108,7 +108,7 @@ class MockDispatcher : public Dispatcher { bool bind_to_port, bool hand_off_restored_destination_connections)); MOCK_METHOD2(createUdpListener_, - Network::Listener*(Network::Socket& socket, Network::UdpListenerCallbacks& cb)); + Network::UdpListener*(Network::Socket& socket, Network::UdpListenerCallbacks& cb)); MOCK_METHOD1(createTimer_, Timer*(Event::TimerCb cb)); MOCK_METHOD1(deferredDelete_, void(DeferredDeletable* to_delete)); MOCK_METHOD0(exit, void()); diff --git a/test/mocks/network/mocks.h b/test/mocks/network/mocks.h index ee7551960bf5..77d2c37d74c6 100644 --- a/test/mocks/network/mocks.h +++ b/test/mocks/network/mocks.h @@ -335,6 +335,8 @@ class MockConnectionHandler : public ConnectionHandler { ~MockConnectionHandler() override; MOCK_METHOD0(numConnections, uint64_t()); + MOCK_METHOD0(incNumConnections, void()); + MOCK_METHOD0(decNumConnections, void()); MOCK_METHOD1(addListener, void(ListenerConfig& config)); MOCK_METHOD1(findListenerByAddress, Network::Listener*(const Network::Address::Instance& address)); diff --git a/test/server/BUILD b/test/server/BUILD index 821e257bd2d7..161688205c06 100644 --- a/test/server/BUILD +++ b/test/server/BUILD @@ -162,11 +162,25 @@ envoy_cc_test( ], ) +envoy_cc_test_library( + name = "listener_manager_impl_test_lib", + hdrs = ["listener_manager_impl_test.h"], + deps = [ + "//source/server:listener_manager_lib", + "//test/mocks/network:network_mocks", + "//test/mocks/server:server_mocks", + "//test/test_common:environment_lib", + "//test/test_common:simulated_time_system_lib", + "//test/test_common:test_time_lib", + ], +) + envoy_cc_test( name = "listener_manager_impl_test", srcs = ["listener_manager_impl_test.cc"], data = ["//test/extensions/transport_sockets/tls/test_data:certs"], deps = [ + ":listener_manager_impl_test_lib", ":utility_lib", "//source/common/api:os_sys_calls_lib", "//source/common/config:metadata_lib", @@ -183,13 +197,20 @@ envoy_cc_test( "//source/extensions/transport_sockets/tls:config", "//source/extensions/transport_sockets/tls:ssl_socket_lib", "//source/server:active_raw_udp_listener_config", - "//source/server:listener_manager_lib", - "//test/mocks/network:network_mocks", - "//test/mocks/server:server_mocks", - "//test/test_common:environment_lib", "//test/test_common:registry_lib", - "//test/test_common:simulated_time_system_lib", - "//test/test_common:test_time_lib", + "//test/test_common:threadsafe_singleton_injector_lib", + ], +) + +# Stand-alone quic test because of FIPS. +envoy_cc_test( + name = "listener_manager_impl_quic_only_test", + srcs = ["listener_manager_impl_quic_only_test.cc"], + tags = ["nofips"], + deps = [ + ":listener_manager_impl_test_lib", + "//source/extensions/quic_listeners/quiche:active_quic_listener_config_lib", + "//source/extensions/transport_sockets/raw_buffer:config", "//test/test_common:threadsafe_singleton_injector_lib", ], ) diff --git a/test/server/connection_handler_test.cc b/test/server/connection_handler_test.cc index eb53c6261cfa..a6dadb2b9e54 100644 --- a/test/server/connection_handler_test.cc +++ b/test/server/connection_handler_test.cc @@ -819,9 +819,10 @@ TEST_F(ConnectionHandlerTest, UdpListenerNoFilterThrowsException) { std::chrono::milliseconds()); Network::MockUdpListener* listener = new Network::MockUdpListener(); EXPECT_CALL(dispatcher_, createUdpListener_(_, _)) - .WillOnce(Invoke([&](Network::Socket&, Network::UdpListenerCallbacks&) -> Network::Listener* { - return listener; - })); + .WillOnce( + Invoke([&](Network::Socket&, Network::UdpListenerCallbacks&) -> Network::UdpListener* { + return listener; + })); EXPECT_CALL(factory_, createUdpListenerFilterChain(_, _)) .WillOnce(Invoke([&](Network::UdpListenerFilterManager&, Network::UdpReadFilterCallbacks&) -> bool { return true; })); diff --git a/test/server/listener_manager_impl_quic_only_test.cc b/test/server/listener_manager_impl_quic_only_test.cc new file mode 100644 index 000000000000..ad0f08d1a8dd --- /dev/null +++ b/test/server/listener_manager_impl_quic_only_test.cc @@ -0,0 +1,46 @@ +#include "test/server/listener_manager_impl_test.h" +#include "test/test_common/threadsafe_singleton_injector.h" + +using testing::AtLeast; + +namespace Envoy { +namespace Server { +namespace { + +class ListenerManagerImplQuicOnlyTest : public ListenerManagerImplTest { +protected: + NiceMock os_sys_calls_; + TestThreadsafeSingletonInjector os_calls_{&os_sys_calls_}; +}; + +TEST_F(ListenerManagerImplQuicOnlyTest, QuicListenerFactory) { + const std::string proto_text = R"EOF( +address: { + socket_address: { + protocol: UDP + address: "127.0.0.1" + port_value: 1234 + } +} +filter_chains: {} +udp_listener_config: { + udp_listener_name: "quiche_quic_listener" + config: {} +} + )EOF"; + envoy::api::v2::Listener listener_proto; + EXPECT_TRUE(Protobuf::TextFormat::ParseFromString(proto_text, &listener_proto)); + + EXPECT_CALL(server_.random_, uuid()); + EXPECT_CALL(listener_factory_, + createListenSocket(_, Network::Address::SocketType::Datagram, _, true)); + EXPECT_CALL(os_sys_calls_, setsockopt_(_, _, _, _, _)).Times(testing::AtLeast(1)); + EXPECT_CALL(os_sys_calls_, close(_)).WillRepeatedly(Return(Api::SysCallIntResult{0, errno})); + manager_->addOrUpdateListener(listener_proto, "", true); + EXPECT_EQ(1u, manager_->listeners().size()); + EXPECT_NE(nullptr, manager_->listeners()[0].get().udpListenerFactory()); +} + +} // namespace +} // namespace Server +} // namespace Envoy diff --git a/test/server/listener_manager_impl_test.cc b/test/server/listener_manager_impl_test.cc index 791aee6c2dce..1b3b27645463 100644 --- a/test/server/listener_manager_impl_test.cc +++ b/test/server/listener_manager_impl_test.cc @@ -1,10 +1,11 @@ +#include "test/server/listener_manager_impl_test.h" + #include #include #include #include #include -#include "envoy/admin/v2alpha/config_dump.pb.h" #include "envoy/registry/registry.h" #include "envoy/server/filter_config.h" @@ -17,134 +18,25 @@ #include "common/network/utility.h" #include "common/protobuf/protobuf.h" -#include "server/configuration_impl.h" -#include "server/listener_manager_impl.h" - #include "extensions/filters/listener/original_dst/original_dst.h" #include "extensions/transport_sockets/tls/ssl_socket.h" -#include "test/mocks/network/mocks.h" -#include "test/mocks/server/mocks.h" #include "test/server/utility.h" -#include "test/test_common/environment.h" #include "test/test_common/registry.h" -#include "test/test_common/simulated_time_system.h" #include "test/test_common/threadsafe_singleton_injector.h" #include "test/test_common/utility.h" #include "absl/strings/escaping.h" #include "absl/strings/match.h" -#include "gtest/gtest.h" -using testing::_; using testing::AtLeast; using testing::InSequence; -using testing::Invoke; -using testing::NiceMock; -using testing::Return; -using testing::ReturnRef; using testing::Throw; namespace Envoy { namespace Server { namespace { -class ListenerHandle { -public: - ListenerHandle() { EXPECT_CALL(*drain_manager_, startParentShutdownSequence()).Times(0); } - ~ListenerHandle() { onDestroy(); } - - MOCK_METHOD0(onDestroy, void()); - - Init::ExpectableTargetImpl target_; - MockDrainManager* drain_manager_ = new MockDrainManager(); - Configuration::FactoryContext* context_{}; -}; - -class ListenerManagerImplTest : public testing::Test { -protected: - ListenerManagerImplTest() : api_(Api::createApiForTest()) { - ON_CALL(server_, api()).WillByDefault(ReturnRef(*api_)); - EXPECT_CALL(worker_factory_, createWorker_()).WillOnce(Return(worker_)); - manager_ = - std::make_unique(server_, listener_factory_, worker_factory_, false); - } - - /** - * This routing sets up an expectation that does various things: - * 1) Allows us to track listener destruction via filter factory destruction. - * 2) Allows us to register for init manager handling much like RDS, etc. would do. - * 3) Stores the factory context for later use. - * 4) Creates a mock local drain manager for the listener. - */ - ListenerHandle* expectListenerCreate( - bool need_init, bool added_via_api, - envoy::api::v2::Listener::DrainType drain_type = envoy::api::v2::Listener_DrainType_DEFAULT) { - if (added_via_api) { - EXPECT_CALL(server_.validation_context_, staticValidationVisitor()).Times(0); - EXPECT_CALL(server_.validation_context_, dynamicValidationVisitor()); - } else { - EXPECT_CALL(server_.validation_context_, staticValidationVisitor()); - EXPECT_CALL(server_.validation_context_, dynamicValidationVisitor()).Times(0); - } - ListenerHandle* raw_listener = new ListenerHandle(); - EXPECT_CALL(listener_factory_, createDrainManager_(drain_type)) - .WillOnce(Return(raw_listener->drain_manager_)); - EXPECT_CALL(listener_factory_, createNetworkFilterFactoryList(_, _)) - .WillOnce(Invoke( - [raw_listener, need_init]( - const Protobuf::RepeatedPtrField&, - Configuration::FactoryContext& context) -> std::vector { - std::shared_ptr notifier(raw_listener); - raw_listener->context_ = &context; - if (need_init) { - context.initManager().add(notifier->target_); - } - return {[notifier](Network::FilterManager&) -> void {}}; - })); - - return raw_listener; - } - - void checkStats(uint64_t added, uint64_t modified, uint64_t removed, uint64_t warming, - uint64_t active, uint64_t draining) { - EXPECT_EQ(added, server_.stats_store_.counter("listener_manager.listener_added").value()); - EXPECT_EQ(modified, server_.stats_store_.counter("listener_manager.listener_modified").value()); - EXPECT_EQ(removed, server_.stats_store_.counter("listener_manager.listener_removed").value()); - EXPECT_EQ(warming, server_.stats_store_ - .gauge("listener_manager.total_listeners_warming", - Stats::Gauge::ImportMode::NeverImport) - .value()); - EXPECT_EQ(active, server_.stats_store_ - .gauge("listener_manager.total_listeners_active", - Stats::Gauge::ImportMode::NeverImport) - .value()); - EXPECT_EQ(draining, server_.stats_store_ - .gauge("listener_manager.total_listeners_draining", - Stats::Gauge::ImportMode::NeverImport) - .value()); - } - - void checkConfigDump(const std::string& expected_dump_yaml) { - auto message_ptr = server_.admin_.config_tracker_.config_tracker_callbacks_["listeners"](); - const auto& listeners_config_dump = - dynamic_cast(*message_ptr); - - envoy::admin::v2alpha::ListenersConfigDump expected_listeners_config_dump; - TestUtility::loadFromYaml(expected_dump_yaml, expected_listeners_config_dump); - EXPECT_EQ(expected_listeners_config_dump.DebugString(), listeners_config_dump.DebugString()); - } - - NiceMock server_; - NiceMock listener_factory_; - MockWorker* worker_ = new MockWorker(); - NiceMock worker_factory_; - std::unique_ptr manager_; - NiceMock guard_dog_; - Event::SimulatedTimeSystem time_system_; - Api::ApiPtr api_; -}; - class ListenerManagerImplWithRealFiltersTest : public ListenerManagerImplTest { public: ListenerManagerImplWithRealFiltersTest() { diff --git a/test/server/listener_manager_impl_test.h b/test/server/listener_manager_impl_test.h new file mode 100644 index 000000000000..698982d93a7f --- /dev/null +++ b/test/server/listener_manager_impl_test.h @@ -0,0 +1,119 @@ +#include "envoy/admin/v2alpha/config_dump.pb.h" + +#include "server/configuration_impl.h" +#include "server/listener_manager_impl.h" + +#include "test/mocks/network/mocks.h" +#include "test/mocks/server/mocks.h" +#include "test/test_common/environment.h" +#include "test/test_common/simulated_time_system.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +using testing::_; +using testing::NiceMock; +using testing::Return; +using testing::ReturnRef; + +namespace Envoy { +namespace Server { + +class ListenerHandle { +public: + ListenerHandle() { EXPECT_CALL(*drain_manager_, startParentShutdownSequence()).Times(0); } + ~ListenerHandle() { onDestroy(); } + + MOCK_METHOD0(onDestroy, void()); + + Init::ExpectableTargetImpl target_; + MockDrainManager* drain_manager_ = new MockDrainManager(); + Configuration::FactoryContext* context_{}; +}; + +class ListenerManagerImplTest : public testing::Test { +protected: + ListenerManagerImplTest() : api_(Api::createApiForTest()) { + ON_CALL(server_, api()).WillByDefault(ReturnRef(*api_)); + EXPECT_CALL(worker_factory_, createWorker_()).WillOnce(Return(worker_)); + manager_ = + std::make_unique(server_, listener_factory_, worker_factory_, false); + } + + /** + * This routing sets up an expectation that does various things: + * 1) Allows us to track listener destruction via filter factory destruction. + * 2) Allows us to register for init manager handling much like RDS, etc. would do. + * 3) Stores the factory context for later use. + * 4) Creates a mock local drain manager for the listener. + */ + ListenerHandle* expectListenerCreate( + bool need_init, bool added_via_api, + envoy::api::v2::Listener::DrainType drain_type = envoy::api::v2::Listener_DrainType_DEFAULT) { + if (added_via_api) { + EXPECT_CALL(server_.validation_context_, staticValidationVisitor()).Times(0); + EXPECT_CALL(server_.validation_context_, dynamicValidationVisitor()); + } else { + EXPECT_CALL(server_.validation_context_, staticValidationVisitor()); + EXPECT_CALL(server_.validation_context_, dynamicValidationVisitor()).Times(0); + } + auto raw_listener = new ListenerHandle(); + EXPECT_CALL(listener_factory_, createDrainManager_(drain_type)) + .WillOnce(Return(raw_listener->drain_manager_)); + EXPECT_CALL(listener_factory_, createNetworkFilterFactoryList(_, _)) + .WillOnce(Invoke( + [raw_listener, need_init]( + const Protobuf::RepeatedPtrField&, + Configuration::FactoryContext& context) -> std::vector { + std::shared_ptr notifier(raw_listener); + raw_listener->context_ = &context; + if (need_init) { + context.initManager().add(notifier->target_); + } + return {[notifier](Network::FilterManager&) -> void {}}; + })); + + return raw_listener; + } + + void checkStats(uint64_t added, uint64_t modified, uint64_t removed, uint64_t warming, + uint64_t active, uint64_t draining) { + EXPECT_EQ(added, server_.stats_store_.counter("listener_manager.listener_added").value()); + EXPECT_EQ(modified, server_.stats_store_.counter("listener_manager.listener_modified").value()); + EXPECT_EQ(removed, server_.stats_store_.counter("listener_manager.listener_removed").value()); + EXPECT_EQ(warming, server_.stats_store_ + .gauge("listener_manager.total_listeners_warming", + Stats::Gauge::ImportMode::NeverImport) + .value()); + EXPECT_EQ(active, server_.stats_store_ + .gauge("listener_manager.total_listeners_active", + Stats::Gauge::ImportMode::NeverImport) + .value()); + EXPECT_EQ(draining, server_.stats_store_ + .gauge("listener_manager.total_listeners_draining", + Stats::Gauge::ImportMode::NeverImport) + .value()); + } + + void checkConfigDump(const std::string& expected_dump_yaml) { + auto message_ptr = server_.admin_.config_tracker_.config_tracker_callbacks_["listeners"](); + const auto& listeners_config_dump = + dynamic_cast(*message_ptr); + + envoy::admin::v2alpha::ListenersConfigDump expected_listeners_config_dump; + TestUtility::loadFromYaml(expected_dump_yaml, expected_listeners_config_dump); + EXPECT_EQ(expected_listeners_config_dump.DebugString(), listeners_config_dump.DebugString()); + } + + NiceMock server_; + NiceMock listener_factory_; + MockWorker* worker_ = new MockWorker(); + NiceMock worker_factory_; + std::unique_ptr manager_; + NiceMock guard_dog_; + Event::SimulatedTimeSystem time_system_; + Api::ApiPtr api_; +}; + +} // namespace Server +} // namespace Envoy