diff --git a/api/envoy/api/v2/lds.proto b/api/envoy/api/v2/lds.proto index d05e37e5b080a..55bd14579abf6 100644 --- a/api/envoy/api/v2/lds.proto +++ b/api/envoy/api/v2/lds.proto @@ -131,6 +131,12 @@ message Listener { // timeout. If not specified, a default timeout of 15s is used. google.protobuf.Duration listener_filters_timeout = 15 [(gogoproto.stdduration) = true]; + // [#not-implemented-hide:] + // QUIC listener for handing UDP packets. Only used when :ref:`address + // ` specifies a UDP :ref:`SocketAddress + // `. + listener.QuicListener quic_listener = 16; + // Whether the listener should be set as a transparent socket. // When this flag is set to true, connections can be redirected to the listener using an // *iptables* *TPROXY* target, in which case the original source and destination addresses and diff --git a/api/envoy/api/v2/listener/listener.proto b/api/envoy/api/v2/listener/listener.proto index 0d617ceacf5ff..3816dcd7e47cf 100644 --- a/api/envoy/api/v2/listener/listener.proto +++ b/api/envoy/api/v2/listener/listener.proto @@ -215,3 +215,22 @@ message ListenerFilter { google.protobuf.Any typed_config = 3; } } + +// [#proto-status: experimental] +message QuicListener { + // The name of the QUIC listener to instantiate. The name must match a + // supported listener. Future listeners may include: + // + // * envoy.quic.quiche + // * envoy.quic.ngtcp2 + string name = 1 [(validate.rules).string.min_bytes = 1]; + + // Filter specific configuration which depends on the filter being instantiated. + // See the supported filters for further documentation. + oneof config_type { + google.protobuf.Struct config = 2; + + // [#not-implemented-hide:] + google.protobuf.Any typed_config = 3; + } +} diff --git a/api/envoy/config/filter/network/http_connection_manager/v2/BUILD b/api/envoy/config/filter/network/http_connection_manager/v2/BUILD index c89ea09ad2909..fc403cdc43b6d 100644 --- a/api/envoy/config/filter/network/http_connection_manager/v2/BUILD +++ b/api/envoy/config/filter/network/http_connection_manager/v2/BUILD @@ -5,6 +5,7 @@ licenses(["notice"]) # Apache 2 api_proto_library_internal( name = "http_connection_manager", srcs = ["http_connection_manager.proto"], + visibility = ["//envoy/config/quic_listener/quiche/v2alpha:__pkg__"], deps = [ "//envoy/api/v2:rds", "//envoy/api/v2/core:base", diff --git a/api/envoy/config/quic_listener/quiche/v2alpha/BUILD b/api/envoy/config/quic_listener/quiche/v2alpha/BUILD new file mode 100644 index 0000000000000..f3e946611b37b --- /dev/null +++ b/api/envoy/config/quic_listener/quiche/v2alpha/BUILD @@ -0,0 +1,9 @@ +load("//bazel:api_build_system.bzl", "api_proto_library") + +licenses(["notice"]) # Apache 2 + +api_proto_library( + name = "quiche", + srcs = ["quiche.proto"], + deps = ["//envoy/config/filter/network/http_connection_manager/v2:http_connection_manager"], +) diff --git a/api/envoy/config/quic_listener/quiche/v2alpha/quiche.proto b/api/envoy/config/quic_listener/quiche/v2alpha/quiche.proto new file mode 100644 index 0000000000000..42bc0e915b5bc --- /dev/null +++ b/api/envoy/config/quic_listener/quiche/v2alpha/quiche.proto @@ -0,0 +1,27 @@ +syntax = "proto3"; + +package envoy.config.quic_listener.quiche.v2alpha; +option java_package = "io.envoyproxy.envoy.config.quic_listener.quiche.v2alpha"; +option java_multiple_files = true; +option go_package = "v2"; + +import "envoy/config/filter/network/http_connection_manager/v2/http_connection_manager.proto"; + +import "validate/validate.proto"; +import "gogoproto/gogo.proto"; + +// [#protodoc-title: QUICHE] + +// Configuration for QUICHE-based QUIC listener. This will provide QUIC support to Envoy. +// https://quiche.googlesource.com/quiche/ +message Quiche { + + // HTTP-level configuration for the QUIC listener. In particular, HTTP filters to be applied to + // QUIC traffic should be specified in the nested :ref:`HttpConnectionManager + // ` config + // protobuf. + envoy.config.filter.network.http_connection_manager.v2.HttpConnectionManager http_config = 1 + [(validate.rules).message.required = true, (gogoproto.nullable) = false]; + + // QUIC-specific config will be added as fields here. +} diff --git a/include/envoy/network/BUILD b/include/envoy/network/BUILD index 548e8af481643..de6cd23d203ca 100644 --- a/include/envoy/network/BUILD +++ b/include/envoy/network/BUILD @@ -85,6 +85,7 @@ envoy_cc_library( deps = [ ":connection_interface", ":listen_socket_interface", + "//include/envoy/quic:listener_interface", "//include/envoy/stats:stats_interface", ], ) diff --git a/include/envoy/network/listener.h b/include/envoy/network/listener.h index ae34c3151117f..daa2b5ed14267 100644 --- a/include/envoy/network/listener.h +++ b/include/envoy/network/listener.h @@ -7,6 +7,7 @@ #include "envoy/common/exception.h" #include "envoy/network/connection.h" #include "envoy/network/listen_socket.h" +#include "envoy/quic/listener.h" #include "envoy/stats/scope.h" namespace Envoy { @@ -31,6 +32,12 @@ class ListenerConfig { */ virtual FilterChainFactory& filterChainFactory() PURE; + /** + * @return QuicListenerFactory* the factory for creating QUIC listeners, or nullptr if none is + * configured. + */ + virtual Quic::QuicListenerFactory* quicListenerFactory() PURE; + /** * @return Socket& the actual listen socket. The address of this socket may be * different from configured if for example the configured address binds to port zero. diff --git a/include/envoy/quic/BUILD b/include/envoy/quic/BUILD new file mode 100644 index 0000000000000..df1bca6c570d3 --- /dev/null +++ b/include/envoy/quic/BUILD @@ -0,0 +1,27 @@ +licenses(["notice"]) # Apache 2 + +load( + "//bazel:envoy_build_system.bzl", + "envoy_cc_library", + "envoy_package", +) + +envoy_package() + +envoy_cc_library( + name = "config_interface", + hdrs = ["config.h"], + deps = [ + ":listener_interface", + "//include/envoy/server:filter_config_interface", + "//source/common/protobuf", + ], +) + +envoy_cc_library( + name = "listener_interface", + hdrs = ["listener.h"], + deps = [ + "//include/envoy/network:listen_socket_interface", + ], +) diff --git a/include/envoy/quic/config.h b/include/envoy/quic/config.h new file mode 100644 index 0000000000000..5df7b780416b7 --- /dev/null +++ b/include/envoy/quic/config.h @@ -0,0 +1,44 @@ +#pragma once + +#include + +#include "envoy/quic/listener.h" +#include "envoy/server/filter_config.h" + +#include "common/protobuf/protobuf.h" + +namespace Envoy { +namespace Quic { + +/** + * Implemented by the QUIC listener and registered via RegisterFactory. + */ +class QuicListenerConfigFactory { +public: + virtual ~QuicListenerConfigFactory() {} + + /** + * Create a particular QUIC listener factory implementation. If the implementation is unable to + * produce a factory with the provided parameters, it should throw an EnvoyException. + * @param config the protobuf configuration for the listener. + * @param context the listener's context. + */ + virtual QuicListenerFactoryPtr + createListenerFactoryFromProto(const Protobuf::Message& config, + Server::Configuration::ListenerFactoryContext& context) PURE; + + /** + * @return ProtobufTypes::MessagePtr create empty config proto message. The QUIC listener + * config, which arrives in an opaque message, will be parsed into this empty proto. + */ + virtual ProtobufTypes::MessagePtr createEmptyConfigProto() PURE; + + /** + * @return std::string the identifying name for a particular implementation of a QUIC listener + * produced by the factory. + */ + virtual std::string name() PURE; +}; + +} // namespace Quic +} // namespace Envoy diff --git a/include/envoy/quic/listener.h b/include/envoy/quic/listener.h new file mode 100644 index 0000000000000..b254a6f66be71 --- /dev/null +++ b/include/envoy/quic/listener.h @@ -0,0 +1,78 @@ +#pragma once + +#include + +#include "envoy/network/listen_socket.h" + +namespace Envoy { +namespace Event { +class Dispatcher; +} + +namespace Quic { + +/** + * Callbacks used by QuicListener instances to communicate with their execution + * environment. + */ +class QuicListenerCallbacks { +public: + virtual ~QuicListenerCallbacks() {} + + /** + * @return the socket via which the listener can send and receive datagrams. + */ + virtual Network::Socket& socket() PURE; + + /** + * @return the dispatcher via which the listener can register for events. + */ + virtual Event::Dispatcher& dispatcher() PURE; +}; + +/** + * Abstract interface for QUIC listeners. + */ +// Ideally, this class would extend Network::Listener, or Network::Listener +// could even be used directly instead of it. However, as currently stands, this +// would introduce a circular dependency, since Network::ListenerConfig (which +// refers to QuicListenerFactory) and Network::Listener (to which +// QuicListenerFactory would refer) are defined in the same build target +// (//include/envoy/network:listener_interface). +class QuicListener { +public: + virtual ~QuicListener() {} + + /** + * Temporarily disable accepting new connections. + */ + virtual void disable() PURE; + + /** + * Enable accepting new connections. + */ + virtual void enable() PURE; +}; + +typedef std::unique_ptr QuicListenerPtr; + +/** + * Factory for creating QUIC listeners. + */ +class QuicListenerFactory { +public: + virtual ~QuicListenerFactory() {} + + /** + * Create a QUIC listener. The listener is responsible for registering for + * socket events as needed; both socket and dispatcher are accessible via the + * callbacks provided. + * @return the newly created listener. + */ + virtual QuicListenerPtr createQuicListener(QuicListenerCallbacks& callbacks) PURE; +}; + +typedef std::unique_ptr QuicListenerFactoryPtr; + +} // namespace Quic +} // namespace Envoy diff --git a/include/envoy/server/BUILD b/include/envoy/server/BUILD index bbf95b4a96149..6b3cd744c22ab 100644 --- a/include/envoy/server/BUILD +++ b/include/envoy/server/BUILD @@ -174,6 +174,7 @@ envoy_cc_library( ":guarddog_interface", "//include/envoy/network:filter_interface", "//include/envoy/network:listen_socket_interface", + "//include/envoy/quic:listener_interface", "//include/envoy/ssl:context_interface", "//source/common/protobuf", "@envoy_api//envoy/api/v2/listener:listener_cc", diff --git a/include/envoy/server/listener_manager.h b/include/envoy/server/listener_manager.h index fd2808cd6da76..1813e55093137 100644 --- a/include/envoy/server/listener_manager.h +++ b/include/envoy/server/listener_manager.h @@ -4,6 +4,7 @@ #include "envoy/network/filter.h" #include "envoy/network/listen_socket.h" #include "envoy/network/listener.h" +#include "envoy/quic/listener.h" #include "envoy/server/drain_manager.h" #include "envoy/server/filter_config.h" #include "envoy/server/guarddog.h" @@ -74,6 +75,16 @@ class ListenerComponentFactory { const Protobuf::RepeatedPtrField& filters, Configuration::ListenerFactoryContext& context) PURE; + /** + * Creates a QUIC listener factory. + * @param listener_config supplies the QUIC listenerconfiguration. + * @param context supplies the factory creation context. + * @return Quic::QuicListenerFactoryPtr the QUIC listener factory. + */ + virtual Quic::QuicListenerFactoryPtr + createQuicListenerFactory(const envoy::api::v2::listener::QuicListener& listener_config, + Configuration::ListenerFactoryContext& context) PURE; + /** * @return DrainManagerPtr a new drain manager. * @param drain_type supplies the type of draining to do for the owning listener. diff --git a/source/extensions/extensions_build_config.bzl b/source/extensions/extensions_build_config.bzl index 23d472d14ab22..1dadd33995ec7 100644 --- a/source/extensions/extensions_build_config.bzl +++ b/source/extensions/extensions_build_config.bzl @@ -76,6 +76,11 @@ EXTENSIONS = { "envoy.filters.network.thrift_proxy": "//source/extensions/filters/network/thrift_proxy:config", "envoy.filters.network.sni_cluster": "//source/extensions/filters/network/sni_cluster:config", + # + # QUIC listeners + # + "envoy.quic_listeners.quiche": "//source/extensions/quic_listeners/quiche:config", + # # Resource monitors # diff --git a/source/extensions/quic_listeners/BUILD b/source/extensions/quic_listeners/BUILD new file mode 100644 index 0000000000000..6156949edef64 --- /dev/null +++ b/source/extensions/quic_listeners/BUILD @@ -0,0 +1,17 @@ +licenses(["notice"]) # Apache 2 + +load( + "//bazel:envoy_build_system.bzl", + "envoy_cc_library", + "envoy_package", +) + +envoy_package() + +envoy_cc_library( + name = "well_known_names", + hdrs = ["well_known_names.h"], + deps = [ + "//source/common/singleton:const_singleton", + ], +) diff --git a/source/extensions/quic_listeners/quiche/BUILD b/source/extensions/quic_listeners/quiche/BUILD new file mode 100644 index 0000000000000..24362fa15ff23 --- /dev/null +++ b/source/extensions/quic_listeners/quiche/BUILD @@ -0,0 +1,23 @@ +licenses(["notice"]) # Apache 2 + +# QUICHE-based QUIC listener implementation + +load( + "//bazel:envoy_build_system.bzl", + "envoy_cc_library", + "envoy_package", +) + +envoy_package() + +envoy_cc_library( + name = "config", + srcs = ["config.cc"], + deps = [ + "//include/envoy/quic:config_interface", + "//include/envoy/registry", + "//source/common/protobuf:utility_lib", + "//source/extensions/quic_listeners:well_known_names", + "@envoy_api//envoy/config/quic_listener/quiche/v2alpha:quiche_cc", + ], +) diff --git a/source/extensions/quic_listeners/quiche/config.cc b/source/extensions/quic_listeners/quiche/config.cc new file mode 100644 index 0000000000000..b3e33c4edcd9d --- /dev/null +++ b/source/extensions/quic_listeners/quiche/config.cc @@ -0,0 +1,62 @@ +#include +#include + +#include "envoy/config/quic_listener/quiche/v2alpha/quiche.pb.h" +#include "envoy/config/quic_listener/quiche/v2alpha/quiche.pb.validate.h" +#include "envoy/registry/registry.h" +#include "envoy/quic/config.h" + +#include "common/protobuf/utility.h" + +#include "extensions/quic_listeners/well_known_names.h" + +using QuicheConfigProto = envoy::config::quic_listener::quiche::v2alpha::Quiche; + +namespace Envoy { +namespace Extensions { +namespace QuicListeners { +namespace Quiche { + +class QuicheListener : public Quic::QuicListener { +public: + void disable() override {} + void enable() override {} +}; + +class QuicheListenerFactory : public Quic::QuicListenerFactory { +public: + Quic::QuicListenerPtr createQuicListener(Quic::QuicListenerCallbacks&) override { + return std::make_unique(); + } +}; + +/** + * Config registration for the QUICHE QUIC listener. @see QuicListenerConfigFactory. + */ +class QuicheListenerConfigFactory : public Quic::QuicListenerConfigFactory { +public: + // QuicListenerConfigFactory + Quic::QuicListenerFactoryPtr + createListenerFactoryFromProto(const Protobuf::Message& proto_config, + Server::Configuration::ListenerFactoryContext&) override { + MessageUtil::downcastAndValidate(proto_config); + return std::make_unique(); + } + + ProtobufTypes::MessagePtr createEmptyConfigProto() override { + return std::make_unique(); + } + + std::string name() override { return QuicListenerNames::get().Quiche; } +}; + +/** + * Static registration for the QUICHE QUIC listener. @see RegisterFactory. + */ +static Registry::RegisterFactory + registered_; + +} // namespace Quiche +} // namespace QuicListeners +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/quic_listeners/well_known_names.h b/source/extensions/quic_listeners/well_known_names.h new file mode 100644 index 0000000000000..b4b8edb72832c --- /dev/null +++ b/source/extensions/quic_listeners/well_known_names.h @@ -0,0 +1,27 @@ +#pragma once + +#include + +#include "common/singleton/const_singleton.h" + +namespace Envoy { +namespace Extensions { +namespace QuicListeners { + +/** + * Well-known QUIC listener names. + * NOTE: New filters should use the well known name: envoy.filters.listener.name. + */ +class QuicListenerNameValues { +public: + // QUICHE-based implementation + const std::string Quiche = "envoy.quic_listeners.quiche"; + // ngtcp2-based implementation + // const std::string Ngtcp2 = "envoy.quic_listeners.ngtcp2"; +}; + +typedef ConstSingleton QuicListenerNames; + +} // namespace QuicListeners +} // namespace Extensions +} // namespace Envoy diff --git a/source/server/BUILD b/source/server/BUILD index 518d703237112..dde5801266788 100644 --- a/source/server/BUILD +++ b/source/server/BUILD @@ -205,6 +205,7 @@ envoy_cc_library( ":init_manager_lib", ":lds_api_lib", ":transport_socket_config_lib", + "//include/envoy/quic:config_interface", "//include/envoy/server:filter_config_interface", "//include/envoy/server:listener_manager_interface", "//include/envoy/server:transport_socket_config_interface", diff --git a/source/server/config_validation/server.h b/source/server/config_validation/server.h index df320e9bc0a1e..ab37cc547ea37 100644 --- a/source/server/config_validation/server.h +++ b/source/server/config_validation/server.h @@ -113,6 +113,12 @@ class ValidationInstance : Logger::Loggable, Configuration::ListenerFactoryContext& context) override { return ProdListenerComponentFactory::createListenerFilterFactoryList_(filters, context); } + Quic::QuicListenerFactoryPtr + createQuicListenerFactory(const envoy::api::v2::listener::QuicListener& listener_config, + Configuration::ListenerFactoryContext& context) override { + return ProdListenerComponentFactory::createQuicListenerFactory_(listener_config, context); + } + Network::SocketSharedPtr createListenSocket(Network::Address::InstanceConstSharedPtr, Network::Address::SocketType, const Network::Socket::OptionsSharedPtr&, diff --git a/source/server/http/admin.h b/source/server/http/admin.h index 979be5a9c718e..10a5dc21be3ce 100644 --- a/source/server/http/admin.h +++ b/source/server/http/admin.h @@ -256,6 +256,7 @@ class AdminImpl : public Admin, // Network::ListenerConfig Network::FilterChainManager& filterChainManager() override { return parent_; } Network::FilterChainFactory& filterChainFactory() override { return parent_; } + Quic::QuicListenerFactory* quicListenerFactory() override { return nullptr; } Network::Socket& socket() override { return parent_.mutable_socket(); } const Network::Socket& socket() const override { return parent_.mutable_socket(); } bool bindToPort() override { return true; } diff --git a/source/server/listener_manager_impl.cc b/source/server/listener_manager_impl.cc index 06b3a659331b0..9a2f692696a65 100644 --- a/source/server/listener_manager_impl.cc +++ b/source/server/listener_manager_impl.cc @@ -1,6 +1,7 @@ #include "server/listener_manager_impl.h" #include "envoy/admin/v2alpha/config_dump.pb.h" +#include "envoy/quic/config.h" #include "envoy/registry/registry.h" #include "envoy/server/transport_socket_config.h" #include "envoy/stats/scope.h" @@ -97,6 +98,22 @@ ProdListenerComponentFactory::createListenerFilterFactoryList_( return ret; } +Quic::QuicListenerFactoryPtr ProdListenerComponentFactory::createQuicListenerFactory_( + const envoy::api::v2::listener::QuicListener& listener_config, + Configuration::ListenerFactoryContext& context) { + const ProtobufTypes::String string_name = listener_config.name(); + ENVOY_LOG(debug, "QUIC listener:"); + ENVOY_LOG(debug, " name: {}", string_name); + const Json::ObjectSharedPtr json_config = + MessageUtil::getJsonObjectFromMessage(listener_config.config()); + ENVOY_LOG(debug, " config: {}", json_config->asJsonString()); + + // Now see if there is a factory that will accept the config. + auto& factory = Config::Utility::getAndCheckFactory(string_name); + auto message = Config::Utility::translateToFactoryConfig(listener_config, factory); + return factory.createListenerFactoryFromProto(*message, context); +} + Network::SocketSharedPtr ProdListenerComponentFactory::createListenSocket( Network::Address::InstanceConstSharedPtr address, Network::Address::SocketType socket_type, const Network::Socket::OptionsSharedPtr& options, bool bind_to_port) { @@ -190,6 +207,10 @@ ListenerImpl::ListenerImpl(const envoy::api::v2::Listener& config, const std::st listener_filter_factories_ = parent_.factory_.createListenerFilterFactoryList(config.listener_filters(), *this); } + if (config.has_quic_listener()) { + quic_listener_factory_ = + parent_.factory_.createQuicListenerFactory(config.quic_listener(), *this); + } // Add original dst listener filter if 'use_original_dst' flag is set. if (PROTOBUF_GET_WRAPPED_OR_DEFAULT(config, use_original_dst, false)) { auto& factory = diff --git a/source/server/listener_manager_impl.h b/source/server/listener_manager_impl.h index bf860ad9baa31..c994b65482204 100644 --- a/source/server/listener_manager_impl.h +++ b/source/server/listener_manager_impl.h @@ -43,6 +43,12 @@ class ProdListenerComponentFactory : public ListenerComponentFactory, static std::vector createListenerFilterFactoryList_( const Protobuf::RepeatedPtrField& filters, Configuration::ListenerFactoryContext& context); + /** + * Static worker for createQuicListenerFactory() that can be used directly in tests. + */ + static Quic::QuicListenerFactoryPtr + createQuicListenerFactory_(const envoy::api::v2::listener::QuicListener& listener_config, + Configuration::ListenerFactoryContext& context); // Server::ListenerComponentFactory LdsApiPtr createLdsApi(const envoy::api::v2::core::ConfigSource& lds_config) override { @@ -60,6 +66,12 @@ class ProdListenerComponentFactory : public ListenerComponentFactory, Configuration::ListenerFactoryContext& context) override { return createListenerFilterFactoryList_(filters, context); } + Quic::QuicListenerFactoryPtr + createQuicListenerFactory(const envoy::api::v2::listener::QuicListener& listener_config, + Configuration::ListenerFactoryContext& context) override { + return createQuicListenerFactory_(listener_config, context); + } + Network::SocketSharedPtr createListenSocket(Network::Address::InstanceConstSharedPtr address, Network::Address::SocketType socket_type, const Network::Socket::OptionsSharedPtr& options, @@ -242,6 +254,7 @@ class ListenerImpl : public Network::ListenerConfig, // Network::ListenerConfig Network::FilterChainManager& filterChainManager() override { return *this; } Network::FilterChainFactory& filterChainFactory() override { return *this; } + Quic::QuicListenerFactory* quicListenerFactory() override { return quic_listener_factory_.get(); } Network::Socket& socket() override { return *socket_; } const Network::Socket& socket() const override { return *socket_; } bool bindToPort() override { return bind_to_port_; } @@ -404,6 +417,7 @@ class ListenerImpl : public Network::ListenerConfig, InitManagerImpl dynamic_init_manager_; bool initialize_canceled_{}; std::vector listener_filter_factories_; + Quic::QuicListenerFactoryPtr quic_listener_factory_; DrainManagerPtr local_drain_manager_; bool saw_listener_create_failure_{}; const envoy::api::v2::Listener config_; diff --git a/test/extensions/filters/listener/proxy_protocol/proxy_protocol_test.cc b/test/extensions/filters/listener/proxy_protocol/proxy_protocol_test.cc index ea2dfa3be19f3..7c0747d757d00 100644 --- a/test/extensions/filters/listener/proxy_protocol/proxy_protocol_test.cc +++ b/test/extensions/filters/listener/proxy_protocol/proxy_protocol_test.cc @@ -65,6 +65,7 @@ class ProxyProtocolTest : public testing::TestWithParam, // Network::ListenerConfig Network::FilterChainManager& filterChainManager() override { return parent_; } Network::FilterChainFactory& filterChainFactory() override { return parent_; } + Quic::QuicListenerFactory* quicListenerFactory() override { return nullptr; } Network::Socket& socket() override { return *parent_.socket_; } const Network::Socket& socket() const override { return *parent_.socket_; } bool bindToPort() override { return true; } diff --git a/test/mocks/network/mocks.h b/test/mocks/network/mocks.h index f72084ad6774e..6871a5b3f2da3 100644 --- a/test/mocks/network/mocks.h +++ b/test/mocks/network/mocks.h @@ -257,6 +257,7 @@ class MockListenerConfig : public ListenerConfig { MOCK_METHOD0(filterChainManager, FilterChainManager&()); MOCK_METHOD0(filterChainFactory, FilterChainFactory&()); + MOCK_METHOD0(quicListenerFactory, Quic::QuicListenerFactory*()); MOCK_METHOD0(socket, Socket&()); MOCK_CONST_METHOD0(socket, const Socket&()); MOCK_METHOD0(bindToPort, bool()); diff --git a/test/mocks/quic/BUILD b/test/mocks/quic/BUILD new file mode 100644 index 0000000000000..33c16087453ac --- /dev/null +++ b/test/mocks/quic/BUILD @@ -0,0 +1,18 @@ +licenses(["notice"]) # Apache 2 + +load( + "//bazel:envoy_build_system.bzl", + "envoy_cc_mock", + "envoy_package", +) + +envoy_package() + +envoy_cc_mock( + name = "quic_mocks", + hdrs = ["mocks.h"], + deps = [ + "//include/envoy/event:dispatcher_interface", + "//include/envoy/quic:listener_interface", + ], +) diff --git a/test/mocks/quic/mocks.h b/test/mocks/quic/mocks.h new file mode 100644 index 0000000000000..5bf461d1b8d50 --- /dev/null +++ b/test/mocks/quic/mocks.h @@ -0,0 +1,22 @@ +#pragma once + +#include "envoy/event/dispatcher.h" +#include "envoy/quic/listener.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace Envoy { +namespace Quic { + +class MockQuicListenerCallbacks : public QuicListenerCallbacks { +public: + MockQuicListenerCallbacks() {} + ~MockQuicListenerCallbacks() {} + + MOCK_METHOD0(socket, Network::Socket&()); + MOCK_METHOD0(dispatcher, Event::Dispatcher&()); +}; + +} // namespace Quic +} // namespace Envoy diff --git a/test/mocks/server/mocks.h b/test/mocks/server/mocks.h index b2f8cdf4c8564..cc3d94d4587a6 100644 --- a/test/mocks/server/mocks.h +++ b/test/mocks/server/mocks.h @@ -220,6 +220,10 @@ class MockListenerComponentFactory : public ListenerComponentFactory { std::vector( const Protobuf::RepeatedPtrField&, Configuration::ListenerFactoryContext& context)); + MOCK_METHOD2(createQuicListenerFactory, + Quic::QuicListenerFactoryPtr( + const envoy::api::v2::listener::QuicListener&, + Configuration::ListenerFactoryContext& context)); MOCK_METHOD4(createListenSocket, Network::SocketSharedPtr(Network::Address::InstanceConstSharedPtr address, Network::Address::SocketType socket_type, diff --git a/test/server/BUILD b/test/server/BUILD index 5054dea51c9f8..a307e0ada4c98 100644 --- a/test/server/BUILD +++ b/test/server/BUILD @@ -165,10 +165,12 @@ envoy_cc_test( "//source/extensions/filters/listener/original_dst:config", "//source/extensions/filters/listener/tls_inspector:config", "//source/extensions/filters/network/http_connection_manager:config", + "//source/extensions/quic_listeners/quiche:config", "//source/extensions/transport_sockets/raw_buffer:config", "//source/extensions/transport_sockets/ssl:config", "//source/server:listener_manager_lib", "//test/mocks/network:network_mocks", + "//test/mocks/quic:quic_mocks", "//test/mocks/server:server_mocks", "//test/test_common:environment_lib", "//test/test_common:registry_lib", diff --git a/test/server/connection_handler_test.cc b/test/server/connection_handler_test.cc index c22078eee68b8..0b693092b0f6d 100644 --- a/test/server/connection_handler_test.cc +++ b/test/server/connection_handler_test.cc @@ -43,6 +43,7 @@ class ConnectionHandlerTest : public testing::Test, protected Logger::Loggable Quic::QuicListenerFactoryPtr { + return ProdListenerComponentFactory::createQuicListenerFactory_(listener_config, + context); + })); socket_ = std::make_unique>(); local_address_.reset(new Network::Address::Ipv4Instance("127.0.0.1", 1234)); remote_address_.reset(new Network::Address::Ipv4Instance("127.0.0.1", 1234)); @@ -2993,5 +3001,64 @@ TEST_F(ListenerManagerImplWithRealFiltersTest, VerifyIgnoreExpirationWithCA) { EXPECT_NO_THROW(manager_->addOrUpdateListener(parseListenerFromV2Yaml(yaml), "", true)); } +TEST_F(ListenerManagerImplWithRealFiltersTest, NoQuicListener) { + const std::string yaml = R"EOF( + name: "foo" + address: + socket_address: { protocol: UDP, address: 127.0.0.1, port_value: 1234 } + filter_chains: + - filters: + )EOF"; + + EXPECT_CALL(listener_factory_, createListenSocket(_, _, _, true)); + EXPECT_TRUE(manager_->addOrUpdateListener(parseListenerFromV2Yaml(yaml), "", true)); + EXPECT_EQ(nullptr, manager_->listeners().front().get().quicListenerFactory()); +} + +TEST_F(ListenerManagerImplWithRealFiltersTest, QuicListener) { + const std::string yaml = R"EOF( + name: "foo" + address: + socket_address: { protocol: UDP, address: 127.0.0.1, port_value: 1234 } + filter_chains: + - filters: + quic_listener: + name: "envoy.quic_listeners.quiche" + config: + http_config: + stat_prefix: "quiche" + route_config: + virtual_hosts: + - name: "some_virtual_host" + domains: ["some.domain"] + routes: + - match: { prefix: "/" } + route: { cluster: service_foo } + )EOF"; + + EXPECT_CALL(listener_factory_, createListenSocket(_, _, _, true)); + EXPECT_TRUE(manager_->addOrUpdateListener(parseListenerFromV2Yaml(yaml), "", true)); + auto factory = manager_->listeners().front().get().quicListenerFactory(); + EXPECT_NE(nullptr, factory); + Quic::MockQuicListenerCallbacks callbacks; + EXPECT_NE(nullptr, factory->createQuicListener(callbacks)); +} + +TEST_F(ListenerManagerImplWithRealFiltersTest, QuicListenerBadConfig) { + const std::string yaml = R"EOF( + name: "foo" + address: + socket_address: { protocol: UDP, address: 127.0.0.1, port_value: 1234 } + filter_chains: + - filters: + quic_listener: + name: "envoy.quic_listeners.quiche" + config: {} + )EOF"; + + EXPECT_THROW(manager_->addOrUpdateListener(parseListenerFromV2Yaml(yaml), "", true), + ProtoValidationException); +} + } // namespace Server } // namespace Envoy