diff --git a/api/envoy/config/listener/v3/internal_listener.proto b/api/envoy/config/listener/v3/internal_listener.proto new file mode 100644 index 0000000000000..8fb5bb8c78577 --- /dev/null +++ b/api/envoy/config/listener/v3/internal_listener.proto @@ -0,0 +1,20 @@ +syntax = "proto3"; + +package envoy.config.listener.v3; + +import "udpa/annotations/status.proto"; +import "udpa/annotations/versioning.proto"; + +option java_package = "io.envoyproxy.envoy.config.listener.v3"; +option java_outer_classname = "InternalListenerProto"; +option java_multiple_files = true; +option (udpa.annotations.file_status).package_version_status = ACTIVE; + +// [#not-implemented-hide:] +// [#protodoc-title: internal listener] +// Describes a type of internal listener which expects to serve the cluster in +// the same envoy process. +message InternalListener { + option (udpa.annotations.versioning).previous_message_type = + "envoy.config.listener.v2.InternalListener"; +} diff --git a/api/envoy/config/listener/v3/listener.proto b/api/envoy/config/listener/v3/listener.proto index dab0eb1ce68f2..6ee3cc8187e56 100644 --- a/api/envoy/config/listener/v3/listener.proto +++ b/api/envoy/config/listener/v3/listener.proto @@ -8,6 +8,7 @@ import "envoy/config/core/v3/base.proto"; import "envoy/config/core/v3/extension.proto"; import "envoy/config/core/v3/socket_option.proto"; import "envoy/config/listener/v3/api_listener.proto"; +import "envoy/config/listener/v3/internal_listener.proto"; import "envoy/config/listener/v3/listener_components.proto"; import "envoy/config/listener/v3/udp_listener_config.proto"; @@ -36,7 +37,7 @@ message ListenerCollection { repeated udpa.core.v1.CollectionEntry entries = 1; } -// [#next-free-field: 25] +// [#next-free-field: 26] message Listener { option (udpa.annotations.versioning).previous_message_type = "envoy.api.v2.Listener"; @@ -263,4 +264,10 @@ message Listener { // The maximum length a tcp listener's pending connections queue can grow to. If no value is // provided net.core.somaxconn will be used on Linux and 128 otherwise. google.protobuf.UInt32Value tcp_backlog_size = 24; + + // Used to represent an internal listener, which accepts connection from the cluster in the same envoy process. + // When this field is set, the address field must be :ref:`envoy internal address + // `. + // [#not-implemented-hide:] + InternalListener internal_listener = 25; } diff --git a/api/envoy/config/listener/v4alpha/internal_listener.proto b/api/envoy/config/listener/v4alpha/internal_listener.proto new file mode 100644 index 0000000000000..0bffb7e6626d6 --- /dev/null +++ b/api/envoy/config/listener/v4alpha/internal_listener.proto @@ -0,0 +1,20 @@ +syntax = "proto3"; + +package envoy.config.listener.v4alpha; + +import "udpa/annotations/status.proto"; +import "udpa/annotations/versioning.proto"; + +option java_package = "io.envoyproxy.envoy.config.listener.v4alpha"; +option java_outer_classname = "InternalListenerProto"; +option java_multiple_files = true; +option (udpa.annotations.file_status).package_version_status = NEXT_MAJOR_VERSION_CANDIDATE; + +// [#not-implemented-hide:] +// [#protodoc-title: internal listener] +// Describes a type of internal listener which expects to serve the cluster in +// the same envoy process. +message InternalListener { + option (udpa.annotations.versioning).previous_message_type = + "envoy.config.listener.v3.InternalListener"; +} diff --git a/api/envoy/config/listener/v4alpha/listener.proto b/api/envoy/config/listener/v4alpha/listener.proto index 3c9dced082b78..7784219183d6c 100644 --- a/api/envoy/config/listener/v4alpha/listener.proto +++ b/api/envoy/config/listener/v4alpha/listener.proto @@ -8,6 +8,7 @@ import "envoy/config/core/v4alpha/base.proto"; import "envoy/config/core/v4alpha/extension.proto"; import "envoy/config/core/v4alpha/socket_option.proto"; import "envoy/config/listener/v4alpha/api_listener.proto"; +import "envoy/config/listener/v4alpha/internal_listener.proto"; import "envoy/config/listener/v4alpha/listener_components.proto"; import "envoy/config/listener/v4alpha/udp_listener_config.proto"; @@ -39,7 +40,7 @@ message ListenerCollection { repeated udpa.core.v1.CollectionEntry entries = 1; } -// [#next-free-field: 25] +// [#next-free-field: 26] message Listener { option (udpa.annotations.versioning).previous_message_type = "envoy.config.listener.v3.Listener"; @@ -266,4 +267,10 @@ message Listener { // The maximum length a tcp listener's pending connections queue can grow to. If no value is // provided net.core.somaxconn will be used on Linux and 128 otherwise. google.protobuf.UInt32Value tcp_backlog_size = 24; + + // Used to represent an internal listener, which accepts connection from the cluster in the same envoy process. + // When this field is set, the address field must be :ref:`envoy internal address + // `. + // [#not-implemented-hide:] + InternalListener internal_listener = 25; } diff --git a/generated_api_shadow/envoy/config/listener/v3/internal_listener.proto b/generated_api_shadow/envoy/config/listener/v3/internal_listener.proto new file mode 100644 index 0000000000000..8fb5bb8c78577 --- /dev/null +++ b/generated_api_shadow/envoy/config/listener/v3/internal_listener.proto @@ -0,0 +1,20 @@ +syntax = "proto3"; + +package envoy.config.listener.v3; + +import "udpa/annotations/status.proto"; +import "udpa/annotations/versioning.proto"; + +option java_package = "io.envoyproxy.envoy.config.listener.v3"; +option java_outer_classname = "InternalListenerProto"; +option java_multiple_files = true; +option (udpa.annotations.file_status).package_version_status = ACTIVE; + +// [#not-implemented-hide:] +// [#protodoc-title: internal listener] +// Describes a type of internal listener which expects to serve the cluster in +// the same envoy process. +message InternalListener { + option (udpa.annotations.versioning).previous_message_type = + "envoy.config.listener.v2.InternalListener"; +} diff --git a/generated_api_shadow/envoy/config/listener/v3/listener.proto b/generated_api_shadow/envoy/config/listener/v3/listener.proto index 9d7bc38269e89..6ad008d88b655 100644 --- a/generated_api_shadow/envoy/config/listener/v3/listener.proto +++ b/generated_api_shadow/envoy/config/listener/v3/listener.proto @@ -8,6 +8,7 @@ import "envoy/config/core/v3/base.proto"; import "envoy/config/core/v3/extension.proto"; import "envoy/config/core/v3/socket_option.proto"; import "envoy/config/listener/v3/api_listener.proto"; +import "envoy/config/listener/v3/internal_listener.proto"; import "envoy/config/listener/v3/listener_components.proto"; import "envoy/config/listener/v3/udp_listener_config.proto"; @@ -36,7 +37,7 @@ message ListenerCollection { repeated udpa.core.v1.CollectionEntry entries = 1; } -// [#next-free-field: 25] +// [#next-free-field: 26] message Listener { option (udpa.annotations.versioning).previous_message_type = "envoy.api.v2.Listener"; @@ -262,5 +263,11 @@ message Listener { // provided net.core.somaxconn will be used on Linux and 128 otherwise. google.protobuf.UInt32Value tcp_backlog_size = 24; + // Used to represent an internal listener, which accepts connection from the cluster in the same envoy process. + // When this field is set, the address field must be :ref:`envoy internal address + // `. + // [#not-implemented-hide:] + InternalListener internal_listener = 25; + google.protobuf.BoolValue hidden_envoy_deprecated_use_original_dst = 4 [deprecated = true]; } diff --git a/generated_api_shadow/envoy/config/listener/v4alpha/internal_listener.proto b/generated_api_shadow/envoy/config/listener/v4alpha/internal_listener.proto new file mode 100644 index 0000000000000..0bffb7e6626d6 --- /dev/null +++ b/generated_api_shadow/envoy/config/listener/v4alpha/internal_listener.proto @@ -0,0 +1,20 @@ +syntax = "proto3"; + +package envoy.config.listener.v4alpha; + +import "udpa/annotations/status.proto"; +import "udpa/annotations/versioning.proto"; + +option java_package = "io.envoyproxy.envoy.config.listener.v4alpha"; +option java_outer_classname = "InternalListenerProto"; +option java_multiple_files = true; +option (udpa.annotations.file_status).package_version_status = NEXT_MAJOR_VERSION_CANDIDATE; + +// [#not-implemented-hide:] +// [#protodoc-title: internal listener] +// Describes a type of internal listener which expects to serve the cluster in +// the same envoy process. +message InternalListener { + option (udpa.annotations.versioning).previous_message_type = + "envoy.config.listener.v3.InternalListener"; +} diff --git a/generated_api_shadow/envoy/config/listener/v4alpha/listener.proto b/generated_api_shadow/envoy/config/listener/v4alpha/listener.proto index 3c9dced082b78..7784219183d6c 100644 --- a/generated_api_shadow/envoy/config/listener/v4alpha/listener.proto +++ b/generated_api_shadow/envoy/config/listener/v4alpha/listener.proto @@ -8,6 +8,7 @@ import "envoy/config/core/v4alpha/base.proto"; import "envoy/config/core/v4alpha/extension.proto"; import "envoy/config/core/v4alpha/socket_option.proto"; import "envoy/config/listener/v4alpha/api_listener.proto"; +import "envoy/config/listener/v4alpha/internal_listener.proto"; import "envoy/config/listener/v4alpha/listener_components.proto"; import "envoy/config/listener/v4alpha/udp_listener_config.proto"; @@ -39,7 +40,7 @@ message ListenerCollection { repeated udpa.core.v1.CollectionEntry entries = 1; } -// [#next-free-field: 25] +// [#next-free-field: 26] message Listener { option (udpa.annotations.versioning).previous_message_type = "envoy.config.listener.v3.Listener"; @@ -266,4 +267,10 @@ message Listener { // The maximum length a tcp listener's pending connections queue can grow to. If no value is // provided net.core.somaxconn will be used on Linux and 128 otherwise. google.protobuf.UInt32Value tcp_backlog_size = 24; + + // Used to represent an internal listener, which accepts connection from the cluster in the same envoy process. + // When this field is set, the address field must be :ref:`envoy internal address + // `. + // [#not-implemented-hide:] + InternalListener internal_listener = 25; } diff --git a/include/envoy/event/dispatcher.h b/include/envoy/event/dispatcher.h index 0e7865b656d83..c73ff46035792 100644 --- a/include/envoy/event/dispatcher.h +++ b/include/envoy/event/dispatcher.h @@ -113,6 +113,16 @@ class Dispatcher { Network::TransportSocketPtr&& transport_socket, const Network::ConnectionSocket::OptionsSharedPtr& options) PURE; + /** + * Creates an client internal connection. Does NOT initiate the connection; + * the caller must then call connect() on the returned Network::ClientConnection. + * @param internal_address supplies the internal address to connect to. + * @param local_address supplies an address to bind to or nullptr if no bind is necessary. + * @return Network::ClientConnectionPtr a client connection that is owned by the caller. + */ + virtual Network::ClientConnectionPtr + createInternalConnection(Network::Address::InstanceConstSharedPtr internal_address, + Network::Address::InstanceConstSharedPtr local_address) PURE; /** * Creates an async DNS resolver. The resolver should only be used on the thread that runs this * dispatcher. diff --git a/include/envoy/network/listener.h b/include/envoy/network/listener.h index f74d6416103ab..8459797a72125 100644 --- a/include/envoy/network/listener.h +++ b/include/envoy/network/listener.h @@ -146,6 +146,11 @@ class ListenerConfig { */ virtual UdpPacketWriterFactoryOptRef udpPacketWriterFactory() PURE; + /** + * @return true if this listener is internal listener. + */ + virtual bool isInternalListener() PURE; + /** * @return the ``UdpListenerWorkerRouter`` for this listener. This will * be non-empty iff this is a UDP listener. @@ -203,6 +208,18 @@ class TcpListenerCallbacks { virtual void onReject() PURE; }; +/** + * Callbacks invoked by a internal listener. + */ +class InternalListenerCallbacks { +public: + virtual ~InternalListenerCallbacks() = default; + + virtual void setupNewConnection(Network::ConnectionPtr server_conn, + Network::ConnectionSocketPtr socket) PURE; + virtual void onNewSocket(Network::ConnectionSocketPtr socket) PURE; +}; + /** * Utility struct that encapsulates the information from a udp socket's recvmmsg call. */ diff --git a/source/common/event/BUILD b/source/common/event/BUILD index cf0ded8373e92..a434899ad4d29 100644 --- a/source/common/event/BUILD +++ b/source/common/event/BUILD @@ -30,6 +30,7 @@ envoy_cc_library( "//source/common/common:assert_lib", "//source/common/common:thread_lib", "//source/common/filesystem:watcher_lib", + "//source/common/network:buffered_io_socket_handle_lib", "//source/common/network:connection_lib", "//source/common/network:dns_lib", "//source/common/network:listener_lib", diff --git a/source/common/event/dispatcher_impl.cc b/source/common/event/dispatcher_impl.cc index 3ce0a54fdfb0e..492a76bb31630 100644 --- a/source/common/event/dispatcher_impl.cc +++ b/source/common/event/dispatcher_impl.cc @@ -3,9 +3,12 @@ #include #include #include +#include #include #include +#include "common/network/address_impl.h" +#include "common/network/raw_buffer_socket.h" #include "envoy/api/api.h" #include "envoy/network/listen_socket.h" #include "envoy/network/listener.h" @@ -18,6 +21,7 @@ #include "common/event/signal_impl.h" #include "common/event/timer_impl.h" #include "common/filesystem/watcher_impl.h" +#include "common/network/buffered_io_socket_handle_impl.h" #include "common/network/connection_impl.h" #include "common/network/dns_impl.h" #include "common/network/tcp_listener_impl.h" @@ -117,6 +121,71 @@ DispatcherImpl::createClientConnection(Network::Address::InstanceConstSharedPtr std::move(transport_socket), options); } +namespace { +Network::Address::InstanceConstSharedPtr +nextClientAddress(const Network::Address::InstanceConstSharedPtr& server_address) { + uint64_t id = 0; + return std::make_shared(absl::StrCat(server_address->asStringView(), "_", ++id)); +} +} // namespace + +Network::ClientConnectionPtr +DispatcherImpl::createInternalConnection(Network::Address::InstanceConstSharedPtr internal_address, + Network::Address::InstanceConstSharedPtr local_address) { + ASSERT(isThreadSafe()); + if (internal_address == nullptr) { + return nullptr; + } + if (local_address == nullptr) { + local_address = nextClientAddress(internal_address); + } + // Find the internal listener callback. The listener will setup the server connection. + auto iter = internal_listeners_.find(internal_address->asString()); + for (const auto& [name, _] : internal_listeners_) { + ENVOY_LOG_MISC(debug, "lambdai: p listener {}", name); + } + if (iter == internal_listeners_.end()) { + ENVOY_LOG_MISC(debug, "lambdai: no valid listener registered for envoy internal address {}", + internal_address->asString()); + return std::make_unique(*this, internal_address, + local_address); + } + + auto client_io_handle_ = std::make_unique(); + auto server_io_handle_ = std::make_unique(); + client_io_handle_->setWritablePeer(server_io_handle_.get()); + server_io_handle_->setWritablePeer(client_io_handle_.get()); + + Network::RawBufferSocketFactory client_transport_socket_factory; + // ConnectionSocket conn_socket + auto client_conn_socket = std::make_unique( + std::move(client_io_handle_), local_address, internal_address); + auto server_conn_socket = std::make_unique( + std::move(server_io_handle_), internal_address, local_address); + ENVOY_LOG_MISC(debug, "lambdai: internal address {}", internal_address->asString()); + ENVOY_LOG_MISC(debug, "lambdai: client address {}", local_address->asString()); + + auto client_conn = std::make_unique( + *this, internal_address, local_address, + client_transport_socket_factory.createTransportSocket(nullptr), nullptr, + std::move(client_conn_socket)); + + (iter->second)(internal_address, std::move(server_conn_socket)); + return client_conn; +} + +void DispatcherImpl::registerInternalListener( + const std::string& internal_listener_id, + DispatcherImpl::InternalConnectionCallback internal_conn_callback) { + if (internal_conn_callback == nullptr) { + ENVOY_LOG_MISC(debug, "lambdai: unregister pipe factory on address {}", internal_listener_id); + internal_listeners_.erase(internal_listener_id); + } else { + ENVOY_LOG_MISC(debug, "lambdai: register pipe factory on address {}", internal_listener_id); + internal_listeners_[internal_listener_id] = internal_conn_callback; + } +} + Network::DnsResolverSharedPtr DispatcherImpl::createDnsResolver( const std::vector& resolvers, const bool use_tcp_for_dns_lookups) { diff --git a/source/common/event/dispatcher_impl.h b/source/common/event/dispatcher_impl.h index 4b05b355410ca..e3230a9e45ca9 100644 --- a/source/common/event/dispatcher_impl.h +++ b/source/common/event/dispatcher_impl.h @@ -12,6 +12,7 @@ #include "envoy/event/deferred_deletable.h" #include "envoy/event/dispatcher.h" #include "envoy/network/connection_handler.h" +#include "envoy/network/listen_socket.h" #include "envoy/stats/scope.h" #include "common/common/logger.h" @@ -21,6 +22,9 @@ #include "common/signal/fatal_error_handler.h" namespace Envoy { +namespace Network { +class BufferedIoSocketHandleImpl; +} namespace Event { /** @@ -34,7 +38,9 @@ class DispatcherImpl : Logger::Loggable, DispatcherImpl(const std::string& name, Buffer::WatermarkFactoryPtr&& factory, Api::Api& api, Event::TimeSystem& time_system); ~DispatcherImpl() override; - + using InternalConnectionCallback = + std::function internal_socket)>; /** * @return event_base& the libevent base. */ @@ -53,6 +59,14 @@ class DispatcherImpl : Logger::Loggable, Network::Address::InstanceConstSharedPtr source_address, Network::TransportSocketPtr&& transport_socket, const Network::ConnectionSocket::OptionsSharedPtr& options) override; + // Register the internal listener to setup the internal connection. Pass nullptr callback to + // unregister. + void registerInternalListener(const std::string& internal_listener_id, + InternalConnectionCallback internal_conn_callback); + + Network::ClientConnectionPtr + createInternalConnection(Network::Address::InstanceConstSharedPtr internal_address, + Network::Address::InstanceConstSharedPtr local_address) override; Network::DnsResolverSharedPtr createDnsResolver(const std::vector& resolvers, const bool use_tcp_for_dns_lookups) override; @@ -121,6 +135,7 @@ class DispatcherImpl : Logger::Loggable, const ScopeTrackedObject* current_object_{}; bool deferred_deleting_{}; MonotonicTime approximate_monotonic_time_; + absl::flat_hash_map internal_listeners_; }; } // namespace Event diff --git a/source/common/event/file_event_impl.h b/source/common/event/file_event_impl.h index cc3e505d788bb..f9c55dd4b9356 100644 --- a/source/common/event/file_event_impl.h +++ b/source/common/event/file_event_impl.h @@ -1,5 +1,7 @@ #pragma once +#include + #include #include "envoy/event/file_event.h" @@ -8,6 +10,10 @@ #include "common/event/event_impl_base.h" namespace Envoy { +namespace Network { +class BufferedIoSocketHandleImpl; +} + namespace Event { /** @@ -41,5 +47,110 @@ class FileEventImpl : public FileEvent, ImplBase { // polling and activating new fd events. const bool activate_fd_events_next_event_loop_; }; + +// Forward declare for friend class. +class UserSpaceFileEventFactory; + +class EventListener { +public: + virtual ~EventListener() = default; + virtual uint32_t triggeredEvents() PURE; + virtual void onEventEnabled(uint32_t enabled_events) PURE; + virtual void onEventActivated(uint32_t enabled_events) PURE; + virtual uint32_t getAndClearEpheralEvents() PURE; +}; + +// Return the enabled events except EV_CLOSED. This implementation is generally good since only +// epoll supports EV_CLOSED. The event owner must assume EV_CLOSED is not reliable. Also event owner +// must assume OS could notify events which are not actually triggered. +class DefaultEventListener : public EventListener { +public: + ~DefaultEventListener() override = default; + uint32_t triggeredEvents() override { + ENVOY_LOG_MISC(debug, "lambdai: user file event listener triggered events {} on {} and schedule next", pending_events_, static_cast(this)); + + return pending_events_ & (~Event::FileReadyType::Closed); } + void onEventEnabled(uint32_t enabled_events) override { + ENVOY_LOG_MISC(debug, "lambdai: user file event listener set enabled events {} on {} and schedule next", pending_events_, static_cast(this)); + pending_events_ = enabled_events; } + void onEventActivated(uint32_t activated_events) override { + ephermal_events_ |= activated_events; + } + uint32_t getAndClearEpheralEvents() override { + auto res = ephermal_events_; + ephermal_events_ = 0; + return res; + } + +private: + // The persisted interested events and ready events. + uint32_t pending_events_{}; + // The events set by activate() and will be cleared after the io callback. + uint32_t ephermal_events_{}; +}; + +// A FileEvent implementation which is +class UserSpaceFileEventImpl : public FileEvent { +public: + ~UserSpaceFileEventImpl() override { + // if (schedulable_.enabled()) { + schedulable_.cancel(); + //} + ASSERT(event_counter_ == 1); + --event_counter_; + } + + // Event::FileEvent + void activate(uint32_t events) override { + event_listener_.onEventActivated(events); + if (!schedulable_.enabled()) { + schedulable_.scheduleCallbackNextIteration(); + } + } + + void setEnabled(uint32_t events) override { + event_listener_.onEventEnabled(events); + if (!schedulable_.enabled()) { + schedulable_.scheduleCallbackNextIteration(); + ENVOY_LOG_MISC(debug, "lambdai: user file event setEnabled {} on {} and schedule next", events, static_cast(this)); + return; + } + ENVOY_LOG_MISC(debug, "lambdai: user file event setEnabled {} on {} and but not schedule next", events, static_cast(this)); + } + + EventListener& getEventListener() { return event_listener_; } + void onEvents() { cb_(); } + friend class UserSpaceFileEventFactory; + friend class Network::BufferedIoSocketHandleImpl; + +private: + UserSpaceFileEventImpl(Event::FileReadyCb cb, uint32_t events, + SchedulableCallback& schedulable_cb, int& event_counter) + : schedulable_(schedulable_cb), cb_([this, cb]() { + auto all_events = getEventListener().triggeredEvents(); + auto ephemeral_events = getEventListener().getAndClearEpheralEvents(); + ENVOY_LOG_MISC(debug, "lambdai: us event {} cb allevents = {}, ephermal events = {}", static_cast(this), all_events, ephemeral_events); + cb(all_events | ephemeral_events); + }), + event_counter_(event_counter) { + event_listener_.onEventEnabled(events); + } + DefaultEventListener event_listener_; + SchedulableCallback& schedulable_; + std::function cb_; + int& event_counter_; +}; + +class UserSpaceFileEventFactory { +public: + static std::unique_ptr + createUserSpaceFileEventImpl(Event::Dispatcher&, Event::FileReadyCb cb, Event::FileTriggerType, + uint32_t events, SchedulableCallback& scheduable_cb, + int& event_counter) { + return std::unique_ptr( + new UserSpaceFileEventImpl(cb, events, scheduable_cb, event_counter)); + } +}; + } // namespace Event } // namespace Envoy diff --git a/source/common/event/schedulable_cb_impl.cc b/source/common/event/schedulable_cb_impl.cc index 2109af17972eb..81f4efacb476d 100644 --- a/source/common/event/schedulable_cb_impl.cc +++ b/source/common/event/schedulable_cb_impl.cc @@ -9,7 +9,8 @@ namespace Event { SchedulableCallbackImpl::SchedulableCallbackImpl(Libevent::BasePtr& libevent, std::function cb) - : cb_(cb) { + : cb_(cb) { + ENVOY_LOG_MISC(debug, "lambdai: construct SchedulableCallbackImpl {}", static_cast(this)); ASSERT(cb_); evtimer_assign( &raw_event_, libevent.get(), @@ -31,8 +32,10 @@ void SchedulableCallbackImpl::scheduleCallbackCurrentIteration() { void SchedulableCallbackImpl::scheduleCallbackNextIteration() { if (enabled()) { + ENVOY_LOG_MISC(debug, "lambdai: SchedulableCallbackImpl {} scheduleCallbackNextIteration is enabled. won't reschedule", static_cast(this)); return; } + ENVOY_LOG_MISC(debug, "lambdai: SchedulableCallbackImpl {} scheduleCallbackNextIteration is not schedule enabled. Will reschedule. ", static_cast(this)); // libevent computes the list of timers to move to the work list after polling for fd events, but // iteration through the work list starts. Zero delay timers added while iterating through the // work list execute on the next iteration of the event loop. diff --git a/source/common/network/BUILD b/source/common/network/BUILD index 6def5e024a4cb..75d8c421d4c85 100644 --- a/source/common/network/BUILD +++ b/source/common/network/BUILD @@ -145,6 +145,7 @@ envoy_cc_library( hdrs = ["io_socket_error_impl.h"], deps = [ "//include/envoy/api:io_error_interface", + "//include/envoy/api:os_sys_calls_interface", "//source/common/common:assert_lib", "//source/common/common:utility_lib", ], @@ -226,15 +227,25 @@ envoy_cc_library( ], ) +envoy_cc_library( + name = "generic_listener_filter_lib", + hdrs = ["generic_listener_filter.h"], + deps = [ + "//include/envoy/network:filter_interface", + ], +) + envoy_cc_library( name = "listener_lib", srcs = [ "base_listener_impl.cc", + "internal_listener_impl.cc", "tcp_listener_impl.cc", "udp_listener_impl.cc", ], hdrs = [ "base_listener_impl.h", + "internal_listener_impl.h", "tcp_listener_impl.h", "udp_listener_impl.h", ], @@ -254,6 +265,7 @@ envoy_cc_library( "//source/common/common:empty_string", "//source/common/common:linked_object", "//source/common/event:dispatcher_includes", + "//source/common/network:buffered_io_socket_handle_lib", "@envoy_api//envoy/config/core/v3:pkg_cc_proto", ], ) @@ -448,3 +460,27 @@ envoy_cc_library( "//source/common/common:macros", ], ) + +envoy_cc_library( + name = "peer_buffer_lib", + hdrs = ["peer_buffer.h"], + deps = [ + ":utility_lib", + "//include/envoy/network:connection_interface", + "//include/envoy/network:transport_socket_interface", + "//source/common/buffer:buffer_lib", + "//source/common/buffer:watermark_buffer_lib", + "//source/common/common:empty_string", + "//source/common/http:headers_lib", + ], +) + +envoy_cc_library( + name = "buffered_io_socket_handle_lib", + srcs = ["buffered_io_socket_handle_impl.cc"], + hdrs = ["buffered_io_socket_handle_impl.h"], + deps = [ + "default_socket_interface_lib", + ":peer_buffer_lib", + ], +) diff --git a/source/common/network/base_listener_impl.cc b/source/common/network/base_listener_impl.cc index e1adf6b930ec1..bc50038bbbf97 100644 --- a/source/common/network/base_listener_impl.cc +++ b/source/common/network/base_listener_impl.cc @@ -15,14 +15,18 @@ namespace Envoy { namespace Network { -BaseListenerImpl::BaseListenerImpl(Event::DispatcherImpl& dispatcher, SocketSharedPtr socket) - : local_address_(nullptr), dispatcher_(dispatcher), socket_(std::move(socket)) { - const auto ip = socket_->localAddress()->ip(); +BaseListenerImpl::BaseListenerImpl(Event::DispatcherImpl& dispatcher, SocketSharedPtr socket, + const Address::InstanceConstSharedPtr& local_address) + : local_address_(local_address), dispatcher_(dispatcher), socket_(std::move(socket)) { + + if (socket_ != nullptr) { + const auto ip = socket_->localAddress()->ip(); - // Only use the listen socket's local address for new connections if it is not the all hosts - // address (e.g., 0.0.0.0 for IPv4). - if (!(ip && ip->isAnyAddress())) { - local_address_ = socket_->localAddress(); + // Only use the listen socket's local address for new connections if it is not the all hosts + // address (e.g., 0.0.0.0 for IPv4). + if (!(ip && ip->isAnyAddress())) { + local_address_ = socket_->localAddress(); + } } } diff --git a/source/common/network/base_listener_impl.h b/source/common/network/base_listener_impl.h index 2cf97dea86c4a..0f09bdfd13113 100644 --- a/source/common/network/base_listener_impl.h +++ b/source/common/network/base_listener_impl.h @@ -15,9 +15,11 @@ class BaseListenerImpl : public virtual Listener { public: /** * @param socket the listening socket for this listener. It might be shared - * with other listeners if all listeners use single listen socket. + * with other listeners if all listeners use single listen socket. It could be nullptr for + * internal listener. */ - BaseListenerImpl(Event::DispatcherImpl& dispatcher, SocketSharedPtr socket); + BaseListenerImpl(Event::DispatcherImpl& dispatcher, SocketSharedPtr socket, + const Address::InstanceConstSharedPtr& local_address = nullptr); protected: Address::InstanceConstSharedPtr local_address_; diff --git a/source/common/network/buffered_io_socket_handle_impl.cc b/source/common/network/buffered_io_socket_handle_impl.cc new file mode 100644 index 0000000000000..0d4e740d35fa8 --- /dev/null +++ b/source/common/network/buffered_io_socket_handle_impl.cc @@ -0,0 +1,196 @@ +#include "common/network/buffered_io_socket_handle_impl.h" + +#include "envoy/buffer/buffer.h" +#include "envoy/common/platform.h" + +#include "common/api/os_sys_calls_impl.h" +#include "common/common/assert.h" +#include "common/common/utility.h" +#include "common/event/file_event_impl.h" +#include "common/network/address_impl.h" + +#include "absl/container/fixed_array.h" +#include "absl/types/optional.h" + +namespace Envoy { +namespace Network { + +Api::IoCallUint64Result BufferedIoSocketHandleImpl::close() { + ASSERT(!closed_); + if (!write_shutdown_) { + ASSERT(writable_peer_); + // Notify the peer we won't write more data. shutdown(WRITE). + writable_peer_->setWriteEnd(); + // Notify the peer that we no longer accept data. shutdown(RD). + writable_peer_->onPeerDestroy(); + writable_peer_->maybeSetNewData(); + writable_peer_ = nullptr; + write_shutdown_ = true; + } + closed_ = true; + return Api::ioCallUint64ResultNoError(); +} + +bool BufferedIoSocketHandleImpl::isOpen() const { return !closed_; } + +Api::IoCallUint64Result BufferedIoSocketHandleImpl::readv(uint64_t max_length, + Buffer::RawSlice* slices, + uint64_t num_slice) { + if (owned_buffer_.length() == 0) { + if (read_end_stream_) { + return Api::ioCallUint64ResultNoError(); + } else { + return {0, Api::IoErrorPtr(IoSocketError::getIoSocketEagainInstance(), + IoSocketError::deleteIoError)}; + } + } else { + absl::FixedArray iov(num_slice); + uint64_t num_slices_to_read = 0; + uint64_t num_bytes_to_read = 0; + for (; num_slices_to_read < num_slice && num_bytes_to_read < max_length; num_slices_to_read++) { + auto min_len = std::min(std::min(owned_buffer_.length(), max_length) - num_bytes_to_read, + uint64_t(slices[num_slices_to_read].len_)); + owned_buffer_.copyOut(num_bytes_to_read, min_len, slices[num_slices_to_read].mem_); + num_bytes_to_read += min_len; + } + ASSERT(num_bytes_to_read <= max_length); + owned_buffer_.drain(num_bytes_to_read); + ENVOY_LOG_MISC(debug, "lambdai: readv {} on {}", num_bytes_to_read, static_cast(this)); + return {num_bytes_to_read, Api::IoErrorPtr(nullptr, [](Api::IoError*) {})}; + } +} + +Api::IoCallUint64Result BufferedIoSocketHandleImpl::writev(const Buffer::RawSlice* slices, + uint64_t num_slice) { + if (!writable_peer_) { + return sysCallResultToIoCallResult(Api::SysCallSizeResult{-1, SOCKET_ERROR_INTR}); + } + if (writable_peer_->isWriteEndSet() || !writable_peer_->isWritable()) { + return {0, Api::IoErrorPtr(IoSocketError::getIoSocketEagainInstance(), + IoSocketError::deleteIoError)}; + } + // Write along with iteration. Buffer guarantee the fragment is always append-able. + uint64_t num_bytes_to_write = 0; + for (uint64_t i = 0; i < num_slice; i++) { + if (slices[i].mem_ != nullptr && slices[i].len_ != 0) { + writable_peer_->getWriteBuffer()->add(slices[i].mem_, slices[i].len_); + num_bytes_to_write += slices[i].len_; + } + } + writable_peer_->maybeSetNewData(); + ENVOY_LOG_MISC(debug, "lambdai: writev {} on {}", num_bytes_to_write, static_cast(this)); + return {num_bytes_to_write, Api::IoErrorPtr(nullptr, [](Api::IoError*) {})}; +} + +Api::IoCallUint64Result BufferedIoSocketHandleImpl::sendmsg(const Buffer::RawSlice*, uint64_t, int, + const Address::Ip*, + const Address::Instance&) { + return IoSocketError::ioResultSocketInvalidAddress(); +} + +Api::IoCallUint64Result BufferedIoSocketHandleImpl::recvmsg(Buffer::RawSlice*, const uint64_t, + uint32_t, RecvMsgOutput&) { + return IoSocketError::ioResultSocketInvalidAddress(); +} + +Api::IoCallUint64Result BufferedIoSocketHandleImpl::recvmmsg(RawSliceArrays&, uint32_t, + RecvMsgOutput&) { + return IoSocketError::ioResultSocketInvalidAddress(); +} + +Api::IoCallUint64Result BufferedIoSocketHandleImpl::recv(void* buffer, size_t length, int flags) { + // No data and the writer closed. + if (owned_buffer_.length() == 0) { + if (read_end_stream_) { + return sysCallResultToIoCallResult(Api::SysCallSizeResult{-1, SOCKET_ERROR_INTR}); + } else { + return {0, Api::IoErrorPtr(IoSocketError::getIoSocketEagainInstance(), + IoSocketError::deleteIoError)}; + } + } else { + auto min_len = std::min(owned_buffer_.length(), length); + owned_buffer_.copyOut(0, min_len, buffer); + if (!(flags & MSG_PEEK)) { + owned_buffer_.drain(min_len); + } + return {min_len, Api::IoErrorPtr(nullptr, [](Api::IoError*) {})}; + } +} + +bool BufferedIoSocketHandleImpl::supportsMmsg() const { return false; } + +bool BufferedIoSocketHandleImpl::supportsUdpGro() const { return false; } + +Api::SysCallIntResult makeInvalidSyscall() { + return Api::SysCallIntResult{-1, SOCKET_ERROR_NOT_SUP /*SOCKET_ERROR_NOT_SUP*/}; +} + +Api::SysCallIntResult BufferedIoSocketHandleImpl::bind(Address::InstanceConstSharedPtr) { + return makeInvalidSyscall(); +} + +Api::SysCallIntResult BufferedIoSocketHandleImpl::listen(int) { return makeInvalidSyscall(); } + +IoHandlePtr BufferedIoSocketHandleImpl::accept(struct sockaddr*, socklen_t*) { + + NOT_IMPLEMENTED_GCOVR_EXCL_LINE; +} + +Api::SysCallIntResult BufferedIoSocketHandleImpl::connect(Address::InstanceConstSharedPtr) { + // Buffered Io handle should always be considered as connected. Use write to determine if peer is + // closed. + return {0, 0}; +} + +Api::SysCallIntResult BufferedIoSocketHandleImpl::setOption(int, int, const void*, socklen_t) { + return makeInvalidSyscall(); +} + +Api::SysCallIntResult BufferedIoSocketHandleImpl::getOption(int, int, void*, socklen_t*) { + return makeInvalidSyscall(); +} + +Api::SysCallIntResult BufferedIoSocketHandleImpl::setBlocking(bool) { return makeInvalidSyscall(); } + +absl::optional BufferedIoSocketHandleImpl::domain() { return absl::nullopt; } + +Address::InstanceConstSharedPtr BufferedIoSocketHandleImpl::localAddress() { + throw EnvoyException(fmt::format("getsockname failed for BufferedIoSocketHandleImpl")); +} + +Address::InstanceConstSharedPtr BufferedIoSocketHandleImpl::peerAddress() { + + throw EnvoyException(fmt::format("getsockname failed for BufferedIoSocketHandleImpl")); +} + +Event::FileEventPtr BufferedIoSocketHandleImpl::createFileEvent(Event::Dispatcher& dispatcher, + Event::FileReadyCb cb, + Event::FileTriggerType trigger_type, + uint32_t events) { + ASSERT(event_counter_ == 0); + ++event_counter_; + io_callback_ = dispatcher.createSchedulableCallback([this]() { user_file_event_->onEvents(); }); + auto res = Event::UserSpaceFileEventFactory::createUserSpaceFileEventImpl( + dispatcher, cb, trigger_type, events, *io_callback_, event_counter_); + user_file_event_ = res.get(); + // Blindly activate the events. + io_callback_->scheduleCallbackNextIteration(); + return res; +} + +Api::SysCallIntResult BufferedIoSocketHandleImpl::shutdown(int how) { + if ((how == ENVOY_SHUT_WR) || (how == ENVOY_SHUT_RDWR)) { + ASSERT(!closed_); + if (!write_shutdown_) { + ASSERT(writable_peer_); + // Notify the peer we won't write more data. shutdown(WRITE). + writable_peer_->setWriteEnd(); + writable_peer_->maybeSetNewData(); + write_shutdown_ = true; + } + } + return {0, 0}; +} + +} // namespace Network +} // namespace Envoy \ No newline at end of file diff --git a/source/common/network/buffered_io_socket_handle_impl.h b/source/common/network/buffered_io_socket_handle_impl.h new file mode 100644 index 0000000000000..ee9bbb1458f3b --- /dev/null +++ b/source/common/network/buffered_io_socket_handle_impl.h @@ -0,0 +1,159 @@ +#pragma once + +#include + +#include "envoy/api/io_error.h" +#include "envoy/api/os_sys_calls.h" +#include "envoy/common/platform.h" +#include "envoy/event/dispatcher.h" +#include "envoy/network/io_handle.h" + +#include "common/buffer/watermark_buffer.h" +#include "common/common/logger.h" +#include "common/event/file_event_impl.h" +#include "common/network/io_socket_error_impl.h" +#include "common/network/peer_buffer.h" + +namespace Envoy { +namespace Network { + +/** + * IoHandle implementation which provides a buffer as data source. + */ +class BufferedIoSocketHandleImpl : public IoHandle, + public WritablePeer, + public ReadableSource, + protected Logger::Loggable { +public: + BufferedIoSocketHandleImpl() + : closed_{false}, + owned_buffer_( + [this]() -> void { + over_high_watermark_ = false; + triggered_high_to_low_watermark_ = true; + if (writable_peer_) { + ENVOY_LOG_MISC(debug, "lambdai: {} switch to low water mark {}", + static_cast(this), static_cast(writable_peer_)); + writable_peer_->onPeerBufferWritable(); + } + }, + [this]() -> void { + over_high_watermark_ = true; + // low to high is checked by peer after peer writes data. + }, + []() -> void {}) {} + + ~BufferedIoSocketHandleImpl() override { ASSERT(closed_); } + + // IoHandle + os_fd_t fdDoNotUse() const override { return INVALID_SOCKET; } + + Api::IoCallUint64Result close() override; + + bool isOpen() const override; + + Api::IoCallUint64Result readv(uint64_t max_length, Buffer::RawSlice* slices, + uint64_t num_slice) override; + + Api::IoCallUint64Result writev(const Buffer::RawSlice* slices, uint64_t num_slice) override; + + Api::IoCallUint64Result sendmsg(const Buffer::RawSlice* slices, uint64_t num_slice, int flags, + const Address::Ip* self_ip, + const Address::Instance& peer_address) override; + + Api::IoCallUint64Result recvmsg(Buffer::RawSlice* slices, const uint64_t num_slice, + uint32_t self_port, RecvMsgOutput& output) override; + + Api::IoCallUint64Result recvmmsg(RawSliceArrays& slices, uint32_t self_port, + RecvMsgOutput& output) override; + Api::IoCallUint64Result recv(void* buffer, size_t length, int flags) override; + + bool supportsMmsg() const override; + bool supportsUdpGro() const override; + + Api::SysCallIntResult bind(Address::InstanceConstSharedPtr address) override; + Api::SysCallIntResult listen(int backlog) override; + IoHandlePtr accept(struct sockaddr* addr, socklen_t* addrlen) override; + Api::SysCallIntResult connect(Address::InstanceConstSharedPtr address) override; + Api::SysCallIntResult setOption(int level, int optname, const void* optval, + socklen_t optlen) override; + Api::SysCallIntResult getOption(int level, int optname, void* optval, socklen_t* optlen) override; + Api::SysCallIntResult setBlocking(bool blocking) override; + absl::optional domain() override; + Address::InstanceConstSharedPtr localAddress() override; + Address::InstanceConstSharedPtr peerAddress() override; + Event::FileEventPtr createFileEvent(Event::Dispatcher& dispatcher, Event::FileReadyCb cb, + Event::FileTriggerType trigger, uint32_t events) override; + Api::SysCallIntResult shutdown(int how) override; + + Buffer::WatermarkBuffer& getBufferForTest() { return owned_buffer_; } + void scheduleWriteEvent() {} + void scheduleReadEvent() {} + + void scheduleNextEvent() { + // It's possible there is no pending file event so as no io_callback. + if (io_callback_) { + ENVOY_LOG_MISC(debug, "lambdai: has io_callback {} on {}", __FUNCTION__, static_cast(this)); + io_callback_->scheduleCallbackNextIteration(); + return; + } + ENVOY_LOG_MISC(debug, "lambdai: no io_callback {} on {}", __FUNCTION__, static_cast(this)); + } + + void setWritablePeer(WritablePeer* writable_peer) { + // Swapping writable peer is undefined behavior. + ASSERT(!writable_peer_); + ASSERT(!write_shutdown_); + writable_peer_ = writable_peer; + } + + // WritablePeer + void setWriteEnd() override { read_end_stream_ = true; } + bool isWriteEndSet() override { return read_end_stream_; } + void maybeSetNewData() override { + ENVOY_LOG_MISC(debug, "lambdai: {} on {}", __FUNCTION__, static_cast(this)); + scheduleReadEvent(); + scheduleNextEvent(); + } + void onPeerDestroy() override { + writable_peer_ = nullptr; + write_shutdown_ = true; + } + void onPeerBufferWritable() override { + scheduleWriteEvent(); + scheduleNextEvent(); + } + bool isWritable() const override { return !isOverHighWatermark(); } + Buffer::Instance* getWriteBuffer() override { return &owned_buffer_; } + // ReadableSource + bool isPeerShutDownWrite() const override { return read_end_stream_; } + bool isOverHighWatermark() const override { return over_high_watermark_; } + bool isReadable() const override { return isPeerShutDownWrite() || owned_buffer_.length() > 0; } + +private: + // Support isOpen() and close(). IoHandle owner must invoke close() to avoid potential resource + // leak. + bool closed_; + + Event::UserSpaceFileEventImpl* user_file_event_; + int event_counter_{0}; + // Trigger of the io event. + Event::SchedulableCallbackPtr io_callback_; + + // True if owned_buffer_ is not addable. Note that owned_buffer_ may have pending data to drain. + bool read_end_stream_{false}; + Buffer::WatermarkBuffer owned_buffer_; + + // bool shutdown_{false}; + // Destination of the write(). + WritablePeer* writable_peer_{nullptr}; + + // The flag whether the peer is valid. Any write attempt should check flag. + bool write_shutdown_{false}; + + bool over_high_watermark_{false}; + bool triggered_high_to_low_watermark_{true}; +}; + +} // namespace Network +} // namespace Envoy \ No newline at end of file diff --git a/source/common/network/connection_impl.cc b/source/common/network/connection_impl.cc index f297d9cc024bc..33e365579194c 100644 --- a/source/common/network/connection_impl.cc +++ b/source/common/network/connection_impl.cc @@ -13,6 +13,7 @@ #include "common/common/assert.h" #include "common/common/empty_string.h" #include "common/common/enum_to_int.h" +#include "common/common/macros.h" #include "common/network/address_impl.h" #include "common/network/listen_socket_impl.h" #include "common/network/raw_buffer_socket.h" @@ -69,7 +70,7 @@ ConnectionImpl::ConnectionImpl(Event::Dispatcher& dispatcher, ConnectionSocketPt file_event_ = socket_->ioHandle().createFileEvent( dispatcher_, [this](uint32_t events) -> void { onFileEvent(events); }, trigger, Event::FileReadyType::Read | Event::FileReadyType::Write); - + ENVOY_LOG_MISC(debug, "lambdai: create file event {} on connection {}", static_cast(file_event_.get()), id()); transport_socket_->setTransportSocketCallbacks(*this); } @@ -706,7 +707,17 @@ ClientConnectionImpl::ClientConnectionImpl( const Network::Address::InstanceConstSharedPtr& source_address, Network::TransportSocketPtr&& transport_socket, const Network::ConnectionSocket::OptionsSharedPtr& options) - : ConnectionImpl(dispatcher, std::make_unique(remote_address, options), + : ClientConnectionImpl(dispatcher, remote_address, source_address, + std::move(transport_socket), options, std::make_unique(remote_address, options)) { +} + +ClientConnectionImpl::ClientConnectionImpl( + Event::Dispatcher& dispatcher, const Address::InstanceConstSharedPtr& remote_address, + const Network::Address::InstanceConstSharedPtr& source_address, + Network::TransportSocketPtr&& transport_socket, + const Network::ConnectionSocket::OptionsSharedPtr& options, + ConnectionSocketPtr conn_socket) + : ConnectionImpl(dispatcher, std::move(conn_socket), std::move(transport_socket), stream_info_, false), stream_info_(dispatcher.timeSource()) { // There are no meaningful socket options or source address semantics for @@ -774,5 +785,95 @@ void ClientConnectionImpl::connect() { } } } + +ClosingClientConnectionImpl::ClosingClientConnectionImpl( + Event::Dispatcher& dispatcher, const Address::InstanceConstSharedPtr& remote_address, + const Network::Address::InstanceConstSharedPtr& source_address) + + : dispatcher_(dispatcher), + immediate_close_timer_(dispatcher.createTimer([this]() { closeSocket(); })), + remote_address_(remote_address), source_address_(source_address), + stream_info_(dispatcher.timeSource()) { + immediate_close_timer_->enableTimer(std::chrono::milliseconds(0)); +} +void ClosingClientConnectionImpl::connect() {} + +void ClosingClientConnectionImpl::addFilter(FilterSharedPtr) {} +void ClosingClientConnectionImpl::addWriteFilter(WriteFilterSharedPtr) {} +void ClosingClientConnectionImpl::addReadFilter(ReadFilterSharedPtr) {} +bool ClosingClientConnectionImpl::initializeReadFilters() { return true; } +void ClosingClientConnectionImpl::addConnectionCallbacks(ConnectionCallbacks& cb) { + callbacks_.push_back(&cb); +} +void ClosingClientConnectionImpl::addBytesSentCallback(BytesSentCb) {} +void ClosingClientConnectionImpl::enableHalfClose(bool) {} +void ClosingClientConnectionImpl::close(ConnectionCloseType) { + if (is_closed_) { + ASSERT(!immediate_close_timer_->enabled()); + return; + } + closeSocket(); +} +Event::Dispatcher& ClosingClientConnectionImpl::dispatcher() { return dispatcher_; } +uint64_t ClosingClientConnectionImpl::id() const { return 0; } +void ClosingClientConnectionImpl::hashKey(std::vector&) const {} +std::string ClosingClientConnectionImpl::nextProtocol() const { return EMPTY_STRING; } +void ClosingClientConnectionImpl::noDelay(bool) {} +void ClosingClientConnectionImpl::readDisable(bool) { + // Always enable read since we want to trigger the read and lead to close(). +} +void ClosingClientConnectionImpl::detectEarlyCloseWhenReadDisabled(bool) {} +bool ClosingClientConnectionImpl::readEnabled() const { return true; } +const Network::Address::InstanceConstSharedPtr& ClosingClientConnectionImpl::remoteAddress() const { + return remote_address_; +} +// TODO(lambdai): add transport socket option add set up the remote address. +const Network::Address::InstanceConstSharedPtr& +ClosingClientConnectionImpl::directRemoteAddress() const { + return remote_address_; +} +absl::optional +ClosingClientConnectionImpl::unixSocketPeerCredentials() const { + return absl::nullopt; +} +const Network::Address::InstanceConstSharedPtr& ClosingClientConnectionImpl::localAddress() const { + return source_address_; +} +void ClosingClientConnectionImpl::setConnectionStats(const ConnectionStats& stats) { + UNREFERENCED_PARAMETER(stats); +} +Ssl::ConnectionInfoConstSharedPtr ClosingClientConnectionImpl::ssl() const { return nullptr; } +absl::string_view ClosingClientConnectionImpl::requestedServerName() const { return EMPTY_STRING; } +Connection::State ClosingClientConnectionImpl::state() const { + return is_closed_ ? Connection::State::Closed : Connection::State::Open; +} +void ClosingClientConnectionImpl::write(Buffer::Instance&, bool) {} +void ClosingClientConnectionImpl::setBufferLimits(uint32_t) {} +uint32_t ClosingClientConnectionImpl::bufferLimit() const { return 65000; } +bool ClosingClientConnectionImpl::localAddressRestored() const { return true; } +bool ClosingClientConnectionImpl::aboveHighWatermark() const { return false; } +const ConnectionSocket::OptionsSharedPtr& ClosingClientConnectionImpl::socketOptions() const { + return socket_options_; +} +StreamInfo::StreamInfo& ClosingClientConnectionImpl::streamInfo() { return stream_info_; } +const StreamInfo::StreamInfo& ClosingClientConnectionImpl::streamInfo() const { + return stream_info_; +} +void ClosingClientConnectionImpl::setDelayedCloseTimeout(std::chrono::milliseconds) {} +absl::string_view ClosingClientConnectionImpl::transportFailureReason() const { + return EMPTY_STRING; +} + +void ClosingClientConnectionImpl::closeSocket() { + if (is_closed_) { + ASSERT(!immediate_close_timer_->enabled()); + return; + } + is_closed_ = true; + for (ConnectionCallbacks* callback : callbacks_) { + callback->onEvent(ConnectionEvent::RemoteClose); + } + immediate_close_timer_->disableTimer(); +} } // namespace Network } // namespace Envoy diff --git a/source/common/network/connection_impl.h b/source/common/network/connection_impl.h index 17ebe609a2630..039bb802cd8b5 100644 --- a/source/common/network/connection_impl.h +++ b/source/common/network/connection_impl.h @@ -6,6 +6,7 @@ #include #include +#include "envoy/event/timer.h" #include "envoy/network/transport_socket.h" #include "common/buffer/watermark_buffer.h" @@ -208,11 +209,78 @@ class ClientConnectionImpl : public ConnectionImpl, virtual public ClientConnect Network::TransportSocketPtr&& transport_socket, const Network::ConnectionSocket::OptionsSharedPtr& options); + ClientConnectionImpl(Event::Dispatcher& dispatcher, + const Address::InstanceConstSharedPtr& remote_address, + const Network::Address::InstanceConstSharedPtr& source_address, + Network::TransportSocketPtr&& transport_socket, + const Network::ConnectionSocket::OptionsSharedPtr& options, + ConnectionSocketPtr conn_socket); + + // Network::ClientConnection + void connect() override; + +private: + StreamInfo::StreamInfoImpl stream_info_; +}; + +/** + * libevent implementation of Network::ClientConnection. It schedule close() immediately. + */ +class ClosingClientConnectionImpl : virtual public ClientConnection { +public: + ClosingClientConnectionImpl(Event::Dispatcher& dispatcher, + const Address::InstanceConstSharedPtr& remote_address, + const Address::InstanceConstSharedPtr& source_address); + // Network::ClientConnection void connect() override; + void addFilter(FilterSharedPtr filter) override; + void addReadFilter(ReadFilterSharedPtr filter) override; + void addWriteFilter(WriteFilterSharedPtr filter) override; + bool initializeReadFilters() override; + void addConnectionCallbacks(ConnectionCallbacks& cb) override; + void addBytesSentCallback(BytesSentCb) override; + void enableHalfClose(bool enabled) override; + void close(Network::ConnectionCloseType type) override; + Event::Dispatcher& dispatcher() override; + uint64_t id() const override; + void hashKey(std::vector& hash) const override; + std::string nextProtocol() const override; + void noDelay(bool enable) override; + void readDisable(bool disable) override; + void detectEarlyCloseWhenReadDisabled(bool should_detect) override; + bool readEnabled() const override; + const Network::Address::InstanceConstSharedPtr& remoteAddress() const override; + const Network::Address::InstanceConstSharedPtr& directRemoteAddress() const override; + absl::optional unixSocketPeerCredentials() const override; + const Network::Address::InstanceConstSharedPtr& localAddress() const override; + void setConnectionStats(const ConnectionStats& stats) override; + Ssl::ConnectionInfoConstSharedPtr ssl() const override; + absl::string_view requestedServerName() const override; + State state() const override; + void write(Buffer::Instance& data, bool end_stream) override; + void setBufferLimits(uint32_t limit) override; + uint32_t bufferLimit() const override; + bool localAddressRestored() const override; + bool aboveHighWatermark() const override; + const ConnectionSocket::OptionsSharedPtr& socketOptions() const override; + StreamInfo::StreamInfo& streamInfo() override; + const StreamInfo::StreamInfo& streamInfo() const override; + void setDelayedCloseTimeout(std::chrono::milliseconds timeout) override; + absl::string_view transportFailureReason() const override; private: + void closeSocket(); + +public: + Event::Dispatcher& dispatcher_; + Event::TimerPtr immediate_close_timer_; + const Network::Address::InstanceConstSharedPtr remote_address_; + const Network::Address::InstanceConstSharedPtr source_address_; + ConnectionSocket::OptionsSharedPtr socket_options_; StreamInfo::StreamInfoImpl stream_info_; + std::list callbacks_; + bool is_closed_{false}; }; } // namespace Network diff --git a/source/common/network/generic_listener_filter.h b/source/common/network/generic_listener_filter.h new file mode 100644 index 0000000000000..ed04783aa1a36 --- /dev/null +++ b/source/common/network/generic_listener_filter.h @@ -0,0 +1,40 @@ +#include "envoy/network/filter.h" + +namespace Envoy { +namespace Network { + +/** + * GenericListenerFilter wraps another ListenerFilter to provides the common listener filter + * attributes such as ListenerFilterMatcher. + */ +class GenericListenerFilter : public Network::ListenerFilter { +public: + GenericListenerFilter(const Network::ListenerFilterMatcherSharedPtr& matcher, + Network::ListenerFilterPtr listener_filter) + : listener_filter_(std::move(listener_filter)), matcher_(std::move(matcher)) {} + Network::FilterStatus onAccept(ListenerFilterCallbacks& cb) override { + if (isDisabled(cb)) { + return Network::FilterStatus::Continue; + } + return listener_filter_->onAccept(cb); + } + /** + * Check if this filter filter should be disabled on the incoming socket. + * @param cb the callbacks the filter instance can use to communicate with the filter chain. + **/ + bool isDisabled(ListenerFilterCallbacks& cb) { + if (matcher_ == nullptr) { + return false; + } else { + return matcher_->matches(cb); + } + } + +private: + const Network::ListenerFilterPtr listener_filter_; + const Network::ListenerFilterMatcherSharedPtr matcher_; +}; +using ListenerFilterWrapperPtr = std::unique_ptr; + +} // namespace Network +} // namespace Envoy \ No newline at end of file diff --git a/source/common/network/internal_listener_impl.cc b/source/common/network/internal_listener_impl.cc new file mode 100644 index 0000000000000..86c5781da8ada --- /dev/null +++ b/source/common/network/internal_listener_impl.cc @@ -0,0 +1,44 @@ +#include "common/network/internal_listener_impl.h" + +#include "envoy/common/exception.h" +#include "envoy/common/platform.h" +#include "envoy/config/core/v3/base.pb.h" +#include "envoy/network/exception.h" + +#include "common/common/assert.h" +#include "common/common/empty_string.h" +#include "common/common/fmt.h" +#include "common/common/utility.h" +#include "common/event/dispatcher_impl.h" +#include "common/network/address_impl.h" +#include "common/network/buffered_io_socket_handle_impl.h" +#include "common/network/io_socket_handle_impl.h" + +#include "absl/strings/str_cat.h" + +namespace Envoy { +namespace Network { +namespace { +// uint64_t next_internal_connection_id = 0; +} +void InternalListenerImpl::setupInternalListener() { + dispatcher_.registerInternalListener( + internal_listener_id_, + [this](const Address::InstanceConstSharedPtr&, + std::unique_ptr internal_conn_socket) { + cb_.onNewSocket(std::move(internal_conn_socket)); + }); +} + +InternalListenerImpl::InternalListenerImpl(Event::DispatcherImpl& dispatcher, + const std::string& listener_id, + InternalListenerCallbacks& cb) + : BaseListenerImpl(dispatcher, nullptr), internal_listener_id_(listener_id), + dispatcher_(dispatcher), cb_(cb) {} + +void InternalListenerImpl::enable() { NOT_IMPLEMENTED_GCOVR_EXCL_LINE; } + +void InternalListenerImpl::disable() { NOT_IMPLEMENTED_GCOVR_EXCL_LINE; } + +} // namespace Network +} // namespace Envoy \ No newline at end of file diff --git a/source/common/network/internal_listener_impl.h b/source/common/network/internal_listener_impl.h new file mode 100644 index 0000000000000..429268ed1379a --- /dev/null +++ b/source/common/network/internal_listener_impl.h @@ -0,0 +1,30 @@ +#pragma once + +#include "envoy/runtime/runtime.h" + +#include "absl/strings/string_view.h" +#include "base_listener_impl.h" + +namespace Envoy { +namespace Network { + +/** + * Listener accepting connection from thread local cluster. + */ +class InternalListenerImpl : public BaseListenerImpl { +public: + InternalListenerImpl(Event::DispatcherImpl& dispatcher, const std::string& listener_id, + InternalListenerCallbacks& cb); + void disable() override; + void enable() override; + + void setupInternalListener(); + +public: + std::string internal_listener_id_; + Event::DispatcherImpl& dispatcher_; + InternalListenerCallbacks& cb_; +}; + +} // namespace Network +} // namespace Envoy diff --git a/source/common/network/io_socket_error_impl.h b/source/common/network/io_socket_error_impl.h index 50d08b55f26a0..b98d51f1caf33 100644 --- a/source/common/network/io_socket_error_impl.h +++ b/source/common/network/io_socket_error_impl.h @@ -1,6 +1,7 @@ #pragma once #include "envoy/api/io_error.h" +#include "envoy/api/os_sys_calls_common.h" #include "common/common/assert.h" @@ -33,5 +34,23 @@ class IoSocketError : public Api::IoError { int errno_; }; +// Converts a SysCallSizeResult to IoCallUint64Result. +template +Api::IoCallUint64Result sysCallResultToIoCallResult(const Api::SysCallResult& result) { + if (result.rc_ >= 0) { + // Return nullptr as IoError upon success. + return Api::IoCallUint64Result(result.rc_, + Api::IoErrorPtr(nullptr, IoSocketError::deleteIoError)); + } + RELEASE_ASSERT(result.errno_ != SOCKET_ERROR_INVAL, "Invalid argument passed in."); + return Api::IoCallUint64Result( + /*rc=*/0, + (result.errno_ == SOCKET_ERROR_AGAIN + // EAGAIN is frequent enough that its memory allocation should be avoided. + ? Api::IoErrorPtr(IoSocketError::getIoSocketEagainInstance(), + IoSocketError::deleteIoError) + : Api::IoErrorPtr(new IoSocketError(result.errno_), IoSocketError::deleteIoError))); +} + } // namespace Network } // namespace Envoy diff --git a/source/common/network/listen_socket_impl.h b/source/common/network/listen_socket_impl.h index 55299c953956a..f71df9e57e134 100644 --- a/source/common/network/listen_socket_impl.h +++ b/source/common/network/listen_socket_impl.h @@ -3,7 +3,9 @@ #include #include #include +#include +#include "common/common/logger.h" #include "envoy/common/platform.h" #include "envoy/network/connection.h" #include "envoy/network/listen_socket.h" @@ -76,19 +78,19 @@ class UdsListenSocket : public ListenSocketImpl { Socket::Type socketType() const override { return Socket::Type::Stream; } }; -class ConnectionSocketImpl : public SocketImpl, public ConnectionSocket { +class ConnectionSocketImpl : public ConnectionSocket { public: ConnectionSocketImpl(IoHandlePtr&& io_handle, const Address::InstanceConstSharedPtr& local_address, const Address::InstanceConstSharedPtr& remote_address) - : SocketImpl(std::move(io_handle), local_address), remote_address_(remote_address), + : socket_(std::move(io_handle), local_address), remote_address_(remote_address), direct_remote_address_(remote_address) {} ConnectionSocketImpl(Socket::Type type, const Address::InstanceConstSharedPtr& local_address, const Address::InstanceConstSharedPtr& remote_address) - : SocketImpl(type, local_address), remote_address_(remote_address), + : socket_(type, local_address), remote_address_(remote_address), direct_remote_address_(remote_address) { - setLocalAddress(local_address); + socket_.setLocalAddress(local_address); } // Network::Socket @@ -99,8 +101,46 @@ class ConnectionSocketImpl : public SocketImpl, public ConnectionSocket { const Address::InstanceConstSharedPtr& directRemoteAddress() const override { return direct_remote_address_; } + const Address::InstanceConstSharedPtr& localAddress() const override { + return socket_.localAddress(); + } + void setLocalAddress(const Address::InstanceConstSharedPtr& local_address) override { + socket_.setLocalAddress(local_address); + } + IoHandle& ioHandle() override { return socket_.ioHandle(); } + + const IoHandle& ioHandle() const override { return socket_.ioHandle(); } + Address::Type addressType() const override { return socket_.addressType(); } + absl::optional ipVersion() const override { return socket_.ipVersion(); } + void close() override { return socket_.close(); } + bool isOpen() const override { return socket_.isOpen(); } + Api::SysCallIntResult bind(const Address::InstanceConstSharedPtr address) override { + return socket_.bind(address); + } + Api::SysCallIntResult listen(int backlog) override { return socket_.listen(backlog); } + Api::SysCallIntResult connect(const Address::InstanceConstSharedPtr address) override { + return socket_.connect(address); + } + Api::SysCallIntResult setBlockingForTest(bool blocking) override { + return socket_.setBlockingForTest(blocking); + } + + Api::SysCallIntResult setSocketOption(int level, int optname, const void* optval, + socklen_t optlen) override { + return socket_.setSocketOption(level, optname, optval, optlen); + } + + Api::SysCallIntResult getSocketOption(int level, int optname, void* optval, + socklen_t* optlen) const override { + return socket_.getSocketOption(level, optname, optval, optlen); + } + + void addOption(const OptionConstSharedPtr& option) override { return socket_.addOption(option); } + void addOptions(const OptionsSharedPtr& option) override { return socket_.addOptions(option); } + const OptionsSharedPtr& options() const override { return socket_.options(); } + void restoreLocalAddress(const Address::InstanceConstSharedPtr& local_address) override { - setLocalAddress(local_address); + socket_.setLocalAddress(local_address); local_address_restored_ = true; } void setRemoteAddress(const Address::InstanceConstSharedPtr& remote_address) override { @@ -129,6 +169,7 @@ class ConnectionSocketImpl : public SocketImpl, public ConnectionSocket { absl::string_view requestedServerName() const override { return server_name_; } protected: + SocketImpl socket_; Address::InstanceConstSharedPtr remote_address_; const Address::InstanceConstSharedPtr direct_remote_address_; bool local_address_restored_{false}; @@ -159,6 +200,38 @@ class AcceptedSocketImpl : public ConnectionSocketImpl { static std::atomic global_accepted_socket_count_; }; +// InternalConnectionSocketImpl used with internal listener. The owned IoHandle is not referring to +// any OS fd. +class InternalConnectionSocketImpl : public ConnectionSocketImpl { +public: + InternalConnectionSocketImpl(IoHandlePtr&& io_handle, + const Address::InstanceConstSharedPtr& local_address, + const Address::InstanceConstSharedPtr& remote_address) + : ConnectionSocketImpl(std::move(io_handle), local_address, remote_address) {} + ~InternalConnectionSocketImpl() override = default; + + // TODO(lambdai): sockopt: track and report on limited options. + Api::SysCallIntResult getSocketOption(int level, int optname, void* ptr, + socklen_t* len) const override { + if (level == SOL_SOCKET && optname == SO_ERROR) { + memset(ptr, 0, static_cast(*len)); + *(reinterpret_cast(ptr)) = 0; + return {0, 0}; + } else { + ENVOY_LOG_MISC(warn, "unsupported socket option level={} optname={}", level, optname); + return {0, EFAULT}; + } + } + Api::SysCallIntResult setSocketOption(int level, int optname, const void*, socklen_t) override { + if (level == IPPROTO_TCP && optname == TCP_NODELAY) { + return {0, 0}; + } else { + ENVOY_LOG_MISC(warn, "unsupported socket option level={} optname={}", level, optname); + return {0, EFAULT}; + } + } +}; + // ConnectionSocket used with client connections. class ClientSocketImpl : public ConnectionSocketImpl { public: @@ -167,7 +240,7 @@ class ClientSocketImpl : public ConnectionSocketImpl { : ConnectionSocketImpl(Network::ioHandleForAddr(Socket::Type::Stream, remote_address), nullptr, remote_address) { if (options) { - addOptions(options); + socket_.addOptions(options); } } }; diff --git a/source/common/network/peer_buffer.h b/source/common/network/peer_buffer.h new file mode 100644 index 0000000000000..6e0da2dc022c2 --- /dev/null +++ b/source/common/network/peer_buffer.h @@ -0,0 +1,77 @@ +#include + +#include "envoy/buffer/buffer.h" +#include "envoy/common/pure.h" +#include "envoy/network/io_handle.h" +#include "envoy/network/proxy_protocol.h" +#include "envoy/ssl/connection.h" + +#include "common/common/assert.h" +#include "common/common/logger.h" + +#include "absl/types/optional.h" + +namespace Envoy { +namespace Network { + +/** + * The interface for the writer. + */ +class WritablePeer { +public: + virtual ~WritablePeer() = default; + + /** + * Set the flag to indicate no further write from peer. + */ + virtual void setWriteEnd() PURE; + virtual bool isWriteEndSet() PURE; + + /** + * Raised when peer is destroyed. No further write to peer is allowed. + */ + virtual void onPeerDestroy() PURE; + + /** + * Notify that consumable data arrives. The consumable data can be either data to read, or the end + * of stream event. + */ + virtual void maybeSetNewData() PURE; + + /** + * @return the buffer to be written. + */ + virtual Buffer::Instance* getWriteBuffer() PURE; + + /** + * @return false more data is acceptable. + */ + virtual bool isWritable() const PURE; + + /** + * Raised by the peer when the peer switch from high water mark to low. + */ + virtual void onPeerBufferWritable() PURE; + + // virtual bool triggeredHighToLowWatermark() const PURE; + // virtual void clearTriggeredHighToLowWatermark() PURE; + // virtual void setTriggeredHighToLowWatermark() PURE; +}; + +/** + * The interface for the buffer owner who want to consume the buffer. + */ +class ReadableSource { +public: + virtual ~ReadableSource() = default; + + /** + * Read the flag to indicate no further write. Used by early close detection. + */ + virtual bool isPeerShutDownWrite() const PURE; + + virtual bool isOverHighWatermark() const PURE; + virtual bool isReadable() const PURE; +}; +} // namespace Network +} // namespace Envoy \ No newline at end of file diff --git a/source/common/network/socket_impl.h b/source/common/network/socket_impl.h index 1704b6a005f1a..087226c198d68 100644 --- a/source/common/network/socket_impl.h +++ b/source/common/network/socket_impl.h @@ -53,9 +53,9 @@ class SocketImpl : public virtual Socket { Address::Type addressType() const override { return addr_type_; } absl::optional ipVersion() const override; -protected: SocketImpl(IoHandlePtr&& io_handle, const Address::InstanceConstSharedPtr& local_address); +protected: const IoHandlePtr io_handle_; Address::InstanceConstSharedPtr local_address_; OptionsSharedPtr options_; diff --git a/source/common/network/utility.cc b/source/common/network/utility.cc index 426598ace9562..82d40ae280283 100644 --- a/source/common/network/utility.cc +++ b/source/common/network/utility.cc @@ -512,10 +512,13 @@ Utility::protobufAddressSocketType(const envoy::config::core::v3::Address& proto } } case envoy::config::core::v3::Address::AddressCase::kPipe: - return Socket::Type::Stream; - default: + FALLTHRU; + case envoy::config::core::v3::Address::AddressCase::kEnvoyInternalAddress: + break; + case envoy::config::core::v3::Address::AddressCase::ADDRESS_NOT_SET: NOT_REACHED_GCOVR_EXCL_LINE; } + return Socket::Type::Stream; } Api::IoCallUint64Result Utility::writeToSocket(IoHandle& handle, const Buffer::Instance& buffer, diff --git a/source/common/network/utility.h b/source/common/network/utility.h index be64071e9ea69..4c01afdba8004 100644 --- a/source/common/network/utility.h +++ b/source/common/network/utility.h @@ -282,7 +282,7 @@ class Utility { /** * Returns socket type corresponding to SocketAddress.protocol value of the - * given address, or SocketType::Stream if the address is a pipe address. + * given address, or SocketType::Stream if the address is a pipe address or internal address. * @param proto_address the address protobuf * @return socket type */ diff --git a/source/common/upstream/upstream_impl.cc b/source/common/upstream/upstream_impl.cc index e7a5c129a06b7..745ae1ff2b3e0 100644 --- a/source/common/upstream/upstream_impl.cc +++ b/source/common/upstream/upstream_impl.cc @@ -346,11 +346,16 @@ HostImpl::createConnection(Event::Dispatcher& dispatcher, const ClusterInfo& clu } else { connection_options = options; } - ASSERT(!address->envoyInternalAddress()); - Network::ClientConnectionPtr connection = dispatcher.createClientConnection( - address, cluster.sourceAddress(), - socket_factory.createTransportSocket(std::move(transport_socket_options)), - connection_options); + Network::ClientConnectionPtr connection; + if (address->envoyInternalAddress()) { + ASSERT(cluster.sourceAddress() == nullptr || cluster.sourceAddress()->envoyInternalAddress()); + connection = dispatcher.createInternalConnection(address, cluster.sourceAddress()); + } else { + connection = dispatcher.createClientConnection( + address, cluster.sourceAddress(), + socket_factory.createTransportSocket(std::move(transport_socket_options)), + connection_options); + } connection->setBufferLimits(cluster.perConnectionBufferLimitBytes()); cluster.createNetworkFilterChain(*connection); return connection; diff --git a/source/server/BUILD b/source/server/BUILD index bc938e92819e3..b5e383fa958d5 100644 --- a/source/server/BUILD +++ b/source/server/BUILD @@ -80,6 +80,8 @@ envoy_cc_library( "//source/common/common:non_copyable", "//source/common/event:deferred_task", "//source/common/network:connection_lib", + "//source/common/network:generic_listener_filter_lib", + "//source/common/network:listener_lib", "//source/common/stats:timespan_lib", "//source/common/stream_info:stream_info_lib", "//source/extensions/transport_sockets:well_known_names", diff --git a/source/server/admin/admin.h b/source/server/admin/admin.h index 945885ac39098..f30d164835a21 100644 --- a/source/server/admin/admin.h +++ b/source/server/admin/admin.h @@ -345,6 +345,9 @@ class AdminImpl : public Admin, Network::UdpPacketWriterFactoryOptRef udpPacketWriterFactory() override { NOT_REACHED_GCOVR_EXCL_LINE; } + bool isInternalListener() override { + return false; + } Network::UdpListenerWorkerRouterOptRef udpListenerWorkerRouter() override { NOT_REACHED_GCOVR_EXCL_LINE; } diff --git a/source/server/config_validation/dispatcher.cc b/source/server/config_validation/dispatcher.cc index 3316f58065c2b..c45e28f5bf387 100644 --- a/source/server/config_validation/dispatcher.cc +++ b/source/server/config_validation/dispatcher.cc @@ -16,6 +16,12 @@ Network::ClientConnectionPtr ValidationDispatcher::createClientConnection( std::move(transport_socket), options); } +Network::ClientConnectionPtr +ValidationDispatcher::createInternalConnection(Network::Address::InstanceConstSharedPtr, + Network::Address::InstanceConstSharedPtr) { + NOT_IMPLEMENTED_GCOVR_EXCL_LINE; +} + Network::DnsResolverSharedPtr ValidationDispatcher::createDnsResolver( const std::vector&, const bool) { return dns_resolver_; diff --git a/source/server/config_validation/dispatcher.h b/source/server/config_validation/dispatcher.h index b5deea61f58db..64015eabe4d38 100644 --- a/source/server/config_validation/dispatcher.h +++ b/source/server/config_validation/dispatcher.h @@ -23,6 +23,9 @@ class ValidationDispatcher : public DispatcherImpl { createClientConnection(Network::Address::InstanceConstSharedPtr, Network::Address::InstanceConstSharedPtr, Network::TransportSocketPtr&&, const Network::ConnectionSocket::OptionsSharedPtr& options) override; + Network::ClientConnectionPtr + createInternalConnection(Network::Address::InstanceConstSharedPtr internal_address, + Network::Address::InstanceConstSharedPtr local_address) override; Network::DnsResolverSharedPtr createDnsResolver(const std::vector& resolvers, const bool use_tcp_for_dns_lookups) override; diff --git a/source/server/connection_handler_impl.cc b/source/server/connection_handler_impl.cc index 4c3c431c42915..cb62864284de1 100644 --- a/source/server/connection_handler_impl.cc +++ b/source/server/connection_handler_impl.cc @@ -7,6 +7,7 @@ #include "envoy/stats/scope.h" #include "envoy/stats/timespan.h" +#include "common/common/assert.h" #include "common/event/deferred_task.h" #include "common/network/connection_impl.h" #include "common/network/utility.h" @@ -41,7 +42,20 @@ void ConnectionHandlerImpl::decNumConnections() { void ConnectionHandlerImpl::addListener(absl::optional overridden_listener, Network::ListenerConfig& config) { ActiveListenerDetails details; - if (config.listenSocketFactory().socketType() == Network::Socket::Type::Stream) { + if (config.isInternalListener()) { + if (overridden_listener.has_value()) { + for (auto& listener : listeners_) { + if (listener.second.listener_->listenerTag() == overridden_listener) { + listener.second.tcpListener()->get().updateListenerConfig(config); + return; + } + } + } + auto active_internal_listener = std::make_unique(*this, config); + active_internal_listener->internal_listener_->setupInternalListener(); + details.typed_listener_ = *active_internal_listener; + details.listener_ = std::move(active_internal_listener); + } else if (config.listenSocketFactory().socketType() == Network::Socket::Type::Stream) { if (overridden_listener.has_value()) { for (auto& listener : listeners_) { if (listener.second.listener_->listenerTag() == overridden_listener) { @@ -267,12 +281,12 @@ ConnectionHandlerImpl::findActiveTcpListenerByAddress(const Network::Address::In } void ConnectionHandlerImpl::ActiveTcpSocket::onTimeout() { - listener_.stats_.downstream_pre_cx_timeout_.inc(); + stream_listener_.stats_.downstream_pre_cx_timeout_.inc(); ASSERT(inserted()); ENVOY_LOG(debug, "listener filter times out after {} ms", - listener_.listener_filters_timeout_.count()); + stream_listener_.listener_filters_timeout_.count()); - if (listener_.continue_on_listener_filters_timeout_) { + if (stream_listener_.continue_on_listener_filters_timeout_) { ENVOY_LOG(debug, "fallback to default listener filter"); newConnection(); } @@ -280,22 +294,22 @@ void ConnectionHandlerImpl::ActiveTcpSocket::onTimeout() { } void ConnectionHandlerImpl::ActiveTcpSocket::startTimer() { - if (listener_.listener_filters_timeout_.count() > 0) { - timer_ = listener_.parent_.dispatcher_.createTimer([this]() -> void { onTimeout(); }); - timer_->enableTimer(listener_.listener_filters_timeout_); + if (stream_listener_.listener_filters_timeout_.count() > 0) { + timer_ = stream_listener_.parent_.dispatcher_.createTimer([this]() -> void { onTimeout(); }); + timer_->enableTimer(stream_listener_.listener_filters_timeout_); } } void ConnectionHandlerImpl::ActiveTcpSocket::unlink() { - ActiveTcpSocketPtr removed = removeFromList(listener_.sockets_); + ActiveTcpSocketPtr removed = removeFromList(stream_listener_.sockets_); if (removed->timer_ != nullptr) { removed->timer_->disableTimer(); } // Emit logs if a connection is not established. if (!connected_) { - emitLogs(*listener_.config_, *stream_info_); + emitLogs(*stream_listener_.config_, *stream_info_); } - listener_.parent_.dispatcher_.deferredDelete(std::move(removed)); + stream_listener_.parent_.dispatcher_.deferredDelete(std::move(removed)); } void ConnectionHandlerImpl::ActiveTcpSocket::continueFilterChain(bool success) { @@ -350,7 +364,8 @@ void ConnectionHandlerImpl::ActiveTcpSocket::newConnection() { if (hand_off_restored_destination_connections_ && socket_->localAddressRestored()) { // Find a listener associated with the original destination address. - new_listener = listener_.parent_.findActiveTcpListenerByAddress(*socket_->localAddress()); + new_listener = + stream_listener_.parent_.findActiveTcpListenerByAddress(*socket_->localAddress()); } if (new_listener.has_value()) { // Hands off connections redirected by iptables to the listener associated with the @@ -360,7 +375,7 @@ void ConnectionHandlerImpl::ActiveTcpSocket::newConnection() { // initially accepted. Note also that we must account for the number of connections properly // across both listeners. // TODO(mattklein123): See note in ~ActiveTcpSocket() related to making this accounting better. - listener_.decNumConnections(); + stream_listener_.decNumConnections(); new_listener.value().get().incNumConnections(); new_listener.value().get().onAcceptWorker(std::move(socket_), false, true); } else { @@ -375,7 +390,7 @@ void ConnectionHandlerImpl::ActiveTcpSocket::newConnection() { // Particularly the assigned events need to reset before assigning new events in the follow up. accept_filters_.clear(); // Create a new connection on this listener. - listener_.newConnection(std::move(socket_), std::move(stream_info_)); + stream_listener_.newConnection(std::move(socket_), std::move(stream_info_)); } } @@ -412,12 +427,12 @@ void ConnectionHandlerImpl::ActiveTcpListener::onAcceptWorker( // Move active_socket to the sockets_ list if filter iteration needs to continue later. // Otherwise we let active_socket be destructed when it goes out of scope. - if (active_socket->iter_ != active_socket->accept_filters_.end()) { + if (!active_socket->isListenerFiltersCompleted()) { active_socket->startTimer(); LinkedList::moveIntoListBack(std::move(active_socket), sockets_); } else { // If active_socket is about to be destructed, emit logs if a connection is not created. - if (!active_socket->connected_) { + if (!active_socket->isConnected()) { emitLogs(*config_, *active_socket->stream_info_); } } @@ -461,6 +476,10 @@ void ConnectionHandlerImpl::ActiveTcpListener::newConnection( LinkedList::moveIntoList(std::move(active_connection), active_connections.connections_); } } +Stats::TimespanPtr ConnectionHandlerImpl::ActiveTcpListener::newTimespan(TimeSource& time_source) { + return std::make_unique(stats_.downstream_cx_length_ms_, + time_source); +} ConnectionHandlerImpl::ActiveConnections& ConnectionHandlerImpl::ActiveTcpListener::getOrCreateActiveConnections( @@ -530,7 +549,7 @@ void ConnectionHandlerImpl::ActiveTcpListener::post(Network::ConnectionSocketPtr } ConnectionHandlerImpl::ActiveConnections::ActiveConnections( - ConnectionHandlerImpl::ActiveTcpListener& listener, const Network::FilterChain& filter_chain) + ConnectionHandlerImpl::StreamListener& listener, const Network::FilterChain& filter_chain) : listener_(listener), filter_chain_(filter_chain) {} ConnectionHandlerImpl::ActiveConnections::~ActiveConnections() { @@ -543,36 +562,17 @@ ConnectionHandlerImpl::ActiveTcpConnection::ActiveTcpConnection( TimeSource& time_source, std::unique_ptr&& stream_info) : stream_info_(std::move(stream_info)), active_connections_(active_connections), connection_(std::move(new_connection)), - conn_length_(new Stats::HistogramCompletableTimespanImpl( - active_connections_.listener_.stats_.downstream_cx_length_ms_, time_source)) { + conn_length_(active_connections_.listener_.newTimespan(time_source)) { // We just universally set no delay on connections. Theoretically we might at some point want // to make this configurable. connection_->noDelay(true); - auto& listener = active_connections_.listener_; - listener.stats_.downstream_cx_total_.inc(); - listener.stats_.downstream_cx_active_.inc(); - listener.per_worker_stats_.downstream_cx_total_.inc(); - listener.per_worker_stats_.downstream_cx_active_.inc(); - - // Active connections on the handler (not listener). The per listener connections have already - // been incremented at this point either via the connection balancer or in the socket accept - // path if there is no configured balancer. - ++listener.parent_.num_handler_connections_; + active_connections_.listener_.onNewConnection(); } ConnectionHandlerImpl::ActiveTcpConnection::~ActiveTcpConnection() { - emitLogs(*active_connections_.listener_.config_, *stream_info_); - auto& listener = active_connections_.listener_; - listener.stats_.downstream_cx_active_.dec(); - listener.stats_.downstream_cx_destroy_.inc(); - listener.per_worker_stats_.downstream_cx_active_.dec(); + emitLogs(active_connections_.listener_.listenerConfig(), *stream_info_); conn_length_->complete(); - - // Active listener connections (not handler). - listener.decNumConnections(); - - // Active handler connections (not listener). - listener.parent_.decNumConnections(); + active_connections_.listener_.onDestroyConnection(); } ConnectionHandlerImpl::ActiveTcpListenerOptRef @@ -710,5 +710,336 @@ void ActiveRawUdpListener::addReadFilter(Network::UdpListenerReadFilterPtr&& fil Network::UdpListener& ActiveRawUdpListener::udpListener() { return *udp_listener_; } +void ConnectionHandlerImpl::ActiveInternalSocket::onTimeout() { + stream_listener_.stats_.downstream_pre_cx_timeout_.inc(); + ASSERT(inserted()); + ENVOY_LOG(debug, "listener filter times out after {} ms", + stream_listener_.listener_filters_timeout_.count()); + + if (stream_listener_.continue_on_listener_filters_timeout_) { + ENVOY_LOG(debug, "fallback to default listener filter"); + newConnection(); + } + unlink(); +} + +void ConnectionHandlerImpl::ActiveInternalSocket::startTimer() { + if (stream_listener_.listener_filters_timeout_.count() > 0) { + timer_ = stream_listener_.parent_.dispatcher_.createTimer([this]() -> void { onTimeout(); }); + timer_->enableTimer(stream_listener_.listener_filters_timeout_); + } +} + +void ConnectionHandlerImpl::ActiveInternalSocket::unlink() { + auto removed = removeFromList(stream_listener_.sockets_); + if (removed->timer_ != nullptr) { + removed->timer_->disableTimer(); + } + // Emit logs if a connection is not established. + if (!connected_) { + emitLogs(*stream_listener_.config_, *stream_info_); + } + stream_listener_.parent_.dispatcher_.deferredDelete(std::move(removed)); +} + +void ConnectionHandlerImpl::ActiveInternalSocket::continueFilterChain(bool success) { + if (success) { + bool no_error = true; + if (iter_ == accept_filters_.end()) { + iter_ = accept_filters_.begin(); + } else { + iter_ = std::next(iter_); + } + + for (; iter_ != accept_filters_.end(); iter_++) { + Network::FilterStatus status = (*iter_)->onAccept(*this); + if (status == Network::FilterStatus::StopIteration) { + // The filter is responsible for calling us again at a later time to continue the filter + // chain from the next filter. + if (!socket().ioHandle().isOpen()) { + // break the loop but should not create new connection + no_error = false; + break; + } else { + // Blocking at the filter but no error + return; + } + } + } + // Successfully ran all the accept filters. + if (no_error) { + newConnection(); + } else { + // Signal the caller that no extra filter chain iteration is needed. + iter_ = accept_filters_.end(); + } + } + + // Filter execution concluded, unlink and delete this ActiveTcpSocket if it was linked. + if (inserted()) { + unlink(); + } +} + +void ConnectionHandlerImpl::ActiveInternalSocket::setDynamicMetadata( + const std::string& name, const ProtobufWkt::Struct& value) { + stream_info_->setDynamicMetadata(name, value); +} + +void ConnectionHandlerImpl::ActiveInternalSocket::newConnection() { + connected_ = true; + // Set default transport protocol if none of the listener filters did it. + if (!socket_->detectedTransportProtocol().empty() && + socket_->detectedTransportProtocol() != + Extensions::TransportSockets::TransportProtocolNames::get().RawBuffer) { + ENVOY_LOG(warn, + "internal connection does not support transport protocol {}, use raw buffer " + "transport socket. ", + socket_->detectedTransportProtocol()); + } + socket_->setDetectedTransportProtocol( + Extensions::TransportSockets::TransportProtocolNames::get().RawBuffer); + // TODO(lambdai): add integration test + // TODO: Address issues in wider scope. See https://github.com/envoyproxy/envoy/issues/8925 + // Erase accept filter states because accept filters may not get the opportunity to clean up. + // Particularly the assigned events need to reset before assigning new events in the follow up. + accept_filters_.clear(); + // Create a new connection on this listener. + stream_listener_.newConnection(std::move(socket_), stream_info_->dynamicMetadata()); +} + +ConnectionHandlerImpl::ActiveInternalListener::ActiveInternalListener( + ConnectionHandlerImpl& parent, Network::ListenerConfig& config) + : ConnectionHandlerImpl::ActiveListenerImplBase(parent, &config), parent_(parent), + internal_listener_(std::make_unique( + // TODO(lambdai): promote createInternalConnection to dispatcher interface. + dynamic_cast(parent_.dispatcher_), + config.listenSocketFactory().localAddress()->asString(), *this)), + listener_filters_timeout_(config.listenerFiltersTimeout()), + continue_on_listener_filters_timeout_(config.continueOnListenerFiltersTimeout()) {} + +void ConnectionHandlerImpl::ActiveInternalListener::updateListenerConfig( + Network::ListenerConfig& config) { + ENVOY_LOG(trace, "replacing listener ", config_->listenerTag(), " by ", config.listenerTag()); + config_ = &config; +} + +ConnectionHandlerImpl::ActiveInternalListener::~ActiveInternalListener() { + is_deleting_ = true; + + // Purge sockets that have not progressed to connections. This should only happen when + // a listener filter stops iteration and never resumes. + while (!sockets_.empty()) { + auto removed = sockets_.front()->removeFromList(sockets_); + parent_.dispatcher_.deferredDelete(std::move(removed)); + } + + for (auto& chain_and_connections : connections_by_context_) { + ASSERT(chain_and_connections.second != nullptr); + auto& connections = chain_and_connections.second->connections_; + while (!connections.empty()) { + connections.front()->connection_->close(Network::ConnectionCloseType::NoFlush); + } + } + parent_.dispatcher_.clearDeferredDeleteList(); + + // By the time a listener is destroyed, in the common case, there should be no connections. + // However, this is not always true if there is an in flight rebalanced connection that is + // being posted. This assert is extremely useful for debugging the common path so we will leave it + // for now. If it becomes a problem (developers hitting this assert when using debug builds) we + // can revisit. This case, if it happens, should be benign on production builds. This case is + // covered in ConnectionHandlerTest::RemoveListenerDuringRebalance. + ASSERT(num_listener_connections_ == 0); +} + +void ConnectionHandlerImpl::ActiveInternalListener::removeConnection( + ActiveTcpConnection& connection) { + ENVOY_CONN_LOG(debug, "adding to cleanup list", *connection.connection_); + ActiveConnections& active_connections = connection.active_connections_; + ActiveTcpConnectionPtr removed = connection.removeFromList(active_connections.connections_); + parent_.dispatcher_.deferredDelete(std::move(removed)); + // Delete map entry only iff connections becomes empty. + if (active_connections.connections_.empty()) { + auto iter = connections_by_context_.find(&active_connections.filter_chain_); + ASSERT(iter != connections_by_context_.end()); + // To cover the lifetime of every single connection, Connections need to be deferred deleted + // because the previously contained connection is deferred deleted. + parent_.dispatcher_.deferredDelete(std::move(iter->second)); + // The erase will break the iteration over the connections_by_context_ during the deletion. + if (!is_deleting_) { + connections_by_context_.erase(iter); + } + } +} + +void ConnectionHandlerImpl::ActiveInternalListener::shutdownListener() { + internal_listener_->dispatcher_.registerInternalListener( + internal_listener_->internal_listener_id_, + /* connection_callback= */ nullptr); +} + +void ConnectionHandlerImpl::ActiveInternalListener::onNewSocket( + Network::ConnectionSocketPtr socket) { + ActiveInternalSocketPtr active_socket = + std::make_unique(*this, std::move(socket)); + // Create and run the filters + config_->filterChainFactory().createListenerFilterChain(*active_socket); + active_socket->continueFilterChain(true); + + // Move active_socket to the sockets_ list if filter iteration needs to continue later. + // Otherwise we let active_socket be destructed when it goes out of scope. + if (!active_socket->isListenerFiltersCompleted()) { + active_socket->startTimer(); + LinkedList::moveIntoListBack(std::move(active_socket), sockets_); + } else { + // If active_socket is about to be destructed, emit logs if a connection is not created. + if (!active_socket->isListenerFiltersCompleted()) { + emitLogs(*config_, *active_socket->stream_info_); + } + } +} + +Stats::TimespanPtr +ConnectionHandlerImpl::ActiveInternalListener::newTimespan(TimeSource& time_source) { + return std::make_unique(stats_.downstream_cx_length_ms_, + time_source); +} + +// Copied from newConnection(). Invoked by SetupPipeListener. +void ConnectionHandlerImpl::ActiveInternalListener::setupNewConnection( + Network::ConnectionPtr server_conn, Network::ConnectionSocketPtr socket) { + incNumConnections(); + auto stream_info = std::make_unique(parent_.dispatcher_.timeSource()); + stream_info->setDownstreamLocalAddress(socket->localAddress()); + stream_info->setDownstreamRemoteAddress(socket->remoteAddress()); + stream_info->setDownstreamDirectRemoteAddress(socket->directRemoteAddress()); + + // TODO(lambdai): refactor + // auto p = dynamic_cast(server_conn.get()); + // ASSERT(p); + // p->setStreamInfo(stream_info.get()); + + // Find matching filter chain. + const auto filter_chain = config_->filterChainManager().findFilterChain(*socket); + if (filter_chain == nullptr) { + ENVOY_LOG(debug, "closing connection: no matching filter chain found"); + stats_.no_filter_chain_match_.inc(); + stream_info->setResponseFlag(StreamInfo::ResponseFlag::NoRouteFound); + stream_info->setResponseCodeDetails(StreamInfo::ResponseCodeDetails::get().FilterChainNotFound); + emitLogs(*config_, *stream_info); + socket->close(); + return; + } + + auto transport_socket = filter_chain->transportSocketFactory().createTransportSocket(nullptr); + stream_info->setDownstreamSslConnection(transport_socket->ssl()); + auto& active_connections = getOrCreateActiveConnections(*filter_chain); + // TODO(lambdai): set stream_info + ActiveTcpConnectionPtr active_connection( + new ActiveTcpConnection(active_connections, std::move(server_conn), + parent_.dispatcher_.timeSource(), std::move(stream_info))); + active_connection->connection_->setBufferLimits(config_->perConnectionBufferLimitBytes()); + + const bool empty_filter_chain = !config_->filterChainFactory().createNetworkFilterChain( + *active_connection->connection_, filter_chain->networkFilterFactories()); + if (empty_filter_chain) { + ENVOY_CONN_LOG(debug, "closing connection: no filters", *active_connection->connection_); + active_connection->connection_->close(Network::ConnectionCloseType::NoFlush); + } + + // If the connection is already closed, we can just let this connection immediately die. + if (active_connection->connection_->state() != Network::Connection::State::Closed) { + ENVOY_CONN_LOG(debug, "new connection", *active_connection->connection_); + active_connection->connection_->addConnectionCallbacks(*active_connection); + LinkedList::moveIntoList(std::move(active_connection), active_connections.connections_); + } +} + +void ConnectionHandlerImpl::ActiveInternalListener::newConnection( + Network::ConnectionSocketPtr&& socket, + const envoy::config::core::v3::Metadata& dynamic_metadata) { + auto stream_info = std::make_unique( + parent_.dispatcher_.timeSource(), StreamInfo::FilterState::LifeSpan::Connection); + stream_info->setDownstreamLocalAddress(socket->localAddress()); + stream_info->setDownstreamRemoteAddress(socket->remoteAddress()); + stream_info->setDownstreamDirectRemoteAddress(socket->directRemoteAddress()); + + // Merge from the given dynamic metadata if it's not empty. + if (dynamic_metadata.filter_metadata_size() > 0) { + stream_info->dynamicMetadata().MergeFrom(dynamic_metadata); + } + + // Find matching filter chain. + const auto filter_chain = config_->filterChainManager().findFilterChain(*socket); + if (filter_chain == nullptr) { + ENVOY_LOG(debug, "closing connection: no matching filter chain found"); + stats_.no_filter_chain_match_.inc(); + stream_info->setResponseFlag(StreamInfo::ResponseFlag::NoRouteFound); + stream_info->setResponseCodeDetails(StreamInfo::ResponseCodeDetails::get().FilterChainNotFound); + emitLogs(*config_, *stream_info); + socket->close(); + return; + } + + auto transport_socket = filter_chain->transportSocketFactory().createTransportSocket(nullptr); + stream_info->setDownstreamSslConnection(transport_socket->ssl()); + auto& active_connections = getOrCreateActiveConnections(*filter_chain); + Network::ConnectionPtr server_conn_ptr; + server_conn_ptr = parent_.dispatcher_.createServerConnection( + std::move(socket), std::move(transport_socket), *stream_info); + + ActiveTcpConnectionPtr active_connection( + new ActiveTcpConnection(active_connections, std::move(server_conn_ptr), + parent_.dispatcher_.timeSource(), std::move(stream_info))); + active_connection->connection_->setBufferLimits(config_->perConnectionBufferLimitBytes()); + + const bool empty_filter_chain = !config_->filterChainFactory().createNetworkFilterChain( + *active_connection->connection_, filter_chain->networkFilterFactories()); + if (empty_filter_chain) { + ENVOY_CONN_LOG(debug, "closing connection: no filters", *active_connection->connection_); + active_connection->connection_->close(Network::ConnectionCloseType::NoFlush); + } + + // If the connection is already closed, we can just let this connection immediately die. + if (active_connection->connection_->state() != Network::Connection::State::Closed) { + ENVOY_CONN_LOG(debug, "new connection", *active_connection->connection_); + active_connection->connection_->addConnectionCallbacks(*active_connection); + LinkedList::moveIntoList(std::move(active_connection), active_connections.connections_); + } +} + +ConnectionHandlerImpl::ActiveConnections& +ConnectionHandlerImpl::ActiveInternalListener::getOrCreateActiveConnections( + const Network::FilterChain& filter_chain) { + ActiveConnectionsPtr& connections = connections_by_context_[&filter_chain]; + if (connections == nullptr) { + connections = std::make_unique(*this, filter_chain); + } + return *connections; +} + +void ConnectionHandlerImpl::ActiveInternalListener::deferredRemoveFilterChains( + const std::list& draining_filter_chains) { + // Need to recover the original deleting state. + const bool was_deleting = is_deleting_; + is_deleting_ = true; + for (const auto* filter_chain : draining_filter_chains) { + auto iter = connections_by_context_.find(filter_chain); + if (iter == connections_by_context_.end()) { + // It is possible when listener is stopping. + } else { + auto& connections = iter->second->connections_; + while (!connections.empty()) { + connections.front()->connection_->close(Network::ConnectionCloseType::NoFlush); + } + // Since is_deleting_ is on, we need to manually remove the map value and drive the iterator. + // Defer delete connection container to avoid race condition in destroying connection. + parent_.dispatcher_.deferredDelete(std::move(iter->second)); + connections_by_context_.erase(iter); + } + } + is_deleting_ = was_deleting; +} + } // namespace Server } // namespace Envoy diff --git a/source/server/connection_handler_impl.h b/source/server/connection_handler_impl.h index 3eb3e8e58c351..435406684c67c 100644 --- a/source/server/connection_handler_impl.h +++ b/source/server/connection_handler_impl.h @@ -19,6 +19,9 @@ #include "common/common/linked_object.h" #include "common/common/non_copyable.h" +#include "common/network/generic_listener_filter.h" +#include "common/network/internal_listener_impl.h" +#include "common/network/listen_socket_impl.h" #include "common/stream_info/stream_info_impl.h" #include "spdlog/spdlog.h" @@ -102,16 +105,31 @@ class ConnectionHandlerImpl : public Network::ConnectionHandler, private: struct ActiveTcpConnection; using ActiveTcpConnectionPtr = std::unique_ptr; + class StreamListener { + public: + virtual ~StreamListener() = default; + // virtual ListenerStats& listenerStats() PURE; + // virtual PerHandlerListenerStats& per_worker_stats_() PURE; + virtual void onNewConnection() PURE; + virtual void onDestroyConnection() PURE; + virtual Stats::TimespanPtr newTimespan(TimeSource& time_source) PURE; + virtual Network::ListenerConfig& listenerConfig() PURE; + virtual void removeConnection(ActiveTcpConnection& conn) PURE; + }; + struct ActiveTcpSocket; using ActiveTcpSocketPtr = std::unique_ptr; class ActiveConnections; using ActiveConnectionsPtr = std::unique_ptr; + struct ActiveInternalSocket; + using ActiveInternalSocketPtr = std::unique_ptr; /** * Wrapper for an active tcp listener owned by this handler. */ class ActiveTcpListener : public Network::TcpListenerCallbacks, public ActiveListenerImplBase, + public StreamListener, public Network::BalancedConnectionHandler { public: ActiveTcpListener(ConnectionHandlerImpl& parent, Network::ListenerConfig& config); @@ -125,6 +143,7 @@ class ConnectionHandlerImpl : public Network::ConnectionHandler, } void onAcceptWorker(Network::ConnectionSocketPtr&& socket, bool hand_off_restored_destination_connections, bool rebalanced); + void decNumConnections() { ASSERT(num_listener_connections_ > 0); --num_listener_connections_; @@ -141,6 +160,30 @@ class ConnectionHandlerImpl : public Network::ConnectionHandler, void resumeListening() override { listener_->enable(); } void shutdownListener() override { listener_.reset(); } + // StreamListener + void onNewConnection() override { + stats_.downstream_cx_total_.inc(); + stats_.downstream_cx_active_.inc(); + per_worker_stats_.downstream_cx_total_.inc(); + per_worker_stats_.downstream_cx_active_.inc(); + // Active connections on the handler (not listener). The per listener connections have already + // been incremented at this point either via the connection balancer or in the socket accept + // path if there is no configured balancer. + ++parent_.num_handler_connections_; + } + void onDestroyConnection() override { + stats_.downstream_cx_active_.dec(); + stats_.downstream_cx_destroy_.inc(); + per_worker_stats_.downstream_cx_active_.dec(); + // Active listener connections (not handler). + decNumConnections(); + // Active handler connections (not listener). + parent_.decNumConnections(); + } + Network::ListenerConfig& listenerConfig() override { return *config_; } + + Stats::TimespanPtr newTimespan(TimeSource& time_source) override; + // Network::BalancedConnectionHandler uint64_t numConnections() const override { return num_listener_connections_; } void incNumConnections() override { @@ -153,7 +196,7 @@ class ConnectionHandlerImpl : public Network::ConnectionHandler, * Remove and destroy an active connection. * @param connection supplies the connection to remove. */ - void removeConnection(ActiveTcpConnection& connection); + void removeConnection(ActiveTcpConnection& connection) override; /** * Create a new connection from a socket accepted by the listener. @@ -192,16 +235,111 @@ class ConnectionHandlerImpl : public Network::ConnectionHandler, bool is_deleting_{false}; }; + /** + * Wrapper for an active internal listener owned by this handler. + */ + class ActiveInternalListener : public Network::InternalListenerCallbacks, + public ActiveListenerImplBase, + public StreamListener { + public: + ActiveInternalListener(ConnectionHandlerImpl& parent, Network::ListenerConfig& config); + + ~ActiveInternalListener() override; + + void incNumConnections() { + ++num_listener_connections_; + config_->openConnections().inc(); + } + + void decNumConnections() { + ASSERT(num_listener_connections_ > 0); + --num_listener_connections_; + config_->openConnections().dec(); + } + + // Network::InternalListenerCallbacks + void setupNewConnection(Network::ConnectionPtr server_conn, + Network::ConnectionSocketPtr socket) override; + void onNewSocket(Network::ConnectionSocketPtr socket) override; + // ActiveListenerImplBase + Network::Listener* listener() override { return internal_listener_.get(); } + void pauseListening() override { internal_listener_->disable(); } + void resumeListening() override { internal_listener_->enable(); } + void shutdownListener() override; + + // StreamListener + void onNewConnection() override { + // TODO(lambdai): FIX ME. + incNumConnections(); + stats_.downstream_cx_total_.inc(); + stats_.downstream_cx_active_.inc(); + per_worker_stats_.downstream_cx_total_.inc(); + per_worker_stats_.downstream_cx_active_.inc(); + ++parent_.num_handler_connections_; + } + void onDestroyConnection() override { + stats_.downstream_cx_active_.dec(); + stats_.downstream_cx_destroy_.inc(); + per_worker_stats_.downstream_cx_active_.dec(); + decNumConnections(); + parent_.decNumConnections(); + } + Network::ListenerConfig& listenerConfig() override { return *config_; } + Stats::TimespanPtr newTimespan(TimeSource& time_source) override; + + /** + * Remove and destroy an active connection. + * @param connection supplies the connection to remove. + */ + void removeConnection(ActiveTcpConnection& connection) override; + + /** + * Create a new connection from a socket accepted by the listener. + */ + void newConnection(Network::ConnectionSocketPtr&& socket, + const envoy::config::core::v3::Metadata& dynamic_metadata); + + /** + * Return the active connections container attached with the given filter chain. + */ + ActiveConnections& getOrCreateActiveConnections(const Network::FilterChain& filter_chain); + + /** + * Schedule to remove and destroy the active connections which are not tracked by listener + * config. Caution: The connection are not destroyed yet when function returns. + */ + void deferredRemoveFilterChains( + const std::list& draining_filter_chains); + + /** + * Update the listener config. The follow up connections will see the new config. The existing + * connections are not impacted. + */ + void updateListenerConfig(Network::ListenerConfig& config); + + ConnectionHandlerImpl& parent_; + std::unique_ptr internal_listener_; + const std::chrono::milliseconds listener_filters_timeout_; + const bool continue_on_listener_filters_timeout_; + std::list sockets_; + absl::node_hash_map connections_by_context_; + + // The number of connections currently active on this listener. This is typically used for + // connection balancing across per-handler listeners. + std::atomic num_listener_connections_{}; + bool is_deleting_{false}; + }; + /** * Wrapper for a group of active connections which are attached to the same filter chain context. */ class ActiveConnections : public Event::DeferredDeletable { public: - ActiveConnections(ActiveTcpListener& listener, const Network::FilterChain& filter_chain); + ActiveConnections(StreamListener& listener, const Network::FilterChain& filter_chain); ~ActiveConnections() override; - // listener filter chain pair is the owner of the connections - ActiveTcpListener& listener_; + // Listener filter chain pair is the owner of the connections. + StreamListener& listener_; const Network::FilterChain& filter_chain_; // Owned connections std::list connections_; @@ -244,19 +382,20 @@ class ConnectionHandlerImpl : public Network::ConnectionHandler, public Event::DeferredDeletable { ActiveTcpSocket(ActiveTcpListener& listener, Network::ConnectionSocketPtr&& socket, bool hand_off_restored_destination_connections) - : listener_(listener), socket_(std::move(socket)), + : stream_listener_(listener), socket_(std::move(socket)), hand_off_restored_destination_connections_(hand_off_restored_destination_connections), - iter_(accept_filters_.end()), stream_info_(std::make_unique( - listener_.parent_.dispatcher_.timeSource(), - StreamInfo::FilterState::LifeSpan::Connection)) { - listener_.stats_.downstream_pre_cx_active_.inc(); + stream_info_(std::make_unique( + stream_listener_.parent_.dispatcher_.timeSource(), + StreamInfo::FilterState::LifeSpan::Connection)), + iter_(accept_filters_.end()) { + stream_listener_.stats_.downstream_pre_cx_active_.inc(); stream_info_->setDownstreamLocalAddress(socket_->localAddress()); stream_info_->setDownstreamRemoteAddress(socket_->remoteAddress()); stream_info_->setDownstreamDirectRemoteAddress(socket_->directRemoteAddress()); } ~ActiveTcpSocket() override { accept_filters_.clear(); - listener_.stats_.downstream_pre_cx_active_.dec(); + stream_listener_.stats_.downstream_pre_cx_active_.dec(); // If the underlying socket is no longer attached, it means that it has been transferred to // an active connection. In this case, the active connection will decrement the number @@ -266,7 +405,7 @@ class ConnectionHandlerImpl : public Network::ConnectionHandler, // ActiveTcpConnection, having a shared object which does accounting (but would require // another allocation, etc.). if (socket_ != nullptr) { - listener_.decNumConnections(); + stream_listener_.decNumConnections(); } } @@ -274,46 +413,91 @@ class ConnectionHandlerImpl : public Network::ConnectionHandler, void startTimer(); void unlink(); void newConnection(); + bool isListenerFiltersCompleted() { return iter_ == accept_filters_.end(); } + bool isConnected() { return connected_; } - class GenericListenerFilter : public Network::ListenerFilter { - public: - GenericListenerFilter(const Network::ListenerFilterMatcherSharedPtr& matcher, - Network::ListenerFilterPtr listener_filter) - : listener_filter_(std::move(listener_filter)), matcher_(std::move(matcher)) {} - Network::FilterStatus onAccept(ListenerFilterCallbacks& cb) override { - if (isDisabled(cb)) { - return Network::FilterStatus::Continue; - } - return listener_filter_->onAccept(cb); - } - /** - * Check if this filter filter should be disabled on the incoming socket. - * @param cb the callbacks the filter instance can use to communicate with the filter chain. - **/ - bool isDisabled(ListenerFilterCallbacks& cb) { - if (matcher_ == nullptr) { - return false; - } else { - return matcher_->matches(cb); - } - } + // Network::ListenerFilterManager + void addAcceptFilter(const Network::ListenerFilterMatcherSharedPtr& listener_filter_matcher, + Network::ListenerFilterPtr&& filter) override { + accept_filters_.emplace_back(std::make_unique( + listener_filter_matcher, std::move(filter))); + } - private: - const Network::ListenerFilterPtr listener_filter_; - const Network::ListenerFilterMatcherSharedPtr matcher_; + // Network::ListenerFilterCallbacks + Network::ConnectionSocket& socket() override { return *socket_.get(); } + Event::Dispatcher& dispatcher() override { return stream_listener_.parent_.dispatcher_; } + void continueFilterChain(bool success) override; + void setDynamicMetadata(const std::string& name, const ProtobufWkt::Struct& value) override; + envoy::config::core::v3::Metadata& dynamicMetadata() override { + return stream_info_->dynamicMetadata(); + }; + const envoy::config::core::v3::Metadata& dynamicMetadata() const override { + return stream_info_->dynamicMetadata(); }; - using ListenerFilterWrapperPtr = std::unique_ptr; + + ActiveTcpListener& stream_listener_; + Network::ConnectionSocketPtr socket_; + const bool hand_off_restored_destination_connections_; + Event::TimerPtr timer_; + std::unique_ptr stream_info_; + + private: + std::list accept_filters_; + std::list::iterator iter_; + bool connected_{false}; + }; + + /** + * Wrapper for an active accepted internal socket owned by this handler. + */ + struct ActiveInternalSocket : public Network::ListenerFilterManager, + public Network::ListenerFilterCallbacks, + LinkedObject, + public Event::DeferredDeletable { + ActiveInternalSocket(ActiveInternalListener& listener, Network::ConnectionSocketPtr socket) + : stream_listener_(listener), socket_(std::move(socket)), + stream_info_(std::make_unique( + stream_listener_.parent_.dispatcher_.timeSource(), + StreamInfo::FilterState::LifeSpan::Connection)), + iter_(accept_filters_.end()) { + stream_listener_.stats_.downstream_pre_cx_active_.inc(); + stream_info_->setDownstreamLocalAddress(socket_->localAddress()); + stream_info_->setDownstreamRemoteAddress(socket_->remoteAddress()); + stream_info_->setDownstreamDirectRemoteAddress(socket_->directRemoteAddress()); + } + ~ActiveInternalSocket() override { + accept_filters_.clear(); + stream_listener_.stats_.downstream_pre_cx_active_.dec(); + + // If the underlying socket is no longer attached, it means that it has been transferred to + // an active connection. In this case, the active connection will decrement the number + // of listener connections. + // TODO(mattklein123): In general the way we account for the number of listener connections + // is incredibly fragile. Revisit this by potentially merging ActiveInternalSocket and + // ActiveTcpConnection, having a shared object which does accounting (but would require + // another allocation, etc.). + if (socket_ != nullptr) { + stream_listener_.decNumConnections(); + } + } + + void onTimeout(); + void startTimer(); + void unlink(); + void newConnection(); + bool isListenerFiltersCompleted() { return iter_ == accept_filters_.end(); } + bool isConnected() { return connected_; } // Network::ListenerFilterManager void addAcceptFilter(const Network::ListenerFilterMatcherSharedPtr& listener_filter_matcher, Network::ListenerFilterPtr&& filter) override { - accept_filters_.emplace_back( - std::make_unique(listener_filter_matcher, std::move(filter))); + accept_filters_.emplace_back(std::make_unique( + listener_filter_matcher, std::move(filter))); } // Network::ListenerFilterCallbacks Network::ConnectionSocket& socket() override { return *socket_.get(); } - Event::Dispatcher& dispatcher() override { return listener_.parent_.dispatcher_; } + Event::Dispatcher& dispatcher() override { return stream_listener_.parent_.dispatcher_; } void continueFilterChain(bool success) override; void setDynamicMetadata(const std::string& name, const ProtobufWkt::Struct& value) override; envoy::config::core::v3::Metadata& dynamicMetadata() override { @@ -323,31 +507,36 @@ class ConnectionHandlerImpl : public Network::ConnectionHandler, return stream_info_->dynamicMetadata(); }; - ActiveTcpListener& listener_; + ActiveInternalListener& stream_listener_; Network::ConnectionSocketPtr socket_; - const bool hand_off_restored_destination_connections_; - std::list accept_filters_; - std::list::iterator iter_; Event::TimerPtr timer_; std::unique_ptr stream_info_; + + private: + std::list accept_filters_; + std::list::iterator iter_; bool connected_{false}; }; using ActiveTcpListenerOptRef = absl::optional>; using UdpListenerCallbacksOptRef = absl::optional>; + using ActiveInternalListenerOptRef = + absl::optional>; struct ActiveListenerDetails { // Strong pointer to the listener, whether TCP, UDP, QUIC, etc. Network::ConnectionHandler::ActiveListenerPtr listener_; absl::variant, - std::reference_wrapper> + std::reference_wrapper, + std::reference_wrapper> typed_listener_; // Helpers for accessing the data in the variant for cleaner code. ActiveTcpListenerOptRef tcpListener(); UdpListenerCallbacksOptRef udpListener(); + ActiveInternalListenerOptRef internalListener(); }; using ActiveListenerDetailsOptRef = absl::optional>; diff --git a/source/server/listener_impl.cc b/source/server/listener_impl.cc index 3238108fa712a..450b578f56e5c 100644 --- a/source/server/listener_impl.cc +++ b/source/server/listener_impl.cc @@ -65,7 +65,7 @@ ListenSocketFactoryImpl::ListenSocketFactoryImpl(ListenerComponentFactory& facto const std::string& listener_name, bool reuse_port) : factory_(factory), local_address_(address), socket_type_(socket_type), options_(options), bind_to_port_(bind_to_port), listener_name_(listener_name), reuse_port_(reuse_port) { - + // TODO(lambdai): Dispatch by address type. bool create_socket = false; if (local_address_->type() == Network::Address::Type::Ip) { if (socket_type_ == Network::Socket::Type::Datagram) { @@ -80,10 +80,12 @@ ListenSocketFactoryImpl::ListenSocketFactoryImpl(ListenerComponentFactory& facto // then all worker threads should use same port. create_socket = true; } - } else { - ASSERT(local_address_->type() == Network::Address::Type::Pipe); + } else if (local_address_->type() == Network::Address::Type::Pipe) { // Listeners with Unix domain socket always use shared socket. create_socket = true; + } else { + ASSERT(local_address_->type() == Network::Address::Type::EnvoyInternal); + create_socket = false; } if (create_socket) { @@ -236,6 +238,7 @@ ListenerImpl::ListenerImpl(const envoy::config::listener::v3::Listener& config, workers_started_(workers_started), hash_(hash), tcp_backlog_size_( PROTOBUF_GET_WRAPPED_OR_DEFAULT(config, tcp_backlog_size, ENVOY_TCP_BACKLOG_SIZE)), + is_internal_listener_(config.has_internal_listener()), validation_visitor_( added_via_api_ ? parent_.server_.messageValidationContext().dynamicValidationVisitor() : parent_.server_.messageValidationContext().staticValidationVisitor()), @@ -315,6 +318,7 @@ ListenerImpl::ListenerImpl(ListenerImpl& origin, workers_started_(workers_started), hash_(hash), tcp_backlog_size_( PROTOBUF_GET_WRAPPED_OR_DEFAULT(config, tcp_backlog_size, ENVOY_TCP_BACKLOG_SIZE)), + is_internal_listener_(config.has_internal_listener()), validation_visitor_( added_via_api_ ? parent_.server_.messageValidationContext().dynamicValidationVisitor() : parent_.server_.messageValidationContext().staticValidationVisitor()), diff --git a/source/server/listener_impl.h b/source/server/listener_impl.h index ec0119b32b2f1..f1d29f591c460 100644 --- a/source/server/listener_impl.h +++ b/source/server/listener_impl.h @@ -3,8 +3,10 @@ #include #include "envoy/access_log/access_log.h" +#include "envoy/config/core/v3/address.pb.h" #include "envoy/config/core/v3/base.pb.h" #include "envoy/config/listener/v3/listener.pb.h" +#include "envoy/network/address.h" #include "envoy/network/drain_decision.h" #include "envoy/network/filter.h" #include "envoy/server/drain_manager.h" @@ -57,7 +59,9 @@ class ListenSocketFactoryImpl : public Network::ListenSocketFactory, * @return the socket shared by worker threads; otherwise return null. */ Network::SocketOptRef sharedSocket() const override { - if (!reuse_port_) { + if (!reuse_port_ && + // EnvoyInternalAddress is always handled by worker. + local_address_->type() != Network::Address::Type::EnvoyInternal) { ASSERT(socket_ != nullptr); return *socket_; } @@ -305,6 +309,9 @@ class ListenerImpl final : public Network::ListenerConfig, Network::UdpPacketWriterFactoryOptRef udpPacketWriterFactory() override { return Network::UdpPacketWriterFactoryOptRef(std::ref(*udp_writer_factory_)); } + bool isInternalListener() override { + return is_internal_listener_; + } Network::UdpListenerWorkerRouterOptRef udpListenerWorkerRouter() override { return udp_listener_worker_router_ ? Network::UdpListenerWorkerRouterOptRef(*udp_listener_worker_router_) @@ -378,6 +385,7 @@ class ListenerImpl final : public Network::ListenerConfig, const bool workers_started_; const uint64_t hash_; const uint32_t tcp_backlog_size_; + const bool is_internal_listener_; ProtobufMessage::ValidationVisitor& validation_visitor_; // A target is added to Server's InitManager if workers_started_ is false. diff --git a/source/server/listener_manager_impl.cc b/source/server/listener_manager_impl.cc index 8506d5cbc2862..5c4fabe5af244 100644 --- a/source/server/listener_manager_impl.cc +++ b/source/server/listener_manager_impl.cc @@ -334,11 +334,17 @@ ListenerManagerStats ListenerManagerImpl::generateStats(Stats::Scope& scope) { bool ListenerManagerImpl::addOrUpdateListener(const envoy::config::listener::v3::Listener& config, const std::string& version_info, bool added_via_api) { - RELEASE_ASSERT( - !config.address().has_envoy_internal_address(), - fmt::format("listener {} has envoy internal address {}. Internal address cannot be used by " - "listener yet", - config.name(), config.address().envoy_internal_address().DebugString())); + // Internal address type be used with internal listener field. + if (config.address().has_envoy_internal_address() || config.has_internal_listener()) { + if (!config.has_internal_listener() || !config.has_internal_listener()) { + ENVOY_LOG(debug, + "listener {} has envoy internal address {}. This address must be used with " + "internal listener.", + config.name(), config.address().envoy_internal_address().DebugString()); + return false; + } + } + // TODO(junr03): currently only one ApiListener can be installed via bootstrap to avoid having to // build a collection of listeners, and to have to be able to warm and drain the listeners. In the // future allow multiple ApiListeners, and allow them to be created via LDS as well as bootstrap. diff --git a/test/common/event/BUILD b/test/common/event/BUILD index b6032fe718258..2c36185208f27 100644 --- a/test/common/event/BUILD +++ b/test/common/event/BUILD @@ -19,6 +19,7 @@ envoy_cc_test( "//source/common/event:dispatcher_lib", "//source/common/stats:isolated_store_lib", "//test/mocks:common_lib", + "//test/mocks/network:connection_mocks", "//test/mocks/stats:stats_mocks", "//test/test_common:simulated_time_system_lib", "//test/test_common:test_runtime_lib", diff --git a/test/common/event/dispatcher_impl_test.cc b/test/common/event/dispatcher_impl_test.cc index c6c6a7a96272d..ece54adaaaa4e 100644 --- a/test/common/event/dispatcher_impl_test.cc +++ b/test/common/event/dispatcher_impl_test.cc @@ -1,5 +1,6 @@ #include +#include "common/buffer/buffer_impl.h" #include "envoy/thread/thread.h" #include "common/api/api_impl.h" @@ -8,7 +9,10 @@ #include "common/event/dispatcher_impl.h" #include "common/event/timer_impl.h" #include "common/stats/isolated_store_impl.h" +#include "common/network/address_impl.h" +#include "common/network/connection_impl.h" +#include "test/mocks/network/connection.h" #include "test/mocks/common.h" #include "test/mocks/stats/mocks.h" #include "test/test_common/simulated_time_system.h" @@ -1147,6 +1151,111 @@ TEST_F(TimerUtilsTest, TimerValueConversion) { checkConversion(std::chrono::milliseconds(600014), 600, 14000); } +class InternalConnectionDispatcherTest : public testing::Test { +protected: + InternalConnectionDispatcherTest() + : api_(Api::createApiForTest()), dispatcher_(api_->allocateDispatcher("test_thread")), + dispatcher_impl_(static_cast(dispatcher_.get())) {} + + Api::ApiPtr api_; + DispatcherPtr dispatcher_; + DispatcherImpl* dispatcher_impl_; + const std::shared_ptr remote_address_{ + std::make_shared("listener_addr")}; + const std::shared_ptr source_address_{ + std::make_shared("local_addr")}; +}; + +TEST_F(InternalConnectionDispatcherTest, CreateInternalClientConnection) { + std::unique_ptr server_conn_socket; + auto socket_save_callback = + [&server_conn_socket](const Network::Address::InstanceConstSharedPtr&, + std::unique_ptr internal_socket) { + server_conn_socket = std::move(internal_socket); + }; + dispatcher_impl_->registerInternalListener(remote_address_->asString(), socket_save_callback); + auto client = dispatcher_->createInternalConnection(remote_address_, source_address_); + + Network::MockConnectionCallbacks client_callbacks; + client->addConnectionCallbacks(client_callbacks); + EXPECT_EQ(Network::Connection::State::Open, client->state()); + + EXPECT_CALL(client_callbacks, onEvent(Network::ConnectionEvent::LocalClose)).Times(1); + client->close(Network::ConnectionCloseType::NoFlush); + EXPECT_EQ(Network::Connection::State::Closed, client->state()); + + EXPECT_TRUE(server_conn_socket->ioHandle().isOpen()); + server_conn_socket->ioHandle().close(); + EXPECT_FALSE(server_conn_socket->ioHandle().isOpen()); +} + +TEST_F(InternalConnectionDispatcherTest, InternalClientConnectionAndConnect) { + std::unique_ptr server_conn_socket; + auto socket_save_callback = + [&server_conn_socket](const Network::Address::InstanceConstSharedPtr&, + std::unique_ptr internal_socket) { + server_conn_socket = std::move(internal_socket); + }; + dispatcher_impl_->registerInternalListener(remote_address_->asString(), socket_save_callback); + auto client = dispatcher_->createInternalConnection(remote_address_, source_address_); + + Network::MockConnectionCallbacks client_callbacks; + client->addConnectionCallbacks(client_callbacks); + EXPECT_EQ(Network::Connection::State::Open, client->state()); + + EXPECT_CALL(client_callbacks, onEvent(Network::ConnectionEvent::Connected)).Times(1); + dispatcher_impl_->run(Dispatcher::RunType::NonBlock); + + EXPECT_CALL(client_callbacks, onEvent(Network::ConnectionEvent::LocalClose)).Times(1); + client->close(Network::ConnectionCloseType::NoFlush); + EXPECT_EQ(Network::Connection::State::Closed, client->state()); + + EXPECT_TRUE(server_conn_socket->ioHandle().isOpen()); + server_conn_socket->ioHandle().close(); + EXPECT_FALSE(server_conn_socket->ioHandle().isOpen()); +} + +TEST_F(InternalConnectionDispatcherTest, InternalClientConnectionBasicReadWrite) { + std::unique_ptr server_conn_socket; + auto socket_save_callback = + [&server_conn_socket](const Network::Address::InstanceConstSharedPtr&, + std::unique_ptr internal_socket) { + server_conn_socket = std::move(internal_socket); + }; + dispatcher_impl_->registerInternalListener(remote_address_->asString(), socket_save_callback); + auto client = dispatcher_->createInternalConnection(remote_address_, source_address_); + + Network::MockConnectionCallbacks client_callbacks; + client->addConnectionCallbacks(client_callbacks); + EXPECT_EQ(Network::Connection::State::Open, client->state()); + + EXPECT_CALL(client_callbacks, onEvent(Network::ConnectionEvent::Connected)).Times(1); + dispatcher_impl_->run(Dispatcher::RunType::NonBlock); + + // Client connection write. + { + Buffer::OwnedImpl buf; + buf.add("hello"); + // Write to client connection write buffer. + client->write(buf, false); + // Transport socket write from connection write buffer to server connection buffer. + dispatcher_impl_->run(Dispatcher::RunType::NonBlock); + } + + // Server socket read. + { + Buffer::OwnedImpl buf; + Buffer::RawSlice slice; + buf.reserve(1024, &slice, 1); + auto res = server_conn_socket->ioHandle().readv(1024, &slice, 1); + EXPECT_TRUE(res.ok()); + EXPECT_EQ("hello", absl::string_view(static_cast(slice.mem_), res.rc_)); + } + + EXPECT_CALL(client_callbacks, onEvent(Network::ConnectionEvent::LocalClose)).Times(1); + client->close(Network::ConnectionCloseType::NoFlush); + server_conn_socket->ioHandle().close(); +} } // namespace } // namespace Event } // namespace Envoy diff --git a/test/common/network/BUILD b/test/common/network/BUILD index 31b558b10c50c..f014f12c7d796 100644 --- a/test/common/network/BUILD +++ b/test/common/network/BUILD @@ -95,6 +95,30 @@ envoy_cc_test( ], ) +envoy_cc_test( + name = "closing_connection_impl_test", + srcs = ["closing_connection_impl_test.cc"], + deps = [ + "//source/common/buffer:buffer_lib", + "//source/common/common:empty_string", + "//source/common/event:dispatcher_includes", + "//source/common/event:dispatcher_lib", + "//source/common/network:connection_lib", + "//source/common/network:listen_socket_lib", + "//source/common/network:utility_lib", + "//source/common/stats:stats_lib", + "//test/mocks/buffer:buffer_mocks", + "//test/mocks/event:event_mocks", + "//test/mocks/network:network_mocks", + "//test/mocks/stats:stats_mocks", + "//test/test_common:environment_lib", + "//test/test_common:network_utility_lib", + "//test/test_common:simulated_time_system_lib", + "//test/test_common:test_time_lib", + "@envoy_api//envoy/config/core/v3:pkg_cc_proto", + ], +) + envoy_cc_test( name = "dns_impl_test", srcs = ["dns_impl_test.cc"], @@ -380,6 +404,17 @@ envoy_cc_test( ], ) +envoy_cc_test( + name = "buffered_io_socket_handle_impl_test", + srcs = ["buffered_io_socket_handle_impl_test.cc"], + deps = [ + "//source/common/common:utility_lib", + "//source/common/network:address_lib", + "//source/common/network:buffered_io_socket_handle_lib", + "//test/mocks/event:event_mocks", + ], +) + envoy_cc_test( name = "transport_socket_options_impl_test", srcs = ["transport_socket_options_impl_test.cc"], diff --git a/test/common/network/buffered_io_socket_handle_impl_test.cc b/test/common/network/buffered_io_socket_handle_impl_test.cc new file mode 100644 index 0000000000000..67c5b205e0ade --- /dev/null +++ b/test/common/network/buffered_io_socket_handle_impl_test.cc @@ -0,0 +1,478 @@ +#include + +#include "envoy/event/file_event.h" + +#include "common/buffer/buffer_impl.h" +#include "common/network/buffered_io_socket_handle_impl.h" + +#include "test/mocks/event/mocks.h" + +#include "absl/container/fixed_array.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +using testing::_; +using testing::InSequence; +using testing::Invoke; +using testing::NiceMock; +using testing::Return; +using testing::WithArgs; + +namespace Envoy { +namespace Network { +namespace { + +class MockFileEventCallback { +public: + MOCK_METHOD(void, called, (uint32_t arg)); +}; +class BufferedIoSocketHandleTest : public testing::Test { +public: + BufferedIoSocketHandleTest() : buf_(1024) { + io_handle_ = std::make_unique(); + io_handle_peer_ = std::make_unique(); + io_handle_->setWritablePeer(io_handle_peer_.get()); + io_handle_peer_->setWritablePeer(io_handle_.get()); + } + ~BufferedIoSocketHandleTest() override { + if (io_handle_->isOpen()) { + io_handle_->close(); + } + if (io_handle_peer_->isOpen()) { + io_handle_peer_->close(); + } + } + void expectAgain() { + auto res = io_handle_->recv(buf_.data(), buf_.size(), MSG_PEEK); + EXPECT_FALSE(res.ok()); + EXPECT_EQ(Api::IoError::IoErrorCode::Again, res.err_->getErrorCode()); + } + NiceMock dispatcher_{}; + + // Owned by BufferedIoSocketHandle. + NiceMock* scheduable_cb_; + MockFileEventCallback cb_; + std::unique_ptr io_handle_; + std::unique_ptr io_handle_peer_; + absl::FixedArray buf_; +}; + +// Test recv side effects. +TEST_F(BufferedIoSocketHandleTest, TestBasicRecv) { + auto res = io_handle_->recv(buf_.data(), buf_.size(), 0); + // EAGAIN. + EXPECT_FALSE(res.ok()); + EXPECT_EQ(Api::IoError::IoErrorCode::Again, res.err_->getErrorCode()); + io_handle_->setWriteEnd(); + res = io_handle_->recv(buf_.data(), buf_.size(), 0); + EXPECT_FALSE(res.ok()); + EXPECT_NE(Api::IoError::IoErrorCode::Again, res.err_->getErrorCode()); +} + +// Test recv side effects. +TEST_F(BufferedIoSocketHandleTest, TestBasicPeek) { + auto res = io_handle_->recv(buf_.data(), buf_.size(), MSG_PEEK); + // EAGAIN. + EXPECT_FALSE(res.ok()); + EXPECT_EQ(Api::IoError::IoErrorCode::Again, res.err_->getErrorCode()); + io_handle_->setWriteEnd(); + res = io_handle_->recv(buf_.data(), buf_.size(), MSG_PEEK); + EXPECT_FALSE(res.ok()); + EXPECT_NE(Api::IoError::IoErrorCode::Again, res.err_->getErrorCode()); +} + +TEST_F(BufferedIoSocketHandleTest, TestRecvDrain) { + auto& internal_buffer = io_handle_->getBufferForTest(); + internal_buffer.add("abcd"); + auto res = io_handle_->recv(buf_.data(), buf_.size(), 0); + EXPECT_TRUE(res.ok()); + EXPECT_EQ(4, res.rc_); + EXPECT_EQ(absl::string_view(buf_.data(), 4), "abcd"); + EXPECT_EQ(0, internal_buffer.length()); + expectAgain(); +} + +TEST_F(BufferedIoSocketHandleTest, FlowControl) { + auto& internal_buffer = io_handle_->getBufferForTest(); + WritablePeer* handle_as_peer = io_handle_.get(); + internal_buffer.setWatermarks(128); + EXPECT_FALSE(io_handle_->isReadable()); + EXPECT_TRUE(io_handle_peer_->isWritable()); + + std::string big_chunk(256, 'a'); + internal_buffer.add(big_chunk); + EXPECT_TRUE(io_handle_->isReadable()); + EXPECT_FALSE(handle_as_peer->isWritable()); + + bool writable_flipped = false; + // During the repeated recv, the writable flag must switch to true. + while (internal_buffer.length() > 0) { + SCOPED_TRACE(internal_buffer.length()); + EXPECT_TRUE(io_handle_->isReadable()); + bool writable = handle_as_peer->isWritable(); + if (writable) { + writable_flipped = true; + } else { + ASSERT_FALSE(writable_flipped); + } + auto res = io_handle_->recv(buf_.data(), 32, 0); + EXPECT_TRUE(res.ok()); + EXPECT_EQ(32, res.rc_); + } + ASSERT_EQ(0, internal_buffer.length()); + ASSERT_TRUE(writable_flipped); + + // Finally the buffer is empty. + EXPECT_FALSE(io_handle_->isReadable()); + EXPECT_TRUE(handle_as_peer->isWritable()); +} + +TEST_F(BufferedIoSocketHandleTest, EventScheduleBasic) { + scheduable_cb_ = new NiceMock(&dispatcher_); + EXPECT_CALL(*scheduable_cb_, scheduleCallbackNextIteration()); + auto ev = io_handle_->createFileEvent( + dispatcher_, [this](uint32_t events) { cb_.called(events); }, + Event::PlatformDefaultTriggerType, Event::FileReadyType::Read); + + EXPECT_CALL(cb_, called(_)); + scheduable_cb_->invokeCallback(); + ev.reset(); +} + +TEST_F(BufferedIoSocketHandleTest, TestSetEnabledTriggerEventSchedule) { + scheduable_cb_ = new NiceMock(&dispatcher_); + EXPECT_CALL(*scheduable_cb_, scheduleCallbackNextIteration()); + auto ev = io_handle_->createFileEvent( + dispatcher_, [this](uint32_t events) { cb_.called(events); }, + Event::PlatformDefaultTriggerType, Event::FileReadyType::Read); + + ASSERT_TRUE(scheduable_cb_->enabled()); + EXPECT_CALL(cb_, called(Event::FileReadyType::Read)); + scheduable_cb_->invokeCallback(); + ASSERT_FALSE(scheduable_cb_->enabled()); + + EXPECT_CALL(*scheduable_cb_, scheduleCallbackNextIteration()); + ev->setEnabled(Event::FileReadyType::Read); + ASSERT_TRUE(scheduable_cb_->enabled()); + EXPECT_CALL(cb_, called(_)); + scheduable_cb_->invokeCallback(); + ASSERT_FALSE(scheduable_cb_->enabled()); + + EXPECT_CALL(*scheduable_cb_, scheduleCallbackNextIteration()); + ev->setEnabled(Event::FileReadyType::Write); + ASSERT_TRUE(scheduable_cb_->enabled()); + EXPECT_CALL(cb_, called(Event::FileReadyType::Write)); + scheduable_cb_->invokeCallback(); + ASSERT_FALSE(scheduable_cb_->enabled()); + + EXPECT_CALL(*scheduable_cb_, scheduleCallbackNextIteration()); + ev->setEnabled(Event::FileReadyType::Write | Event::FileReadyType::Read); + ASSERT_TRUE(scheduable_cb_->enabled()); + EXPECT_CALL(cb_, called(Event::FileReadyType::Write | Event::FileReadyType::Read)); + scheduable_cb_->invokeCallback(); + ASSERT_FALSE(scheduable_cb_->enabled()); + ev.reset(); +} + +TEST_F(BufferedIoSocketHandleTest, TestReadAndWriteAreEdgeTriggered) { + scheduable_cb_ = new NiceMock(&dispatcher_); + EXPECT_CALL(*scheduable_cb_, scheduleCallbackNextIteration()); + auto ev = io_handle_->createFileEvent( + dispatcher_, [this](uint32_t events) { cb_.called(events); }, + Event::PlatformDefaultTriggerType, Event::FileReadyType::Read); + + EXPECT_CALL(cb_, called(_)); + scheduable_cb_->invokeCallback(); + + // Neither read and write will trigger self readiness. + EXPECT_CALL(cb_, called(_)).Times(0); + + // Drain 1 bytes. + auto& internal_buffer = io_handle_->getBufferForTest(); + internal_buffer.add("abcd"); + auto res = io_handle_->recv(buf_.data(), 1, 0); + EXPECT_TRUE(res.ok()); + EXPECT_EQ(1, res.rc_); + + ASSERT_FALSE(scheduable_cb_->enabled()); + ev.reset(); +} + +TEST_F(BufferedIoSocketHandleTest, TestSetDisabledBlockEventSchedule) { + scheduable_cb_ = new NiceMock(&dispatcher_); + EXPECT_CALL(*scheduable_cb_, scheduleCallbackNextIteration()); + auto ev = io_handle_->createFileEvent( + dispatcher_, [this](uint32_t events) { cb_.called(events); }, + Event::PlatformDefaultTriggerType, Event::FileReadyType::Read); + + ev->setEnabled(0); + + EXPECT_CALL(cb_, called(0)); + scheduable_cb_->invokeCallback(); + + ASSERT_FALSE(scheduable_cb_->enabled()); + ev.reset(); +} + +TEST_F(BufferedIoSocketHandleTest, TestEventResetClearCallback) { + scheduable_cb_ = new NiceMock(&dispatcher_); + EXPECT_CALL(*scheduable_cb_, scheduleCallbackNextIteration()); + auto ev = io_handle_->createFileEvent( + dispatcher_, [this](uint32_t events) { cb_.called(events); }, + Event::PlatformDefaultTriggerType, Event::FileReadyType::Read); + ASSERT_TRUE(scheduable_cb_->enabled()); + + ev.reset(); + ASSERT_FALSE(scheduable_cb_->enabled()); +} + +TEST_F(BufferedIoSocketHandleTest, TestDrainToLowWaterMarkTriggerReadEvent) { + auto& internal_buffer = io_handle_->getBufferForTest(); + WritablePeer* handle_as_peer = io_handle_.get(); + internal_buffer.setWatermarks(128); + EXPECT_FALSE(io_handle_->isReadable()); + EXPECT_TRUE(io_handle_peer_->isWritable()); + + std::string big_chunk(256, 'a'); + internal_buffer.add(big_chunk); + EXPECT_TRUE(io_handle_->isReadable()); + EXPECT_FALSE(handle_as_peer->isWritable()); + + // Clear invoke callback on peer. + scheduable_cb_ = new NiceMock(&dispatcher_); + EXPECT_CALL(*scheduable_cb_, scheduleCallbackNextIteration()); + auto ev = io_handle_peer_->createFileEvent( + dispatcher_, [this](uint32_t events) { cb_.called(events); }, + Event::PlatformDefaultTriggerType, Event::FileReadyType::Read); + ASSERT_TRUE(scheduable_cb_->enabled()); + EXPECT_CALL(cb_, called(_)); + scheduable_cb_->invokeCallback(); + ASSERT_FALSE(scheduable_cb_->enabled()); + + { + auto res = io_handle_->recv(buf_.data(), 1, 0); + EXPECT_FALSE(handle_as_peer->isWritable()); + } + { + EXPECT_CALL(*scheduable_cb_, scheduleCallbackNextIteration()).Times(1); + auto res = io_handle_->recv(buf_.data(), 232, 0); + EXPECT_TRUE(handle_as_peer->isWritable()); + } + + EXPECT_CALL(*scheduable_cb_, scheduleCallbackNextIteration()).Times(1); + io_handle_->close(); +} + +TEST_F(BufferedIoSocketHandleTest, TestClose) { + auto& internal_buffer = io_handle_->getBufferForTest(); + internal_buffer.add("abcd"); + std::string accumulator; + scheduable_cb_ = new NiceMock(&dispatcher_); + EXPECT_CALL(*scheduable_cb_, scheduleCallbackNextIteration()); + bool should_close = false; + auto ev = io_handle_->createFileEvent( + dispatcher_, + [this, &should_close, handle = io_handle_.get(), &accumulator](uint32_t events) { + if (events & Event::FileReadyType::Read) { + auto res = io_handle_->recv(buf_.data(), buf_.size(), 0); + if (res.ok()) { + accumulator += absl::string_view(buf_.data(), res.rc_); + } else if (res.err_->getErrorCode() == Api::IoError::IoErrorCode::Again) { + ENVOY_LOG_MISC(debug, "read returns EAGAIN"); + } else { + ENVOY_LOG_MISC(debug, "will close"); + should_close = true; + } + } + }, + Event::PlatformDefaultTriggerType, Event::FileReadyType::Read); + scheduable_cb_->invokeCallback(); + + // Not closed yet. + ASSERT_FALSE(should_close); + + EXPECT_CALL(*scheduable_cb_, scheduleCallbackNextIteration()); + io_handle_peer_->close(); + + ASSERT_TRUE(scheduable_cb_->enabled()); + scheduable_cb_->invokeCallback(); + ASSERT_TRUE(should_close); + + EXPECT_CALL(*scheduable_cb_, scheduleCallbackNextIteration()).Times(0); + io_handle_->close(); + EXPECT_EQ(4, accumulator.size()); + ev.reset(); +} + +TEST_F(BufferedIoSocketHandleTest, TestShutdown) { + auto& internal_buffer = io_handle_->getBufferForTest(); + internal_buffer.add("abcd"); + + std::string accumulator; + scheduable_cb_ = new NiceMock(&dispatcher_); + EXPECT_CALL(*scheduable_cb_, scheduleCallbackNextIteration()); + bool should_close = false; + auto ev = io_handle_->createFileEvent( + dispatcher_, + [this, &should_close, handle = io_handle_.get(), &accumulator](uint32_t events) { + if (events & Event::FileReadyType::Read) { + auto res = io_handle_->recv(buf_.data(), buf_.size(), 0); + if (res.ok()) { + accumulator += absl::string_view(buf_.data(), res.rc_); + } else if (res.err_->getErrorCode() == Api::IoError::IoErrorCode::Again) { + ENVOY_LOG_MISC(debug, "read returns EAGAIN"); + } else { + ENVOY_LOG_MISC(debug, "will close"); + should_close = true; + } + } + }, + Event::PlatformDefaultTriggerType, Event::FileReadyType::Read); + scheduable_cb_->invokeCallback(); + + // Not closed yet. + ASSERT_FALSE(should_close); + + EXPECT_CALL(*scheduable_cb_, scheduleCallbackNextIteration()); + io_handle_peer_->shutdown(ENVOY_SHUT_WR); + + ASSERT_TRUE(scheduable_cb_->enabled()); + scheduable_cb_->invokeCallback(); + ASSERT_TRUE(should_close); + EXPECT_EQ(4, accumulator.size()); + io_handle_->close(); + ev.reset(); +} + +TEST_F(BufferedIoSocketHandleTest, TestWriteToPeer) { + std::string raw_data("0123456789"); + absl::InlinedVector slices{ + // Contains 1 byte. + Buffer::RawSlice{static_cast(raw_data.data()), 1}, + // Contains 0 byte. + Buffer::RawSlice{nullptr, 1}, + // Contains 0 byte. + Buffer::RawSlice{raw_data.data() + 1, 0}, + // Contains 2 byte. + Buffer::RawSlice{raw_data.data() + 1, 2}, + }; + io_handle_peer_->writev(slices.data(), slices.size()); + auto& internal_buffer = io_handle_->getBufferForTest(); + EXPECT_EQ(3, internal_buffer.length()); + EXPECT_EQ("012", internal_buffer.toString()); +} + +TEST_F(BufferedIoSocketHandleTest, TestWriteScheduleWritableEvent) { + std::string accumulator; + scheduable_cb_ = new NiceMock(&dispatcher_); + EXPECT_CALL(*scheduable_cb_, scheduleCallbackNextIteration()); + bool should_close = false; + auto ev = io_handle_->createFileEvent( + dispatcher_, + [&should_close, handle = io_handle_.get(), &accumulator](uint32_t events) { + if (events & Event::FileReadyType::Read) { + Buffer::OwnedImpl buf; + Buffer::RawSlice slice; + buf.reserve(1024, &slice, 1); + auto res = handle->readv(1024, &slice, 1); + if (res.ok()) { + accumulator += absl::string_view(static_cast(slice.mem_), res.rc_); + } else if (res.err_->getErrorCode() == Api::IoError::IoErrorCode::Again) { + ENVOY_LOG_MISC(debug, "read returns EAGAIN"); + } else { + ENVOY_LOG_MISC(debug, "will close"); + should_close = true; + } + } + }, + Event::PlatformDefaultTriggerType, Event::FileReadyType::Read); + scheduable_cb_->invokeCallback(); + EXPECT_FALSE(scheduable_cb_->enabled()); + + std::string raw_data("0123456789"); + Buffer::RawSlice slice{static_cast(raw_data.data()), raw_data.size()}; + EXPECT_CALL(*scheduable_cb_, scheduleCallbackNextIteration()); + io_handle_peer_->writev(&slice, 1); + + EXPECT_TRUE(scheduable_cb_->enabled()); + scheduable_cb_->invokeCallback(); + EXPECT_EQ("0123456789", accumulator); + EXPECT_FALSE(should_close); + + io_handle_->close(); +} + +TEST_F(BufferedIoSocketHandleTest, TestReadAfterShutdownWrite) { + io_handle_peer_->shutdown(ENVOY_SHUT_WR); + ENVOY_LOG_MISC(debug, "lambdai: after {} shutdown write ", + static_cast(io_handle_peer_.get())); + std::string accumulator; + scheduable_cb_ = new NiceMock(&dispatcher_); + EXPECT_CALL(*scheduable_cb_, scheduleCallbackNextIteration()); + bool should_close = false; + auto ev = io_handle_peer_->createFileEvent( + dispatcher_, + [&should_close, handle = io_handle_peer_.get(), &accumulator](uint32_t events) { + if (events & Event::FileReadyType::Read) { + Buffer::OwnedImpl buf; + Buffer::RawSlice slice; + buf.reserve(1024, &slice, 1); + auto res = handle->readv(1024, &slice, 1); + if (res.ok()) { + accumulator += absl::string_view(static_cast(slice.mem_), res.rc_); + } else if (res.err_->getErrorCode() == Api::IoError::IoErrorCode::Again) { + ENVOY_LOG_MISC(debug, "read returns EAGAIN"); + } else { + ENVOY_LOG_MISC(debug, "will close"); + should_close = true; + } + } + }, + Event::PlatformDefaultTriggerType, Event::FileReadyType::Read); + scheduable_cb_->invokeCallback(); + + EXPECT_FALSE(scheduable_cb_->enabled()); + std::string raw_data("0123456789"); + Buffer::RawSlice slice{static_cast(raw_data.data()), raw_data.size()}; + EXPECT_CALL(*scheduable_cb_, scheduleCallbackNextIteration()); + io_handle_->writev(&slice, 1); + EXPECT_TRUE(scheduable_cb_->enabled()); + + scheduable_cb_->invokeCallback(); + EXPECT_FALSE(scheduable_cb_->enabled()); + EXPECT_EQ(raw_data, accumulator); + + EXPECT_CALL(*scheduable_cb_, scheduleCallbackNextIteration()); + io_handle_->close(); + ev.reset(); +} + +TEST_F(BufferedIoSocketHandleTest, TestNotififyWritableAfterShutdownWrite) { + auto& peer_internal_buffer = io_handle_peer_->getBufferForTest(); + peer_internal_buffer.setWatermarks(128); + std::string big_chunk(256, 'a'); + peer_internal_buffer.add(big_chunk); + EXPECT_FALSE(io_handle_peer_->isWritable()); + + io_handle_peer_->shutdown(ENVOY_SHUT_WR); + ENVOY_LOG_MISC(debug, "lambdai: after {} shutdown write ", + static_cast(io_handle_peer_.get())); + + scheduable_cb_ = new NiceMock(&dispatcher_); + EXPECT_CALL(*scheduable_cb_, scheduleCallbackNextIteration()); + auto ev = io_handle_->createFileEvent( + dispatcher_, [&, handle = io_handle_.get()](uint32_t) {}, Event::PlatformDefaultTriggerType, + Event::FileReadyType::Read); + scheduable_cb_->invokeCallback(); + EXPECT_FALSE(scheduable_cb_->enabled()); + + EXPECT_CALL(*scheduable_cb_, scheduleCallbackNextIteration()); + peer_internal_buffer.drain(peer_internal_buffer.length()); + EXPECT_TRUE(scheduable_cb_->enabled()); + + io_handle_->close(); +} + +} // namespace +} // namespace Network +} // namespace Envoy \ No newline at end of file diff --git a/test/common/network/closing_connection_impl_test.cc b/test/common/network/closing_connection_impl_test.cc new file mode 100644 index 0000000000000..20513ec3cf885 --- /dev/null +++ b/test/common/network/closing_connection_impl_test.cc @@ -0,0 +1,93 @@ +#include +#include +#include + +#include "envoy/common/platform.h" +#include "envoy/config/core/v3/base.pb.h" + +#include "common/buffer/buffer_impl.h" +#include "common/common/empty_string.h" +#include "common/common/fmt.h" +#include "common/event/dispatcher_impl.h" +#include "common/network/address_impl.h" +#include "common/network/connection_impl.h" +#include "common/network/io_socket_handle_impl.h" +#include "common/network/listen_socket_impl.h" +#include "common/network/utility.h" +#include "common/runtime/runtime_impl.h" + +#include "test/mocks/network/connection.h" +#include "test/mocks/buffer/mocks.h" +#include "test/mocks/event/mocks.h" +#include "test/mocks/network/mocks.h" +#include "test/mocks/stats/mocks.h" +#include "test/test_common/environment.h" +#include "test/test_common/network_utility.h" +#include "test/test_common/printers.h" +#include "test/test_common/simulated_time_system.h" +#include "test/test_common/utility.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +using testing::_; +using testing::AnyNumber; +using testing::DoAll; +using testing::Eq; +using testing::InSequence; +using testing::Invoke; +using testing::InvokeWithoutArgs; +using testing::Return; +using testing::SaveArg; +using testing::Sequence; +using testing::StrictMock; + +namespace Envoy { +namespace Network { + +class ClosingClientConnectionImplTest : public testing::Test { +protected: + ClosingClientConnectionImplTest() + : api_(Api::createApiForTest()), dispatcher_(api_->allocateDispatcher("test_thread")) {} + + Api::ApiPtr api_; + Event::DispatcherPtr dispatcher_; + const std::shared_ptr remote_address_{ + std::make_shared("listener_addr")}; + const std::shared_ptr source_address_{ + std::make_shared("local_addr")}; +}; + +TEST_F(ClosingClientConnectionImplTest, ActiveClose) { + ClosingClientConnectionImpl conn(*dispatcher_, remote_address_, source_address_); + conn.close(ConnectionCloseType::NoFlush); + EXPECT_EQ(Connection::State::Closed, conn.state()); +} + +TEST_F(ClosingClientConnectionImplTest, PassiveCloseDriveByDispatcher) { + MockConnectionCallbacks client_callbacks; + ClosingClientConnectionImpl conn(*dispatcher_, remote_address_, source_address_); + conn.addConnectionCallbacks(client_callbacks); + EXPECT_EQ(Connection::State::Open, conn.state()); + + EXPECT_CALL(client_callbacks, onEvent(Network::ConnectionEvent::RemoteClose)).Times(1); + dispatcher_->run(Event::Dispatcher::RunType::NonBlock); + EXPECT_EQ(Connection::State::Closed, conn.state()); +} + +TEST_F(ClosingClientConnectionImplTest, ClosingConnectionCreatedByDispatcher) { + auto conn = dispatcher_->createInternalConnection( + std::make_shared("listener_id"), + std::make_shared("client_id")); + + MockConnectionCallbacks client_callbacks; + conn->addConnectionCallbacks(client_callbacks); + EXPECT_EQ(Connection::State::Open, conn->state()); + + EXPECT_CALL(client_callbacks, onEvent(Network::ConnectionEvent::RemoteClose)).Times(1); + dispatcher_->run(Event::Dispatcher::RunType::NonBlock); + EXPECT_EQ(Connection::State::Closed, conn->state()); +} + +} // namespace Network +} // namespace Envoy diff --git a/test/config/utility.cc b/test/config/utility.cc index b248c80d5600b..594f81b82f695 100644 --- a/test/config/utility.cc +++ b/test/config/utility.cc @@ -529,6 +529,11 @@ ConfigHelper::ConfigHelper(const Network::Address::IpVersion version, Api::Api& auto* static_resources = bootstrap_.mutable_static_resources(); for (int i = 0; i < static_resources->listeners_size(); ++i) { auto* listener = static_resources->mutable_listeners(i); + // Not need to amend internal address and we need to skip the below implicit address mutation + // helper. + if (listener->mutable_address()->has_envoy_internal_address()) { + continue; + } auto* listener_socket_addr = listener->mutable_address()->mutable_socket_address(); if (listener_socket_addr->address() == "0.0.0.0" || listener_socket_addr->address() == "::") { listener_socket_addr->set_address(Network::Test::getAnyAddressString(version)); @@ -780,10 +785,10 @@ void ConfigHelper::setDefaultHostAndRoute(const std::string& domains, const std: void ConfigHelper::setBufferLimits(uint32_t upstream_buffer_limit, uint32_t downstream_buffer_limit) { RELEASE_ASSERT(!finalized_, ""); - RELEASE_ASSERT(bootstrap_.mutable_static_resources()->listeners_size() == 1, ""); - auto* listener = bootstrap_.mutable_static_resources()->mutable_listeners(0); - listener->mutable_per_connection_buffer_limit_bytes()->set_value(downstream_buffer_limit); - + for (int i = 0; i < bootstrap_.mutable_static_resources()->listeners_size(); ++i) { + auto* listener = bootstrap_.mutable_static_resources()->mutable_listeners(0); + listener->mutable_per_connection_buffer_limit_bytes()->set_value(downstream_buffer_limit); + } auto* static_resources = bootstrap_.mutable_static_resources(); for (int i = 0; i < bootstrap_.mutable_static_resources()->clusters_size(); ++i) { auto* cluster = static_resources->mutable_clusters(i); diff --git a/test/extensions/common/proxy_protocol/proxy_protocol_regression_test.cc b/test/extensions/common/proxy_protocol/proxy_protocol_regression_test.cc index 65f948f22011e..1603f32f9ff8e 100644 --- a/test/extensions/common/proxy_protocol/proxy_protocol_regression_test.cc +++ b/test/extensions/common/proxy_protocol/proxy_protocol_regression_test.cc @@ -72,6 +72,9 @@ class ProxyProtocolRegressionTest : public testing::TestWithParam + #include "common/common/hex.h" #include "common/http/utility.h" +#include "common/network/buffered_io_socket_handle_impl.h" #include "common/network/io_socket_handle_impl.h" #include "extensions/filters/listener/http_inspector/http_inspector.h" @@ -654,6 +657,73 @@ TEST_F(HttpInspectorTest, Http1WithLargeHeader) { EXPECT_EQ(1, cfg_->stats().http10_found_.value()); } +class HttpInspectorTestWithBufferIoSocket : public testing::Test { +public: + HttpInspectorTestWithBufferIoSocket() : cfg_(std::make_shared(store_)) { + io_handle_ = std::make_unique(); + io_handle_peer_ = std::make_unique(); + io_handle_->setWritablePeer(io_handle_peer_.get()); + io_handle_peer_->setWritablePeer(io_handle_.get()); + } + ~HttpInspectorTestWithBufferIoSocket() override { + io_handle_->close(); + io_handle_peer_->close(); + } + + absl::optional init(bool include_inline_recv = true) { + filter_ = std::make_unique(cfg_); + + EXPECT_CALL(cb_, socket()).WillRepeatedly(ReturnRef(socket_)); + EXPECT_CALL(socket_, detectedTransportProtocol()).WillRepeatedly(Return("raw_buffer")); + EXPECT_CALL(cb_, dispatcher()).WillRepeatedly(ReturnRef(dispatcher_)); + EXPECT_CALL(testing::Const(socket_), ioHandle()).WillRepeatedly(ReturnRef(*io_handle_)); + EXPECT_CALL(socket_, ioHandle()).WillRepeatedly(ReturnRef(*io_handle_)); + + if (include_inline_recv) { + // EXPECT_CALL(dispatcher_, + // createFileEvent_(_, _, Event::PlatformDefaultTriggerType, + // Event::FileReadyType::Read | Event::FileReadyType::Closed)) + // .WillOnce(DoAll(SaveArg<1>(&file_event_callback_), + // ReturnNew>())); + + return filter_->onAccept(cb_); + } + return std::nullopt; + } + + Stats::IsolatedStoreImpl store_; + ConfigSharedPtr cfg_; + std::unique_ptr filter_; + Network::MockListenerFilterCallbacks cb_; + Network::MockConnectionSocket socket_; + NiceMock dispatcher_; + Event::FileReadyCb file_event_callback_; + std::unique_ptr io_handle_; + std::unique_ptr io_handle_peer_; +}; + +// Verify peek behavior of BufferedIoSocketHandleImpl. +TEST_F(HttpInspectorTestWithBufferIoSocket, InlineRecv) { + const absl::string_view header = + "GET /anything HTTP/1.0\r\nhost: google.com\r\nuser-agent: curl/7.64.0\r\naccept: " + "*/*\r\nx-forwarded-proto: http\r\nx-request-id: " + "a52df4a0-ed00-4a19-86a7-80e5049c6c84\r\nx-envoy-expected-rq-timeout-ms: " + "15000\r\ncontent-length: 0\r\n\r\n"; + + io_handle_->getBufferForTest().add(header); + const std::vector alpn_protos{Http::Utility::AlpnNames::get().Http10}; + EXPECT_CALL(socket_, setRequestedApplicationProtocols(alpn_protos)); + + EXPECT_EQ(Network::FilterStatus::Continue, init(true)); + EXPECT_EQ(1, cfg_->stats().http10_found_.value()); +} + +// Verify createFileEvent of BufferedIoSocketHandleImpl. +TEST_F(HttpInspectorTestWithBufferIoSocket, InlineRecvAndScheduleEvent) {} + +// Verify sending data from peer socket triggers read event. +TEST_F(HttpInspectorTestWithBufferIoSocket, InlineRecvAndSendByPeer) {} + } // namespace } // namespace HttpInspector } // namespace ListenerFilters 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 e51ebd62fbde4..d6123bf3f66ef 100644 --- a/test/extensions/filters/listener/proxy_protocol/proxy_protocol_test.cc +++ b/test/extensions/filters/listener/proxy_protocol/proxy_protocol_test.cc @@ -88,6 +88,9 @@ class ProxyProtocolTest : public testing::TestWithParam, Network::UdpPacketWriterFactoryOptRef udpPacketWriterFactory() override { return Network::UdpPacketWriterFactoryOptRef(std::ref(*udp_writer_factory_)); } + bool isInternalListener() override { + return false; + } Network::UdpListenerWorkerRouterOptRef udpListenerWorkerRouter() override { return udp_listener_worker_router_; } diff --git a/test/integration/internal_listener_integration_test.cc b/test/integration/internal_listener_integration_test.cc new file mode 100644 index 0000000000000..37c0bebd41ffc --- /dev/null +++ b/test/integration/internal_listener_integration_test.cc @@ -0,0 +1,401 @@ +#include +#include + +#include "envoy/config/bootstrap/v3/bootstrap.pb.h" +#include "envoy/config/cluster/v3/cluster.pb.h" +#include "envoy/config/core/v3/base.pb.h" +#include "envoy/config/filter/network/tcp_proxy/v2/tcp_proxy.pb.h" +#include "envoy/extensions/access_loggers/file/v3/file.pb.h" +#include "envoy/extensions/filters/network/tcp_proxy/v3/tcp_proxy.pb.h" + +#include "test/integration/integration.h" +#include "test/mocks/secret/mocks.h" +#include "test/test_common/environment.h" + +#include "gtest/gtest.h" + +namespace Envoy { + +std::string tcpInternalConfig() { + return fmt::format(R"EOF( +admin: + access_log_path: {} + address: + socket_address: + address: 127.0.0.1 + port_value: 0 +dynamic_resources: + lds_config: + path: {} +static_resources: + secrets: + - name: "secret_static_0" + tls_certificate: + certificate_chain: + inline_string: "DUMMY_INLINE_BYTES" + private_key: + inline_string: "DUMMY_INLINE_BYTES" + password: + inline_string: "DUMMY_INLINE_BYTES" + clusters: + - name: cluster_internal + load_assignment: + cluster_name: cluster_internal + endpoints: + - lb_endpoints: + - endpoint: + address: + envoy_internal_address: + server_listener_name: listener_internal + - name: cluster_0 + load_assignment: + cluster_name: cluster_0 + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: + address: 127.0.0.1 + port_value: 0 + listeners: + - name: tcp_proxy + address: + socket_address: + address: 127.0.0.1 + port_value: 0 + filter_chains: + filters: + name: tcp + typed_config: + "@type": type.googleapis.com/envoy.config.filter.network.tcp_proxy.v2.TcpProxy + stat_prefix: tcp_stats + cluster: cluster_internal + - name: listener_internal + address: + envoy_internal_address: + server_listener_name: listener_internal + internal_listener: + filter_chains: + filters: + name: tcp + typed_config: + "@type": type.googleapis.com/envoy.config.filter.network.tcp_proxy.v2.TcpProxy + stat_prefix: tcp_stats + cluster: cluster_0 +)EOF", + Platform::null_device_path, Platform::null_device_path); +} + +class InternalListenerIntegrationTest : public testing::TestWithParam, + public BaseIntegrationTest { +public: + InternalListenerIntegrationTest() : BaseIntegrationTest(GetParam(), tcpInternalConfig()) { + config_helper_.addConfigModifier( + [&](envoy::config::bootstrap::v3::Bootstrap& bootstrap) -> void { + auto* listener = bootstrap.mutable_static_resources()->mutable_listeners(1); + listener->mutable_internal_listener(); + }); + enable_half_close_ = true; + } + +public: + void initialize() override; +}; + +void InternalListenerIntegrationTest::initialize() { BaseIntegrationTest::initialize(); } + +TEST_P(InternalListenerIntegrationTest, TcpProxyUpstreamWritesFirst) { + initialize(); + IntegrationTcpClientPtr tcp_client = makeTcpConnection(lookupPort("tcp_proxy")); + FakeRawConnectionPtr fake_upstream_connection; + ASSERT_TRUE(fake_upstreams_[0]->waitForRawConnection(fake_upstream_connection)); + + ASSERT_TRUE(fake_upstream_connection->write("hell")); + tcp_client->waitForData("hell"); + // Make sure inexact matches work also on data already received. + tcp_client->waitForData("ell", false); + + // Make sure length based wait works for the data already received + ASSERT_TRUE(tcp_client->waitForData(4)); + ASSERT_TRUE(tcp_client->waitForData(3)); + + // Drain part of the received message + tcp_client->clearData(1); + tcp_client->waitForData("ell"); + ASSERT_TRUE(tcp_client->waitForData(3)); + + ASSERT_TRUE(tcp_client->write("world")); + ASSERT_TRUE(fake_upstream_connection->waitForData(5)); + + ASSERT_TRUE(fake_upstream_connection->write("", true)); + tcp_client->waitForHalfClose(); + ASSERT_TRUE(tcp_client->write("", true)); + ASSERT_TRUE(fake_upstream_connection->waitForHalfClose()); + ASSERT_TRUE(fake_upstream_connection->waitForDisconnect()); +} + +TEST_P(InternalListenerIntegrationTest, TcpProxyDownstreamWritesFirst) { + initialize(); + IntegrationTcpClientPtr tcp_client = makeTcpConnection(lookupPort("tcp_proxy")); + ASSERT_TRUE(tcp_client->write("hell")); + + FakeRawConnectionPtr fake_upstream_connection; + ASSERT_TRUE(fake_upstreams_[0]->waitForRawConnection(fake_upstream_connection)); + + ASSERT_TRUE(fake_upstream_connection->waitForData(4)); + ASSERT_TRUE(fake_upstream_connection->write("world")); + ASSERT_TRUE(fake_upstream_connection->close()); + + ASSERT_TRUE(fake_upstream_connection->waitForDisconnect()); + tcp_client->waitForHalfClose(); + tcp_client->close(); + EXPECT_EQ("world", tcp_client->data()); +} + +TEST_P(InternalListenerIntegrationTest, TcpProxyUpstreamDisconnect) { + initialize(); + IntegrationTcpClientPtr tcp_client = makeTcpConnection(lookupPort("tcp_proxy")); + ASSERT_TRUE(tcp_client->write("hello")); + FakeRawConnectionPtr fake_upstream_connection; + ASSERT_TRUE(fake_upstreams_[0]->waitForRawConnection(fake_upstream_connection)); + ASSERT_TRUE(fake_upstream_connection->waitForData(5)); + ASSERT_TRUE(fake_upstream_connection->write("world")); + ASSERT_TRUE(fake_upstream_connection->close()); + ASSERT_TRUE(fake_upstream_connection->waitForDisconnect()); + tcp_client->waitForHalfClose(); + tcp_client->close(); + + EXPECT_EQ("world", tcp_client->data()); +} + +TEST_P(InternalListenerIntegrationTest, TcpProxyDownstreamDisconnect) { + initialize(); + IntegrationTcpClientPtr tcp_client = makeTcpConnection(lookupPort("tcp_proxy")); + ASSERT_TRUE(tcp_client->write("hell")); + FakeRawConnectionPtr fake_upstream_connection; + ASSERT_TRUE(fake_upstreams_[0]->waitForRawConnection(fake_upstream_connection)); + ASSERT_TRUE(fake_upstream_connection->waitForData(4)); + ASSERT_TRUE(fake_upstream_connection->write("foobar")); + tcp_client->waitForData("foobar"); + ASSERT_TRUE(tcp_client->write("world", true)); + ASSERT_TRUE(fake_upstream_connection->waitForData(9)); + ASSERT_TRUE(fake_upstream_connection->waitForHalfClose()); + ASSERT_TRUE(fake_upstream_connection->write("", true)); + ASSERT_TRUE(fake_upstream_connection->waitForDisconnect()); + tcp_client->waitForDisconnect(); +} + +TEST_P(InternalListenerIntegrationTest, NoUpstream) { + // Set the first upstream to have an invalid port, so connection will fail, + // but it won't fail synchronously (as it would if there were simply no + // upstreams) + fake_upstreams_count_ = 0; + config_helper_.addConfigModifier([&](envoy::config::bootstrap::v3::Bootstrap& bootstrap) -> void { + auto* cluster = bootstrap.mutable_static_resources()->mutable_clusters(1); + auto* lb_endpoint = + cluster->mutable_load_assignment()->mutable_endpoints(0)->mutable_lb_endpoints(0); + lb_endpoint->mutable_endpoint()->mutable_address()->mutable_socket_address()->set_port_value(1); + }); + config_helper_.skipPortUsageValidation(); + enable_half_close_ = false; + initialize(); + + IntegrationTcpClientPtr tcp_client = makeTcpConnection(lookupPort("tcp_proxy")); + tcp_client->waitForDisconnect(); +} + +TEST_P(InternalListenerIntegrationTest, ShutdownWithOpenConnections) { + config_helper_.addConfigModifier([&](envoy::config::bootstrap::v3::Bootstrap& bootstrap) -> void { + auto* static_resources = bootstrap.mutable_static_resources(); + for (int i = 0; i < static_resources->clusters_size(); ++i) { + auto* cluster = static_resources->mutable_clusters(i); + cluster->set_close_connections_on_host_health_failure(true); + } + }); + initialize(); + IntegrationTcpClientPtr tcp_client = makeTcpConnection(lookupPort("tcp_proxy")); + ASSERT_TRUE(tcp_client->write("hello")); + FakeRawConnectionPtr fake_upstream_connection; + ASSERT_TRUE(fake_upstreams_[0]->waitForRawConnection(fake_upstream_connection)); + ASSERT_TRUE(fake_upstream_connection->waitForData(5)); + ASSERT_TRUE(fake_upstream_connection->write("world")); + tcp_client->waitForData("world"); + ASSERT_TRUE(tcp_client->write("hello", false)); + ASSERT_TRUE(fake_upstream_connection->waitForData(10)); + test_server_.reset(); + ASSERT_TRUE(fake_upstream_connection->waitForHalfClose()); + ASSERT_TRUE(fake_upstream_connection->close()); + ASSERT_TRUE(fake_upstream_connection->waitForDisconnect()); + tcp_client->waitForHalfClose(); + tcp_client->close(); + + // Success criteria is that no ASSERTs fire and there are no leaks. +} + +TEST_P(InternalListenerIntegrationTest, TestIdletimeoutWithNoData) { + autonomous_upstream_ = true; + + enable_half_close_ = false; + config_helper_.addConfigModifier([&](envoy::config::bootstrap::v3::Bootstrap& bootstrap) -> void { + // The first listener is connecting to internal cluster. Mutate the 2nd listener which connect + // to normal cluster. + auto* listener = bootstrap.mutable_static_resources()->mutable_listeners(1); + auto* filter_chain = listener->mutable_filter_chains(0); + auto* config_blob = filter_chain->mutable_filters(0)->mutable_typed_config(); + + ASSERT_TRUE( + config_blob->Is()); + auto tcp_proxy_config = MessageUtil::anyConvert(*config_blob); + tcp_proxy_config.mutable_idle_timeout()->set_nanos( + std::chrono::duration_cast(std::chrono::milliseconds(100)) + .count()); + config_blob->PackFrom(tcp_proxy_config); + }); + + initialize(); + IntegrationTcpClientPtr tcp_client = makeTcpConnection(lookupPort("tcp_proxy")); + tcp_client->waitForDisconnect(true); +} + +TEST_P(InternalListenerIntegrationTest, TcpProxyLargeWrite) { + config_helper_.setBufferLimits(1024, 1024); + initialize(); + + std::string data(1024 * 16, 'a'); + IntegrationTcpClientPtr tcp_client = makeTcpConnection(lookupPort("tcp_proxy")); + ASSERT_TRUE(tcp_client->write(data)); + FakeRawConnectionPtr fake_upstream_connection; + ASSERT_TRUE(fake_upstreams_[0]->waitForRawConnection(fake_upstream_connection)); + ASSERT_TRUE(fake_upstream_connection->waitForData(data.size())); + ASSERT_TRUE(fake_upstream_connection->write(data)); + tcp_client->waitForData(data); + tcp_client->close(); + ASSERT_TRUE(fake_upstream_connection->waitForHalfClose()); + ASSERT_TRUE(fake_upstream_connection->close()); + ASSERT_TRUE(fake_upstream_connection->waitForDisconnect()); + + uint32_t upstream_pauses = + test_server_->counter("cluster.cluster_0.upstream_flow_control_paused_reading_total") + ->value(); + uint32_t upstream_resumes = + test_server_->counter("cluster.cluster_0.upstream_flow_control_resumed_reading_total") + ->value(); + ENVOY_LOG_MISC(debug, "lambdai: cluster_0 pause reading = {} resume reading = {}", + upstream_pauses, upstream_resumes); + EXPECT_EQ(upstream_pauses, upstream_resumes); + + uint32_t downstream_pauses = + test_server_->counter("tcp.tcp_stats.downstream_flow_control_paused_reading_total")->value(); + uint32_t downstream_resumes = + test_server_->counter("tcp.tcp_stats.downstream_flow_control_resumed_reading_total")->value(); + ENVOY_LOG_MISC(debug, "lambdai: listener pause reading = {} resume reading = {}", + downstream_pauses, downstream_resumes); + + EXPECT_EQ(downstream_pauses, downstream_resumes); +} + +// Test that a downstream flush works correctly (all data is flushed) +TEST_P(InternalListenerIntegrationTest, TcpProxyDownstreamFlush) { + // Use a very large size to make sure it is larger than the kernel socket read buffer. + const uint32_t size = 50 * 1024 * 1024; + config_helper_.setBufferLimits(size / 4, size / 4); + initialize(); + + std::string data(size, 'a'); + IntegrationTcpClientPtr tcp_client = makeTcpConnection(lookupPort("tcp_proxy")); + FakeRawConnectionPtr fake_upstream_connection; + ASSERT_TRUE(fake_upstreams_[0]->waitForRawConnection(fake_upstream_connection)); + tcp_client->readDisable(true); + ASSERT_TRUE(tcp_client->write("", true)); + + // This ensures that readDisable(true) has been run on it's thread + // before tcp_client starts writing. + ASSERT_TRUE(fake_upstream_connection->waitForHalfClose()); + + ASSERT_TRUE(fake_upstream_connection->write(data, true)); + + test_server_->waitForCounterGe("cluster.cluster_0.upstream_flow_control_paused_reading_total", 1); + + tcp_client->readDisable(false); + tcp_client->waitForData(data); + tcp_client->waitForHalfClose(); + ASSERT_TRUE(fake_upstream_connection->waitForHalfClose()); + + uint32_t upstream_pauses = + test_server_->counter("cluster.cluster_0.upstream_flow_control_paused_reading_total") + ->value(); + uint32_t upstream_resumes = + test_server_->counter("cluster.cluster_0.upstream_flow_control_resumed_reading_total") + ->value(); + ENVOY_LOG_MISC(debug, "lambdai: cluster_0 pause reading = {} resume reading = {}", + upstream_pauses, upstream_resumes); + + EXPECT_GE(upstream_pauses, upstream_resumes); + + EXPECT_GT(upstream_resumes, 0); +} + +// Test that an upstream flush works correctly (all data is flushed) +TEST_P(InternalListenerIntegrationTest, TcpProxyUpstreamFlush) { + // Use a very large size to make sure it is larger than the kernel socket read buffer. + const uint32_t size = 50 * 1024 * 1024; + config_helper_.setBufferLimits(size, size); + initialize(); + + std::string data(size, 'a'); + IntegrationTcpClientPtr tcp_client = makeTcpConnection(lookupPort("tcp_proxy")); + FakeRawConnectionPtr fake_upstream_connection; + ASSERT_TRUE(fake_upstreams_[0]->waitForRawConnection(fake_upstream_connection)); + ASSERT_TRUE(fake_upstream_connection->readDisable(true)); + ASSERT_TRUE(fake_upstream_connection->write("", true)); + + // This ensures that fake_upstream_connection->readDisable has been run on it's thread + // before tcp_client starts writing. + tcp_client->waitForHalfClose(); + + ASSERT_TRUE(tcp_client->write(data, true)); + + test_server_->waitForGaugeEq("tcp.tcp_stats.upstream_flush_active", 1); + ASSERT_TRUE(fake_upstream_connection->readDisable(false)); + ASSERT_TRUE(fake_upstream_connection->waitForData(data.size())); + ASSERT_TRUE(fake_upstream_connection->waitForHalfClose()); + ASSERT_TRUE(fake_upstream_connection->waitForDisconnect()); + tcp_client->waitForHalfClose(); + // We have 2 tcp proxy. Each contribute 1. + EXPECT_EQ(test_server_->counter("tcp.tcp_stats.upstream_flush_total")->value(), 2); + test_server_->waitForGaugeEq("tcp.tcp_stats.upstream_flush_active", 0); +} + +// Test that Envoy doesn't crash or assert when shutting down with an upstream flush active +TEST_P(InternalListenerIntegrationTest, TcpProxyUpstreamFlushEnvoyExit) { + // Use a very large size to make sure it is larger than the kernel socket read buffer. + const uint32_t size = 50 * 1024 * 1024; + config_helper_.setBufferLimits(size, size); + initialize(); + + std::string data(size, 'a'); + IntegrationTcpClientPtr tcp_client = makeTcpConnection(lookupPort("tcp_proxy")); + FakeRawConnectionPtr fake_upstream_connection; + ASSERT_TRUE(fake_upstreams_[0]->waitForRawConnection(fake_upstream_connection)); + ASSERT_TRUE(fake_upstream_connection->readDisable(true)); + ASSERT_TRUE(fake_upstream_connection->write("", true)); + + // This ensures that fake_upstream_connection->readDisable has been run on it's thread + // before tcp_client starts writing. + tcp_client->waitForHalfClose(); + + ASSERT_TRUE(tcp_client->write(data, true)); + + test_server_->waitForGaugeEq("tcp.tcp_stats.upstream_flush_active", 1); + test_server_.reset(); + ASSERT_TRUE(fake_upstream_connection->close()); + ASSERT_TRUE(fake_upstream_connection->waitForDisconnect()); + + // Success criteria is that no ASSERTs fire and there are no leaks. +} + +INSTANTIATE_TEST_SUITE_P(IpVersions, InternalListenerIntegrationTest, + testing::ValuesIn(TestEnvironment::getIpVersionsForTest()), + TestUtility::ipTestParamsToString); +} // namespace Envoy \ No newline at end of file diff --git a/test/integration/socket_interface_integration_test.cc b/test/integration/socket_interface_integration_test.cc index 93a5db5aff842..bec4b10ca3fe4 100644 --- a/test/integration/socket_interface_integration_test.cc +++ b/test/integration/socket_interface_integration_test.cc @@ -89,8 +89,7 @@ TEST_P(SocketInterfaceIntegrationTest, AddressWithSocketInterface) { client_->close(Network::ConnectionCloseType::FlushWrite); } -// Test that connecting to internal address will crash. -// TODO(lambdai): Add internal connection implementation to enable the connection creation. +// Test that connection to internal address which is not bind by listener will be closed. TEST_P(SocketInterfaceIntegrationTest, InternalAddressWithSocketInterface) { BaseIntegrationTest::initialize(); @@ -101,10 +100,17 @@ TEST_P(SocketInterfaceIntegrationTest, InternalAddressWithSocketInterface) { Network::Address::InstanceConstSharedPtr address = std::make_shared("listener_0", sock_interface); - ASSERT_DEATH(client_ = dispatcher_->createClientConnection( - address, Network::Address::InstanceConstSharedPtr(), - Network::Test::createRawBufferSocket(), nullptr), - "panic: not implemented"); + client_ = + dispatcher_->createInternalConnection(address, Network::Address::InstanceConstSharedPtr()); + + client_->addConnectionCallbacks(connect_callbacks_); + client_->connect(); + + while (!connect_callbacks_.closed()) { + dispatcher_->run(Event::Dispatcher::RunType::NonBlock); + } + + client_->close(Network::ConnectionCloseType::FlushWrite); } // Test that recv from internal address will crash. diff --git a/test/mocks/event/mocks.h b/test/mocks/event/mocks.h index 9f85e09ea2098..ea722c3e759a1 100644 --- a/test/mocks/event/mocks.h +++ b/test/mocks/event/mocks.h @@ -17,6 +17,7 @@ #include "envoy/network/transport_socket.h" #include "envoy/ssl/context.h" +#include "common/common/assert.h" #include "common/common/scope_tracker.h" #include "test/mocks/buffer/mocks.h" @@ -54,6 +55,12 @@ class MockDispatcher : public Dispatcher { createClientConnection_(address, source_address, transport_socket, options)}; } + Network::ClientConnectionPtr + createInternalConnection(Network::Address::InstanceConstSharedPtr, + Network::Address::InstanceConstSharedPtr) override { + NOT_IMPLEMENTED_GCOVR_EXCL_LINE; + } + 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/network/mocks.h b/test/mocks/network/mocks.h index 88e580effa692..3483d7ba8b66e 100644 --- a/test/mocks/network/mocks.h +++ b/test/mocks/network/mocks.h @@ -359,6 +359,7 @@ class MockListenerConfig : public ListenerConfig { MOCK_METHOD(const std::string&, name, (), (const)); MOCK_METHOD(Network::ActiveUdpListenerFactory*, udpListenerFactory, ()); MOCK_METHOD(Network::UdpPacketWriterFactoryOptRef, udpPacketWriterFactory, ()); + MOCK_METHOD(bool, isInternalListener, ()); MOCK_METHOD(Network::UdpListenerWorkerRouterOptRef, udpListenerWorkerRouter, ()); MOCK_METHOD(ConnectionBalancer&, connectionBalancer, ()); MOCK_METHOD(ResourceLimit&, openConnections, ()); diff --git a/test/server/connection_handler_test.cc b/test/server/connection_handler_test.cc index 47d58f94e256f..9b7254a42bd98 100644 --- a/test/server/connection_handler_test.cc +++ b/test/server/connection_handler_test.cc @@ -111,6 +111,9 @@ class ConnectionHandlerTest : public testing::Test, protected Logger::LoggableaddListener(absl::nullopt, *test_listener); } +TEST_F(ConnectionHandlerTest, AddInternalListener) {} + +TEST_F(ConnectionHandlerTest, StopInternalListener) {} + +TEST_F(ConnectionHandlerTest, InternalListenerAddConnection) {} + +TEST_F(ConnectionHandlerTest, RemoveInternalListener) {} + } // namespace } // namespace Server } // namespace Envoy diff --git a/test/server/listener_manager_impl_test.cc b/test/server/listener_manager_impl_test.cc index b5f6cef747fa2..8390d54299561 100644 --- a/test/server/listener_manager_impl_test.cc +++ b/test/server/listener_manager_impl_test.cc @@ -4823,6 +4823,72 @@ TEST_F(ListenerManagerImplTest, TcpBacklogCustomConfig) { EXPECT_EQ(100U, manager_->listeners().back().get().tcpBacklogSize()); } +// Test that internal listener config can be added. +TEST_F(ListenerManagerImplTest, InternalListenerBasic) { + const envoy::config::listener::v3::Listener listener = parseListenerFromV3Yaml(R"EOF( +internal_listener: {} +reuse_port: false +address: + envoy_internal_address: + server_listener_name: listener_foo +filter_chains: + filters: [] + )EOF"); + + manager_->addOrUpdateListener(listener, "", true); + EXPECT_EQ(1U, manager_->listeners().size()); +} + +// Test internal listener can be updated and drained. +TEST_F(ListenerManagerImplForInPlaceFilterChainUpdateTest, InternalListenerUpdate) { + const envoy::config::listener::v3::Listener listener_proto = parseListenerFromV3Yaml(R"EOF( +name: listener_name_foo +internal_listener: {} +reuse_port: false +address: + envoy_internal_address: + server_listener_name: listener_foo +filter_chains: + filters: [] + )EOF"); + + EXPECT_CALL(*worker_, start(_)); + manager_->startWorkers(guard_dog_); + + ListenerHandle* listener_foo = expectListenerCreate(false, true); + + // expectAddListener(listener_proto, listener_foo) except no listen socket + { + // EXPECT_CALL(listener_factory_, createListenSocket(_, _, _, {true})); + EXPECT_CALL(*worker_, addListener(_, _, _)); + manager_->addOrUpdateListener(listener_proto, "", true); + worker_->callAddCompletion(true); + EXPECT_EQ(1UL, manager_->listeners().size()); + checkStats(__LINE__, 1, 0, 0, 0, 1, 0, 0); + } + ListenerHandle* listener_foo_update1 = expectListenerCreate(false, true); + + auto new_listener_proto = listener_proto; + new_listener_proto.set_traffic_direction(::envoy::config::core::v3::TrafficDirection::INBOUND); + expectUpdateToThenDrain(new_listener_proto, listener_foo); + + // expectRemove(new_listener_proto, listener_foo_update1) except no socket close_ + { + EXPECT_CALL(*worker_, stopListener(_, _)); + // EXPECT_CALL(*listener_factory_.socket_, close()); + EXPECT_CALL(*listener_foo_update1->drain_manager_, startDrainSequence(_)); + EXPECT_TRUE(manager_->removeListener(new_listener_proto.name())); + + EXPECT_CALL(*worker_, removeListener(_, _)); + listener_foo_update1->drain_manager_->drain_sequence_completion_(); + + EXPECT_CALL(*listener_foo_update1, onDestroy()); + worker_->callRemovalCompletion(); + } + EXPECT_EQ(0UL, manager_->listeners().size()); + EXPECT_EQ(0, server_.stats_store_.counter("listener_manager.listener_in_place_updated").value()); +} + } // namespace } // namespace Server } // namespace Envoy