diff --git a/envoy/event/dispatcher.h b/envoy/event/dispatcher.h index 86fd2855a1512..b2ba88a273ba9 100644 --- a/envoy/event/dispatcher.h +++ b/envoy/event/dispatcher.h @@ -218,6 +218,19 @@ class Dispatcher : public DispatcherBase, public ScopeTracker { Network::TransportSocketPtr&& transport_socket, const Network::ConnectionSocket::OptionsSharedPtr& options) PURE; + /** + * Register an internal listener manager for this dispatcher. + */ + virtual void + registerInternalListenerManager(Network::InternalListenerManager& internal_listener_manager) PURE; + + /** + * @brief Get the Internal Listener Manager object. + * + * @return the registered internal istener manager or nullopt. + */ + virtual Network::InternalListenerManagerOptRef getInternalListenerManager() PURE; + /** * @return Filesystem::WatcherPtr a filesystem watcher owned by the caller. */ diff --git a/envoy/network/BUILD b/envoy/network/BUILD index 77c31f791aa81..178009c63fa28 100644 --- a/envoy/network/BUILD +++ b/envoy/network/BUILD @@ -28,6 +28,18 @@ envoy_cc_library( ], ) +envoy_cc_library( + name = "client_connection_factory", + hdrs = ["client_connection_factory.h"], + deps = [ + ":address_interface", + ":connection_interface", + ":listen_socket_interface", + ":transport_socket_interface", + "//envoy/config:typed_config_interface", + ], +) + envoy_cc_library( name = "connection_handler_interface", hdrs = ["connection_handler.h"], diff --git a/envoy/network/client_connection_factory.h b/envoy/network/client_connection_factory.h new file mode 100644 index 0000000000000..142f4f54b7df7 --- /dev/null +++ b/envoy/network/client_connection_factory.h @@ -0,0 +1,29 @@ +#pragma once + +#include "envoy/config/typed_config.h" +#include "envoy/network/address.h" +#include "envoy/network/connection.h" +#include "envoy/network/listen_socket.h" +#include "envoy/network/transport_socket.h" + +namespace Envoy { +namespace Network { + +class ClientConnectionFactory : public Config::UntypedFactory { +public: + ClientConnectionFactory() = default; + ~ClientConnectionFactory() override = default; + + // Config::UntypedFactory + std::string category() const override { return "network.connection"; } + + virtual Network::ClientConnectionPtr + createClientConnection(Event::Dispatcher& dispatcher, + Network::Address::InstanceConstSharedPtr address, + Network::Address::InstanceConstSharedPtr source_address, + Network::TransportSocketPtr&& transport_socket, + const Network::ConnectionSocket::OptionsSharedPtr& options) PURE; +}; + +} // namespace Network +} // namespace Envoy diff --git a/source/common/event/BUILD b/source/common/event/BUILD index 22efdc958ac26..8a1dafbe880e8 100644 --- a/source/common/event/BUILD +++ b/source/common/event/BUILD @@ -39,11 +39,14 @@ envoy_cc_library( "//envoy/common:scope_tracker_interface", "//envoy/common:time_interface", "//envoy/event:signal_interface", + "//envoy/network:client_connection_factory", "//envoy/network:listen_socket_interface", "//envoy/network:listener_interface", "//source/common/common:assert_lib", "//source/common/common:thread_lib", + "//source/common/config:utility_lib", "//source/common/filesystem:watcher_lib", + "//source/common/network:address_lib", "//source/common/network:connection_lib", "//source/common/network:listener_lib", "@envoy_api//envoy/config/overload/v3:pkg_cc_proto", diff --git a/source/common/event/dispatcher_impl.cc b/source/common/event/dispatcher_impl.cc index a94b537ff45e7..2b45f13601586 100644 --- a/source/common/event/dispatcher_impl.cc +++ b/source/common/event/dispatcher_impl.cc @@ -9,6 +9,7 @@ #include "envoy/api/api.h" #include "envoy/common/scope_tracker.h" #include "envoy/config/overload/v3/overload.pb.h" +#include "envoy/network/client_connection_factory.h" #include "envoy/network/listen_socket.h" #include "envoy/network/listener.h" @@ -16,12 +17,14 @@ #include "source/common/common/assert.h" #include "source/common/common/lock_guard.h" #include "source/common/common/thread.h" +#include "source/common/config/utility.h" #include "source/common/event/file_event_impl.h" #include "source/common/event/libevent_scheduler.h" #include "source/common/event/scaled_range_timer_manager_impl.h" #include "source/common/event/signal_impl.h" #include "source/common/event/timer_impl.h" #include "source/common/filesystem/watcher_impl.h" +#include "source/common/network/address_impl.h" #include "source/common/network/connection_impl.h" #include "source/common/network/tcp_listener_impl.h" #include "source/common/network/udp_listener_impl.h" @@ -148,8 +151,12 @@ DispatcherImpl::createClientConnection(Network::Address::InstanceConstSharedPtr Network::TransportSocketPtr&& transport_socket, const Network::ConnectionSocket::OptionsSharedPtr& options) { ASSERT(isThreadSafe()); - return std::make_unique(*this, address, source_address, - std::move(transport_socket), options); + + auto factory = Config::Utility::getFactoryByName( + std::string(Network::Address::addressType(address))); + // The caller expects a non-null connection as of today. A unsupported address will crash any way. + return factory->createClientConnection(*this, address, source_address, + std::move(transport_socket), options); } FileEventPtr DispatcherImpl::createFileEvent(os_fd_t fd, FileReadyCb cb, FileTriggerType trigger, diff --git a/source/common/event/dispatcher_impl.h b/source/common/event/dispatcher_impl.h index 068638b727252..d8d0fc9a0ba07 100644 --- a/source/common/event/dispatcher_impl.h +++ b/source/common/event/dispatcher_impl.h @@ -66,6 +66,17 @@ class DispatcherImpl : Logger::Loggable, Network::Address::InstanceConstSharedPtr source_address, Network::TransportSocketPtr&& transport_socket, const Network::ConnectionSocket::OptionsSharedPtr& options) override; + + void registerInternalListenerManager( + Network::InternalListenerManager& internal_listener_manager) override { + ASSERT(!internal_listener_manager_.has_value()); + internal_listener_manager_ = internal_listener_manager; + } + + Network::InternalListenerManagerOptRef getInternalListenerManager() override { + return internal_listener_manager_; + } + FileEventPtr createFileEvent(os_fd_t fd, FileReadyCb cb, FileTriggerType trigger, uint32_t events) override; Filesystem::WatcherPtr createFilesystemWatcher() override; @@ -172,6 +183,7 @@ class DispatcherImpl : Logger::Loggable, MonotonicTime approximate_monotonic_time_; WatchdogRegistrationPtr watchdog_registration_; const ScaledRangeTimerManagerPtr scaled_timer_manager_; + Network::InternalListenerManagerOptRef internal_listener_manager_; }; } // namespace Event diff --git a/source/common/network/BUILD b/source/common/network/BUILD index 1f352e7e59638..dea4dbe2d3da1 100644 --- a/source/common/network/BUILD +++ b/source/common/network/BUILD @@ -79,6 +79,7 @@ envoy_cc_library( deps = [ ":address_lib", ":connection_base_lib", + ":default_client_connection_factory", ":raw_buffer_socket_lib", ":utility_lib", "//envoy/event:timer_interface", @@ -108,6 +109,30 @@ envoy_cc_library( ], ) +envoy_cc_library( + name = "default_client_connection_factory", + srcs = [ + "default_client_connection_factory.cc", + ], + hdrs = [ + "connection_impl.h", + "default_client_connection_factory.h", + ], + deps = [ + ":address_lib", + "//envoy/network:client_connection_factory", + "//envoy/network:connection_interface", + "//envoy/network:transport_socket_interface", + "//envoy/registry", + + # required by connection_impl.h + # will organize below when default client factory is confirmed to use dedicated target. + "//source/common/buffer:watermark_buffer_lib", + ":connection_base_lib", + "//source/common/stream_info:stream_info_lib", + ], +) + envoy_cc_library( name = "filter_lib", hdrs = ["filter_impl.h"], diff --git a/source/common/network/address_impl.cc b/source/common/network/address_impl.cc index 5954724186bce..5223e9cd5c564 100644 --- a/source/common/network/address_impl.cc +++ b/source/common/network/address_impl.cc @@ -118,6 +118,18 @@ addressFromSockAddrOrDie(const sockaddr_storage& ss, socklen_t ss_len, os_fd_t f return *address; } +absl::string_view addressType(const Network::Address::InstanceConstSharedPtr& addr) { + ASSERT(addr != nullptr); + switch (addr->type()) { + case Network::Address::Type::Ip: + case Network::Address::Type::Pipe: + return "default"; + case Network::Address::Type::EnvoyInternal: + return "EnvoyInternal"; + } + NOT_REACHED_GCOVR_EXCL_LINE; +}; + Ipv4Instance::Ipv4Instance(const sockaddr_in* address, const SocketInterface* sock_interface) : InstanceBase(Type::Ip, sockInterfaceOrDefault(sock_interface)) { throwOnError(validateProtocolSupported()); diff --git a/source/common/network/address_impl.h b/source/common/network/address_impl.h index 2ade85fc89984..76bf59ccddadc 100644 --- a/source/common/network/address_impl.h +++ b/source/common/network/address_impl.h @@ -44,6 +44,12 @@ InstanceConstSharedPtr addressFromSockAddrOrThrow(const sockaddr_storage& ss, so InstanceConstSharedPtr addressFromSockAddrOrDie(const sockaddr_storage& ss, socklen_t ss_len, os_fd_t fd, bool v6only = true); +/** + * Return the address type in string_view. The returned type name is used by calling + * ClientConnectionFactory. + */ +absl::string_view addressType(const Network::Address::InstanceConstSharedPtr& addr); + /** * Base class for all address types. */ diff --git a/source/common/network/default_client_connection_factory.cc b/source/common/network/default_client_connection_factory.cc new file mode 100644 index 0000000000000..e1a65a0e81579 --- /dev/null +++ b/source/common/network/default_client_connection_factory.cc @@ -0,0 +1,24 @@ +#include "source/common/network/default_client_connection_factory.h" + +#include "envoy/registry/registry.h" + +#include "source/common/network/address_impl.h" +#include "source/common/network/connection_impl.h" + +namespace Envoy { + +namespace Network { + +Network::ClientConnectionPtr DefaultClientConnectionFactory::createClientConnection( + Event::Dispatcher& dispatcher, Network::Address::InstanceConstSharedPtr address, + Network::Address::InstanceConstSharedPtr source_address, + Network::TransportSocketPtr&& transport_socket, + const Network::ConnectionSocket::OptionsSharedPtr& options) { + ASSERT(address->ip() || address->pipe()); + return std::make_unique(dispatcher, address, source_address, + std::move(transport_socket), options); +} +REGISTER_FACTORY(DefaultClientConnectionFactory, Network::ClientConnectionFactory); + +} // namespace Network +} // namespace Envoy diff --git a/source/common/network/default_client_connection_factory.h b/source/common/network/default_client_connection_factory.h new file mode 100644 index 0000000000000..a4f96e29e267b --- /dev/null +++ b/source/common/network/default_client_connection_factory.h @@ -0,0 +1,24 @@ +#pragma once + +#include "envoy/common/pure.h" +#include "envoy/network/client_connection_factory.h" +#include "envoy/network/connection.h" + +namespace Envoy { + +namespace Network { + +class DefaultClientConnectionFactory : public Network::ClientConnectionFactory { +public: + ~DefaultClientConnectionFactory() override = default; + std::string name() const override { return "default"; } + Network::ClientConnectionPtr + createClientConnection(Event::Dispatcher& dispatcher, + Network::Address::InstanceConstSharedPtr address, + Network::Address::InstanceConstSharedPtr source_address, + Network::TransportSocketPtr&& transport_socket, + const Network::ConnectionSocket::OptionsSharedPtr& options) override; +}; + +} // namespace Network +} // namespace Envoy diff --git a/source/extensions/io_socket/user_space/BUILD b/source/extensions/io_socket/user_space/BUILD index 01a5948f4c9e4..402e99b524646 100644 --- a/source/extensions/io_socket/user_space/BUILD +++ b/source/extensions/io_socket/user_space/BUILD @@ -13,6 +13,7 @@ envoy_cc_extension( name = "config", srcs = ["config.h"], deps = [ + ":client_connection_factory", ":io_handle_impl_lib", ], ) @@ -57,3 +58,21 @@ envoy_cc_library( "//source/common/network:default_socket_interface_lib", ], ) + +envoy_cc_library( + name = "client_connection_factory", + srcs = [ + "client_connection_factory.cc", + ], + hdrs = [ + "client_connection_factory.h", + ], + deps = [ + ":io_handle_impl_lib", + "//envoy/network:client_connection_factory", + "//envoy/network:connection_interface", + "//envoy/registry", + "//source/common/network:connection_lib", + "//source/common/network:listen_socket_lib", + ], +) diff --git a/source/extensions/io_socket/user_space/client_connection_factory.cc b/source/extensions/io_socket/user_space/client_connection_factory.cc new file mode 100644 index 0000000000000..7fb7fdee59bc7 --- /dev/null +++ b/source/extensions/io_socket/user_space/client_connection_factory.cc @@ -0,0 +1,56 @@ +#include "source/extensions/io_socket/user_space/client_connection_factory.h" + +#include "envoy/registry/registry.h" + +#include "source/common/network/address_impl.h" +#include "source/common/network/connection_impl.h" +#include "source/common/network/listen_socket_impl.h" +#include "source/extensions/io_socket/user_space/io_handle_impl.h" + +namespace Envoy { + +namespace Extensions { +namespace IoSocket { +namespace UserSpace { + +Network::ClientConnectionPtr InternalClientConnectionFactory::createClientConnection( + Event::Dispatcher& dispatcher, Network::Address::InstanceConstSharedPtr address, + Network::Address::InstanceConstSharedPtr source_address, + Network::TransportSocketPtr&& transport_socket, + const Network::ConnectionSocket::OptionsSharedPtr& options) { + + auto [io_handle_client, io_handle_server] = + Extensions::IoSocket::UserSpace::IoHandleFactory::createIoHandlePair(); + + auto client_conn = std::make_unique( + dispatcher, + std::make_unique(std::move(io_handle_client), source_address, + address), + source_address, std::move(transport_socket), options); + + // It's either in the main thread or the worker is not yet started. + auto internal_listener_manager = dispatcher.getInternalListenerManager(); + if (!internal_listener_manager.has_value()) { + io_handle_server->close(); + return client_conn; + } + + // The request internal listener may not exist. + auto internal_listener = internal_listener_manager.value().get().findByAddress(address); + if (!internal_listener.has_value()) { + io_handle_server->close(); + return client_conn; + } + + auto accepted_socket = std::make_unique(std::move(io_handle_server), + address, source_address); + internal_listener->onAccept(std::move(accepted_socket)); + return client_conn; +} + +REGISTER_FACTORY(InternalClientConnectionFactory, Network::ClientConnectionFactory); + +} // namespace UserSpace +} // namespace IoSocket +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/io_socket/user_space/client_connection_factory.h b/source/extensions/io_socket/user_space/client_connection_factory.h new file mode 100644 index 0000000000000..d143955b18273 --- /dev/null +++ b/source/extensions/io_socket/user_space/client_connection_factory.h @@ -0,0 +1,34 @@ +#pragma once + +#include +#include + +#include "envoy/common/pure.h" +#include "envoy/network/client_connection_factory.h" +#include "envoy/network/connection.h" + +#include "source/common/common/logger.h" + +namespace Envoy { + +namespace Extensions { +namespace IoSocket { +namespace UserSpace { + +class InternalClientConnectionFactory : public Network::ClientConnectionFactory, + Logger::Loggable { +public: + ~InternalClientConnectionFactory() override = default; + std::string name() const override { return "EnvoyInternal"; } + Network::ClientConnectionPtr + createClientConnection(Event::Dispatcher& dispatcher, + Network::Address::InstanceConstSharedPtr address, + Network::Address::InstanceConstSharedPtr source_address, + Network::TransportSocketPtr&& transport_socket, + const Network::ConnectionSocket::OptionsSharedPtr& options) override; +}; + +} // namespace UserSpace +} // namespace IoSocket +} // namespace Extensions +} // namespace Envoy diff --git a/source/server/worker_impl.cc b/source/server/worker_impl.cc index 3e0fdbc93e2bc..bbadce5ce79fd 100644 --- a/source/server/worker_impl.cc +++ b/source/server/worker_impl.cc @@ -19,6 +19,7 @@ WorkerPtr ProdWorkerFactory::createWorker(uint32_t index, OverloadManager& overl Event::DispatcherPtr dispatcher( api_.allocateDispatcher(worker_name, overload_manager.scaledTimerFactory())); auto conn_handler = std::make_unique(*dispatcher, index); + dispatcher->registerInternalListenerManager(*conn_handler); return std::make_unique(tls_, hooks_, std::move(dispatcher), std::move(conn_handler), overload_manager, api_, stat_names_); } diff --git a/test/common/event/BUILD b/test/common/event/BUILD index c42e8e1aff1ea..8b945fe028615 100644 --- a/test/common/event/BUILD +++ b/test/common/event/BUILD @@ -16,6 +16,7 @@ envoy_cc_test( "//source/common/event:deferred_task", "//source/common/event:dispatcher_includes", "//source/common/event:dispatcher_lib", + "//source/common/network:address_lib", "//source/common/stats:isolated_store_lib", "//test/mocks:common_lib", "//test/mocks/server:watch_dog_mocks", diff --git a/test/common/event/dispatcher_impl_test.cc b/test/common/event/dispatcher_impl_test.cc index 7538a8c495c07..ac979a379f6ef 100644 --- a/test/common/event/dispatcher_impl_test.cc +++ b/test/common/event/dispatcher_impl_test.cc @@ -11,11 +11,13 @@ #include "source/common/event/deferred_task.h" #include "source/common/event/dispatcher_impl.h" #include "source/common/event/timer_impl.h" +#include "source/common/network/address_impl.h" #include "source/common/stats/isolated_store_impl.h" #include "test/mocks/common.h" #include "test/mocks/server/watch_dog.h" #include "test/mocks/stats/mocks.h" +#include "test/test_common/network_utility.h" #include "test/test_common/simulated_time_system.h" #include "test/test_common/test_runtime.h" #include "test/test_common/utility.h" @@ -1550,6 +1552,35 @@ TEST_F(DispatcherWithWatchdogTest, TouchBeforeFdEvent) { dispatcher_->run(Dispatcher::RunType::NonBlock); } +class DispatcherConnectionTest : public testing::Test { +protected: + DispatcherConnectionTest() + : api_(Api::createApiForTest()), dispatcher_(api_->allocateDispatcher("test_thread")) {} + + Api::ApiPtr api_; + DispatcherPtr dispatcher_; +}; + +TEST_F(DispatcherConnectionTest, CreateTcpConnection) { + + auto client_conn = dispatcher_->createClientConnection( + std::make_shared("127.0.0.1", 10911), + Network::Address::InstanceConstSharedPtr(), Network::Test::createRawBufferSocket(), nullptr); + EXPECT_NE(nullptr, client_conn); + client_conn->close(Network::ConnectionCloseType::NoFlush); +} + +// If the internal connection factory is not linked, envoy will be dead when creating connection to +// internal address. +TEST_F(DispatcherConnectionTest, CreateEnvoyInternalConnectionWhenFactoryNotExist) { + EXPECT_DEATH( + dispatcher_->createClientConnection( + std::make_shared("listener_internal_address"), + Network::Address::InstanceConstSharedPtr(), Network::Test::createRawBufferSocket(), + nullptr), + ""); +} + } // namespace } // namespace Event } // namespace Envoy diff --git a/test/common/network/connection_impl_test.cc b/test/common/network/connection_impl_test.cc index 88b8f79e9e6b7..c0647d09bd0a3 100644 --- a/test/common/network/connection_impl_test.cc +++ b/test/common/network/connection_impl_test.cc @@ -2989,6 +2989,9 @@ class InternalClientConnectionImplTest : public testing::Test { StrictMock client_callbacks_; }; +// The internal address is passed to Envoy by EDS. If this Envoy instance is configured as internal +// address disabled, the EDS subscription should reject the config before dispatcher attempt to +// establish connection to such address. TEST_F(InternalClientConnectionImplTest, CannotCreateConnectionToInternalAddressWithInternalAddressEnabled) { auto scoped_runtime_guard = std::make_unique(); @@ -2999,14 +3002,14 @@ TEST_F(InternalClientConnectionImplTest, "envoy.extensions.network.socket_interface.default_socket_interface"); Network::Address::InstanceConstSharedPtr address = std::make_shared("listener_0", sock_interface); - // Not implemented yet. + ASSERT_DEATH( { ClientConnectionPtr connection = dispatcher_->createClientConnection(address, Network::Address::InstanceConstSharedPtr(), Network::Test::createRawBufferSocket(), nullptr); }, - "panic: not implemented"); + ""); } } // namespace diff --git a/test/extensions/io_socket/user_space/BUILD b/test/extensions/io_socket/user_space/BUILD index 458d6345d36da..86d68e0988c4a 100644 --- a/test/extensions/io_socket/user_space/BUILD +++ b/test/extensions/io_socket/user_space/BUILD @@ -58,3 +58,16 @@ envoy_extension_cc_test( "//test/test_common:network_utility_lib", ], ) + +envoy_extension_cc_test( + name = "client_connection_factory_test", + srcs = ["client_connection_factory_test.cc"], + extension_names = ["envoy.io_socket.user_space"], + deps = [ + "//source/common/common:utility_lib", + "//source/common/network:address_lib", + "//source/extensions/io_socket/user_space:client_connection_factory", + "//source/extensions/io_socket/user_space:io_handle_impl_lib", + "//test/mocks/event:event_mocks", + ], +) diff --git a/test/extensions/io_socket/user_space/client_connection_factory_test.cc b/test/extensions/io_socket/user_space/client_connection_factory_test.cc new file mode 100644 index 0000000000000..d9f321ab6360f --- /dev/null +++ b/test/extensions/io_socket/user_space/client_connection_factory_test.cc @@ -0,0 +1,137 @@ +#include "envoy/buffer/buffer.h" +#include "envoy/event/file_event.h" +#include "envoy/network/listen_socket.h" + +#include "source/common/buffer/buffer_impl.h" +#include "source/common/common/fancy_logger.h" +#include "source/common/network/address_impl.h" +#include "source/extensions/io_socket/user_space/client_connection_factory.h" +#include "source/extensions/io_socket/user_space/io_handle_impl.h" + +#include "test/mocks/event/mocks.h" +#include "test/test_common/network_utility.h" + +#include "absl/container/fixed_array.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +using testing::NiceMock; + +namespace Envoy { +namespace Extensions { +namespace IoSocket { +namespace UserSpace { + +namespace { + +// The internal connection factory is linked in this test suite. This test suite verifies the +// connection can be created. +class ClientConnectionFactoryTest : public testing::Test { +public: + ClientConnectionFactoryTest() + : api_(Api::createApiForTest()), dispatcher_(api_->allocateDispatcher("test_thread")), + buf_(1024) { + std::tie(io_handle_, io_handle_peer_) = IoHandleFactory::createIoHandlePair(); + } + + Api::ApiPtr api_; + Event::DispatcherPtr dispatcher_; + + // Owned by IoHandleImpl. + NiceMock* schedulable_cb_; + std::unique_ptr io_handle_; + std::unique_ptr io_handle_peer_; + absl::FixedArray buf_; + Network::Address::EnvoyInternalInstance listener_addr{"listener_internal_address"}; +}; + +class MockInternalListener : public Network::InternalListener { +public: + MOCK_METHOD(void, onAccept, (Network::ConnectionSocketPtr &&)); + MOCK_METHOD(Event::Dispatcher&, dispatcher, ()); +}; + +class MockInternalListenerManger : public Network::InternalListenerManager { +public: + MOCK_METHOD(Network::InternalListenerOptRef, findByAddress, + (const Network::Address::InstanceConstSharedPtr&)); +}; + +TEST_F(ClientConnectionFactoryTest, ConnectFailsIfInternalConnectionManagerNotExist) { + auto client_conn = dispatcher_->createClientConnection( + std::make_shared(listener_addr), + Network::Address::InstanceConstSharedPtr(), Network::Test::createRawBufferSocket(), nullptr); + EXPECT_NE(nullptr, client_conn); + EXPECT_TRUE(client_conn->connecting()); + client_conn->connect(); + // Connect returns error immediately because no internal listener manager is registered. + EXPECT_FALSE(client_conn->connecting()); + client_conn->close(Network::ConnectionCloseType::NoFlush); +} + +TEST_F(ClientConnectionFactoryTest, ConnectFailsIfInternalListenerNotExist) { + MockInternalListenerManger internal_listener_manager; + dispatcher_->registerInternalListenerManager(internal_listener_manager); + + EXPECT_CALL(internal_listener_manager, findByAddress(_)) + .WillOnce(testing::Return(Network::InternalListenerOptRef())); + + auto client_conn = dispatcher_->createClientConnection( + std::make_shared(listener_addr), + Network::Address::InstanceConstSharedPtr(), Network::Test::createRawBufferSocket(), nullptr); + + EXPECT_NE(nullptr, client_conn); + EXPECT_TRUE(client_conn->connecting()); + client_conn->connect(); + // Connect returns error immediately because no internal listener is ready. + EXPECT_FALSE(client_conn->connecting()); + client_conn->close(Network::ConnectionCloseType::NoFlush); +} + +// Verify that the client connection to envoy internal address can be established. This test case +// does not instantiate a server connection. The server connection is tested in internal listener. +TEST_F(ClientConnectionFactoryTest, ConnectSucceeds) { + MockInternalListenerManger internal_listener_manager; + dispatcher_->registerInternalListenerManager(internal_listener_manager); + MockInternalListener internal_listener; + Network::InternalListenerOptRef internal_listener_opt{internal_listener}; + + EXPECT_CALL(internal_listener_manager, findByAddress(_)) + .WillOnce(testing::Return(internal_listener_opt)); + Network::ConnectionSocketPtr server_socket; + EXPECT_CALL(internal_listener, onAccept(_)).WillOnce([&](auto&& socket) { + server_socket = std::move(socket); + }); + + auto client_conn = dispatcher_->createClientConnection( + std::make_shared(listener_addr), + Network::Address::InstanceConstSharedPtr(), Network::Test::createRawBufferSocket(), nullptr); + + EXPECT_NE(nullptr, server_socket); + + EXPECT_NE(nullptr, client_conn); + EXPECT_TRUE(client_conn->connecting()); + client_conn->connect(); + + // Connect is successful but the connecting state takes another poll cycle to clear. + EXPECT_TRUE(client_conn->connecting()); + + Buffer::OwnedImpl buf_to_write("0123456789"); + + client_conn->write(buf_to_write, false); + dispatcher_->run(Event::Dispatcher::RunType::NonBlock); + // The write callback detects that connecting is completed. + EXPECT_FALSE(client_conn->connecting()); + + auto result = server_socket->ioHandle().recv(buf_.data(), buf_.size(), 0); + ASSERT_EQ(10, result.return_value_); + ASSERT_EQ("0123456789", absl::string_view(buf_.data(), result.return_value_)); + + client_conn->close(Network::ConnectionCloseType::NoFlush); + server_socket->close(); +} +} // namespace +} // namespace UserSpace +} // namespace IoSocket +} // namespace Extensions +} // namespace Envoy diff --git a/test/integration/socket_interface_integration_test.cc b/test/integration/socket_interface_integration_test.cc index 9cae29698a006..c0a4c987056e8 100644 --- a/test/integration/socket_interface_integration_test.cc +++ b/test/integration/socket_interface_integration_test.cc @@ -89,7 +89,8 @@ TEST_P(SocketInterfaceIntegrationTest, AddressWithSocketInterface) { client_->close(Network::ConnectionCloseType::FlushWrite); } -// Test that connecting to internal address will crash. +// Test that connecting to internal address will crash if the user space socket extension is not +// linked. TEST_P(SocketInterfaceIntegrationTest, InternalAddressWithSocketInterface) { BaseIntegrationTest::initialize(); @@ -103,7 +104,7 @@ TEST_P(SocketInterfaceIntegrationTest, InternalAddressWithSocketInterface) { ASSERT_DEATH(client_ = dispatcher_->createClientConnection( address, Network::Address::InstanceConstSharedPtr(), Network::Test::createRawBufferSocket(), nullptr), - "panic: not implemented"); + "" /* Nullptr dereference */); } // Test that recv from internal address will crash. diff --git a/test/mocks/event/mocks.h b/test/mocks/event/mocks.h index a155a75e75d10..cb86c15265c46 100644 --- a/test/mocks/event/mocks.h +++ b/test/mocks/event/mocks.h @@ -60,6 +60,9 @@ class MockDispatcher : public Dispatcher { createClientConnection_(address, source_address, transport_socket, options)}; } + MOCK_METHOD(Network::InternalListenerManagerOptRef, getInternalListenerManager, ()); + MOCK_METHOD(void, registerInternalListenerManager, (Network::InternalListenerManager&)); + FileEventPtr createFileEvent(os_fd_t fd, FileReadyCb cb, FileTriggerType trigger, uint32_t events) override { return FileEventPtr{createFileEvent_(fd, cb, trigger, events)}; diff --git a/test/mocks/event/wrapped_dispatcher.h b/test/mocks/event/wrapped_dispatcher.h index 634235cbbd632..0d990b725add2 100644 --- a/test/mocks/event/wrapped_dispatcher.h +++ b/test/mocks/event/wrapped_dispatcher.h @@ -50,6 +50,15 @@ class WrappedDispatcher : public Dispatcher { std::move(transport_socket), options); } + void registerInternalListenerManager( + Network::InternalListenerManager& internal_listener_manager) override { + impl_.registerInternalListenerManager(internal_listener_manager); + } + + Network::InternalListenerManagerOptRef getInternalListenerManager() override { + return impl_.getInternalListenerManager(); + } + FileEventPtr createFileEvent(os_fd_t fd, FileReadyCb cb, FileTriggerType trigger, uint32_t events) override { return impl_.createFileEvent(fd, cb, trigger, events);