diff --git a/source/common/network/io_socket_error_impl.cc b/source/common/network/io_socket_error_impl.cc index a3e955f4d68a2..b2a4e93f9e726 100644 --- a/source/common/network/io_socket_error_impl.cc +++ b/source/common/network/io_socket_error_impl.cc @@ -18,6 +18,12 @@ IoSocketError* IoSocketError::getIoSocketInvalidAddressInstance() { return instance; } +IoSocketError* IoSocketError::getIoSocketEbadfInstance() { + static auto* instance = + new IoSocketError(SOCKET_ERROR_BADF, Api::IoError::IoErrorCode::NoSupport); + return instance; +} + IoSocketError* IoSocketError::getIoSocketEagainInstance() { static auto* instance = new IoSocketError(SOCKET_ERROR_AGAIN, Api::IoError::IoErrorCode::Again); return instance; diff --git a/source/common/network/io_socket_error_impl.h b/source/common/network/io_socket_error_impl.h index d17ce2a2f31cf..392dd6129ebba 100644 --- a/source/common/network/io_socket_error_impl.h +++ b/source/common/network/io_socket_error_impl.h @@ -25,6 +25,8 @@ class IoSocketError : public Api::IoError { // deleter deleteIoError() below to avoid deallocating memory for this error. static IoSocketError* getIoSocketEagainInstance(); + static IoSocketError* getIoSocketEbadfInstance(); + // This error is introduced when Envoy create socket for unsupported address. It is either a bug, // or this Envoy instance received config which is not yet supported. This should not be fatal // error. diff --git a/test/integration/BUILD b/test/integration/BUILD index 0ac91631679ab..89af4478f0f07 100644 --- a/test/integration/BUILD +++ b/test/integration/BUILD @@ -501,6 +501,7 @@ envoy_cc_test_library( ], deps = [ ":http_protocol_integration_lib", + ":socket_interface_swap_lib", "//source/common/http:header_map_lib", "//source/extensions/filters/http/buffer:config", "//test/common/http/http2:http2_frame", diff --git a/test/integration/protocol_integration_test.cc b/test/integration/protocol_integration_test.cc index 72c79fea27df5..9bb4776079539 100644 --- a/test/integration/protocol_integration_test.cc +++ b/test/integration/protocol_integration_test.cc @@ -30,6 +30,7 @@ #include "test/common/upstream/utility.h" #include "test/integration/autonomous_upstream.h" #include "test/integration/http_integration.h" +#include "test/integration/socket_interface_swap.h" #include "test/integration/test_host_predicate_config.h" #include "test/integration/utility.h" #include "test/mocks/upstream/retry_priority.h" @@ -3525,4 +3526,25 @@ TEST_P(DownstreamProtocolIntegrationTest, ContentLengthLargerThanPayload) { EXPECT_EQ(Http::StreamResetReason::RemoteReset, response->resetReason()); } +TEST_P(DownstreamProtocolIntegrationTest, HandleSocketFail) { + SocketInterfaceSwap socket_swap; + if (downstreamProtocol() == Http::CodecType::HTTP3) { + return; + } + + initialize(); + codec_client_ = makeHttpConnection(lookupPort("http")); + auto response = codec_client_->makeHeaderOnlyRequest(default_request_headers_); + waitForNextUpstreamRequest(); + + // Makes us have Envoy's writes to downstream return EBADF + Network::IoSocketError* ebadf = Network::IoSocketError::getIoSocketEbadfInstance(); + socket_swap.writev_matcher_->setSourcePort(lookupPort("http")); + socket_swap.writev_matcher_->setWritevOverride(ebadf); + upstream_request_->encodeHeaders(Http::TestResponseHeaderMapImpl{{":status", "200"}}, false); + + ASSERT_TRUE(codec_client_->waitForDisconnect()); + socket_swap.writev_matcher_->setWritevOverride(nullptr); +} + } // namespace Envoy diff --git a/test/integration/socket_interface_swap.cc b/test/integration/socket_interface_swap.cc index 521153817a256..94d8abba9fbd1 100644 --- a/test/integration/socket_interface_swap.cc +++ b/test/integration/socket_interface_swap.cc @@ -9,10 +9,10 @@ SocketInterfaceSwap::SocketInterfaceSwap() { [writev_matcher = writev_matcher_](Envoy::Network::TestIoSocketHandle* io_handle, const Buffer::RawSlice*, uint64_t) -> absl::optional { - if (writev_matcher->shouldReturnEgain(io_handle)) { + Network::IoSocketError* error_override = writev_matcher->returnOverride(io_handle); + if (error_override) { return Api::IoCallUint64Result( - 0, Api::IoErrorPtr(Network::IoSocketError::getIoSocketEagainInstance(), - Network::IoSocketError::deleteIoError)); + 0, Api::IoErrorPtr(error_override, Network::IoSocketError::deleteIoError)); } return absl::nullopt; })); @@ -23,7 +23,7 @@ void SocketInterfaceSwap::IoHandleMatcher::setResumeWrites() { mutex_.Await(absl::Condition( +[](Network::TestIoSocketHandle** matched_iohandle) { return *matched_iohandle != nullptr; }, &matched_iohandle_)); - writev_returns_egain_ = false; + error_ = nullptr; matched_iohandle_->activateInDispatcherThread(Event::FileReadyType::Write); } diff --git a/test/integration/socket_interface_swap.h b/test/integration/socket_interface_swap.h index ee4f84db47659..820041a5d4eb0 100644 --- a/test/integration/socket_interface_swap.h +++ b/test/integration/socket_interface_swap.h @@ -14,16 +14,16 @@ class SocketInterfaceSwap { // Object of this class hold the state determining the IoHandle which // should return EAGAIN from the `writev` call. struct IoHandleMatcher { - bool shouldReturnEgain(Envoy::Network::TestIoSocketHandle* io_handle) { + Network::IoSocketError* returnOverride(Envoy::Network::TestIoSocketHandle* io_handle) { absl::MutexLock lock(&mutex_); - if (writev_returns_egain_ && (io_handle->localAddress()->ip()->port() == src_port_ || - io_handle->peerAddress()->ip()->port() == dst_port_)) { + if (error_ && (io_handle->localAddress()->ip()->port() == src_port_ || + io_handle->peerAddress()->ip()->port() == dst_port_)) { ASSERT(matched_iohandle_ == nullptr || matched_iohandle_ == io_handle, "Matched multiple io_handles, expected at most one to match."); matched_iohandle_ = io_handle; - return true; + return error_; } - return false; + return nullptr; } // Source port to match. The port specified should be associated with a listener. @@ -41,9 +41,14 @@ class SocketInterfaceSwap { } void setWritevReturnsEgain() { + setWritevOverride(Network::IoSocketError::getIoSocketEagainInstance()); + } + + // The caller is responsible for memory management. + void setWritevOverride(Network::IoSocketError* error) { absl::WriterMutexLock lock(&mutex_); ASSERT(src_port_ != 0 || dst_port_ != 0); - writev_returns_egain_ = true; + error_ = error; } void setResumeWrites(); @@ -52,7 +57,7 @@ class SocketInterfaceSwap { mutable absl::Mutex mutex_; uint32_t src_port_ ABSL_GUARDED_BY(mutex_) = 0; uint32_t dst_port_ ABSL_GUARDED_BY(mutex_) = 0; - bool writev_returns_egain_ ABSL_GUARDED_BY(mutex_) = false; + Network::IoSocketError* error_ ABSL_GUARDED_BY(mutex_) = nullptr; Network::TestIoSocketHandle* matched_iohandle_{}; }; @@ -63,7 +68,6 @@ class SocketInterfaceSwap { Envoy::Network::SocketInterfaceSingleton::initialize(previous_socket_interface_); } -protected: Envoy::Network::SocketInterface* const previous_socket_interface_{ Envoy::Network::SocketInterfaceSingleton::getExisting()}; std::shared_ptr writev_matcher_{std::make_shared()};