Skip to content

Commit

Permalink
network: add timeout for transport connect (#13610)
Browse files Browse the repository at this point in the history
Adds a configurable timeout for the amount of time a downstream client is allowed to finish the transport-level connect before the connection is forcefully terminated. This can be used to require that a client finishes the TLS handshake in a bounded amount of time.

Signed-off-by: Alex Konradi <akonradi@google.com>
  • Loading branch information
akonradi authored Oct 27, 2020
1 parent 38cf888 commit 2d0a2f6
Show file tree
Hide file tree
Showing 26 changed files with 354 additions and 115 deletions.
7 changes: 6 additions & 1 deletion api/envoy/config/listener/v3/listener_components.proto
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ message FilterChainMatch {

// A filter chain wraps a set of match criteria, an option TLS context, a set of filters, and
// various other parameters.
// [#next-free-field: 9]
// [#next-free-field: 10]
message FilterChain {
option (udpa.annotations.versioning).previous_message_type = "envoy.api.v2.listener.FilterChain";

Expand Down Expand Up @@ -230,6 +230,11 @@ message FilterChain {
// will be set up with plaintext.
core.v3.TransportSocket transport_socket = 6;

// If present and nonzero, the amount of time to allow incoming connections to complete any
// transport socket negotiations. If this expires before the transport reports connection
// establishment, the connection is summarily closed.
google.protobuf.Duration transport_socket_connect_timeout = 9;

// [#not-implemented-hide:] The unique name (or empty) by which this filter chain is known. If no
// name is provided, Envoy will allocate an internal UUID for the filter chain. If the filter
// chain is to be dynamically updated or removed via FCDS a unique name must be provided.
Expand Down
7 changes: 6 additions & 1 deletion api/envoy/config/listener/v4alpha/listener_components.proto

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

16 changes: 15 additions & 1 deletion docs/root/faq/configuration/timeouts.rst
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ Connection timeouts apply to the entire HTTP connection and all streams the conn
:ref:`common_http_protocol_options <envoy_v3_api_field_config.cluster.v3.Cluster.common_http_protocol_options>` field
in the cluster configuration.

See :ref:`below <faq_configuration_timeouts_transport_socket>` for other connection timeouts.

Stream timeouts
^^^^^^^^^^^^^^^

Expand Down Expand Up @@ -102,8 +104,20 @@ TCP

.. attention::

For TLS connections, the connect timeout includes the TLS handshake.
For upstream TLS connections, the connect timeout includes the TLS handshake. For downstream
connections, see :ref:`below <faq_configuration_timeouts_transport_socket>` for configuration options.

* The TCP proxy :ref:`idle_timeout
<envoy_v3_api_field_extensions.filters.network.tcp_proxy.v3.TcpProxy.idle_timeout>`
is the amount of time that the TCP proxy will allow a connection to exist with no upstream
or downstream activity. The default idle timeout if not otherwise specified is *1 hour*.

.. _faq_configuration_timeouts_transport_socket:

Transport Socket
----------------

* The :ref:`transport_socket_connect_timeout <envoy_v3_api_field_config.listener.v3.FilterChain.transport_socket_connect_timeout>`
specifies the amount of time Envoy will wait for a downstream client to complete transport-level
negotiations. When configured on a filter chain with a TLS or ALTS transport socket, this limits
the amount of time allowed to finish the encrypted handshake after establishing a TCP connection.
1 change: 1 addition & 0 deletions docs/root/version_history/current.rst
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ New Features
* listener: added an optional :ref:`default filter chain <envoy_v3_api_field_config.listener.v3.Listener.default_filter_chain>`. If this field is supplied, and none of the :ref:`filter_chains <envoy_v3_api_field_config.listener.v3.Listener.filter_chains>` matches, this default filter chain is used to serve the connection.
* lua: added `downstreamDirectRemoteAddress()` and `downstreamLocalAddress()` APIs to :ref:`streamInfo() <config_http_filters_lua_stream_info_wrapper>`.
* mongo_proxy: the list of commands to produce metrics for is now :ref:`configurable <envoy_v3_api_field_extensions.filters.network.mongo_proxy.v3.MongoProxy.commands>`.
* network: added a :ref:`timeout <envoy_v3_api_field_config.listener.v3.FilterChain.transport_socket_connect_timeout>` for incoming connections completing transport-level negotiation, including TLS and ALTS hanshakes.
* ratelimit: added support for use of various :ref:`metadata <envoy_v3_api_field_config.route.v3.RateLimit.Action.metadata>` as a ratelimit action.
* ratelimit: added :ref:`disable_x_envoy_ratelimited_header <envoy_v3_api_msg_extensions.filters.http.ratelimit.v3.RateLimit>` option to disable `X-Envoy-RateLimited` header.
* tcp: added a new :ref:`envoy.overload_actions.reject_incoming_connections <config_overload_manager_overload_actions>` action to reject incoming TCP connections.
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion include/envoy/event/dispatcher.h
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ class Dispatcher {
* @param stream_info info object for the server connection
* @return Network::ConnectionPtr a server connection that is owned by the caller.
*/
virtual Network::ConnectionPtr
virtual Network::ServerConnectionPtr
createServerConnection(Network::ConnectionSocketPtr&& socket,
Network::TransportSocketPtr&& transport_socket,
StreamInfo::StreamInfo& stream_info) PURE;
Expand Down
15 changes: 15 additions & 0 deletions include/envoy/network/connection.h
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,21 @@ class Connection : public Event::DeferredDeletable, public FilterManager {

using ConnectionPtr = std::unique_ptr<Connection>;

/**
* Connections servicing inbound connects.
*/
class ServerConnection : public virtual Connection {
public:
/**
* Set the amount of time allowed for the transport socket to report that a connection is
* established. The provided timeout is relative to the current time. If this method is called
* after a connection has already been established, it is a no-op.
*/
virtual void setTransportSocketConnectTimeout(std::chrono::milliseconds timeout) PURE;
};

using ServerConnectionPtr = std::unique_ptr<ServerConnection>;

/**
* Connections capable of outbound connects.
*/
Expand Down
7 changes: 7 additions & 0 deletions include/envoy/network/filter.h
Original file line number Diff line number Diff line change
Expand Up @@ -357,6 +357,13 @@ class FilterChain {
*/
virtual const TransportSocketFactory& transportSocketFactory() const PURE;

/**
* @return std::chrono::milliseconds the amount of time to wait for the transport socket to report
* that a connection has been established. If the timeout is reached, the connection is closed. 0
* specifies a disabled timeout.
*/
virtual std::chrono::milliseconds transportSocketConnectTimeout() const PURE;

/**
* const std::vector<FilterFactoryCb>& a list of filters to be used by the new connection.
*/
Expand Down
6 changes: 3 additions & 3 deletions source/common/event/dispatcher_impl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -103,13 +103,13 @@ void DispatcherImpl::clearDeferredDeleteList() {
deferred_deleting_ = false;
}

Network::ConnectionPtr
Network::ServerConnectionPtr
DispatcherImpl::createServerConnection(Network::ConnectionSocketPtr&& socket,
Network::TransportSocketPtr&& transport_socket,
StreamInfo::StreamInfo& stream_info) {
ASSERT(isThreadSafe());
return std::make_unique<Network::ConnectionImpl>(*this, std::move(socket),
std::move(transport_socket), stream_info, true);
return std::make_unique<Network::ServerConnectionImpl>(
*this, std::move(socket), std::move(transport_socket), stream_info, true);
}

Network::ClientConnectionPtr
Expand Down
7 changes: 4 additions & 3 deletions source/common/event/dispatcher_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,10 @@ class DispatcherImpl : Logger::Loggable<Logger::Id::main>,
TimeSource& timeSource() override { return api_.timeSource(); }
void initializeStats(Stats::Scope& scope, const absl::optional<std::string>& prefix) override;
void clearDeferredDeleteList() override;
Network::ConnectionPtr createServerConnection(Network::ConnectionSocketPtr&& socket,
Network::TransportSocketPtr&& transport_socket,
StreamInfo::StreamInfo& stream_info) override;
Network::ServerConnectionPtr
createServerConnection(Network::ConnectionSocketPtr&& socket,
Network::TransportSocketPtr&& transport_socket,
StreamInfo::StreamInfo& stream_info) override;
Network::ClientConnectionPtr
createClientConnection(Network::Address::InstanceConstSharedPtr address,
Network::Address::InstanceConstSharedPtr source_address,
Expand Down
40 changes: 40 additions & 0 deletions source/common/network/connection_impl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,12 @@

namespace Envoy {
namespace Network {
namespace {

constexpr absl::string_view kTransportSocketConnectTimeoutTerminationDetails =
"transport socket timeout was reached";

}

void ConnectionImplUtility::updateBufferStats(uint64_t delta, uint64_t new_total,
uint64_t& previous_total, Stats::Counter& stat_total,
Expand Down Expand Up @@ -701,6 +707,40 @@ void ConnectionImpl::flushWriteBuffer() {
}
}

ServerConnectionImpl::ServerConnectionImpl(Event::Dispatcher& dispatcher,
ConnectionSocketPtr&& socket,
TransportSocketPtr&& transport_socket,
StreamInfo::StreamInfo& stream_info, bool connected)
: ConnectionImpl(dispatcher, std::move(socket), std::move(transport_socket), stream_info,
connected) {}

void ServerConnectionImpl::setTransportSocketConnectTimeout(std::chrono::milliseconds timeout) {
if (!transport_connect_pending_) {
return;
}
if (transport_socket_connect_timer_ == nullptr) {
transport_socket_connect_timer_ =
dispatcher_.createTimer([this] { onTransportSocketConnectTimeout(); });
}
transport_socket_connect_timer_->enableTimer(timeout);
}

void ServerConnectionImpl::raiseEvent(ConnectionEvent event) {
switch (event) {
case ConnectionEvent::Connected:
case ConnectionEvent::RemoteClose:
case ConnectionEvent::LocalClose:
transport_connect_pending_ = false;
transport_socket_connect_timer_.reset();
}
ConnectionImpl::raiseEvent(event);
}

void ServerConnectionImpl::onTransportSocketConnectTimeout() {
stream_info_.setConnectionTerminationDetails(kTransportSocketConnectTimeoutTerminationDetails);
closeConnectionImmediately();
}

ClientConnectionImpl::ClientConnectionImpl(
Event::Dispatcher& dispatcher, const Address::InstanceConstSharedPtr& remote_address,
const Network::Address::InstanceConstSharedPtr& source_address,
Expand Down
21 changes: 20 additions & 1 deletion source/common/network/connection_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ class ConnectionImpl : public ConnectionImplBase, public TransportSocketCallback
IoHandle& ioHandle() final { return socket_->ioHandle(); }
const IoHandle& ioHandle() const override { return socket_->ioHandle(); }
Connection& connection() override { return *this; }
void raiseEvent(ConnectionEvent event) final;
void raiseEvent(ConnectionEvent event) override;
// Should the read buffer be drained?
bool shouldDrainReadBuffer() override {
return read_buffer_limit_ > 0 && read_buffer_.length() >= read_buffer_limit_;
Expand Down Expand Up @@ -197,6 +197,25 @@ class ConnectionImpl : public ConnectionImplBase, public TransportSocketCallback
bool dispatch_buffered_data_ : 1;
};

class ServerConnectionImpl : public ConnectionImpl, virtual public ServerConnection {
public:
ServerConnectionImpl(Event::Dispatcher& dispatcher, ConnectionSocketPtr&& socket,
TransportSocketPtr&& transport_socket, StreamInfo::StreamInfo& stream_info,
bool connected);

// ServerConnection impl
void setTransportSocketConnectTimeout(std::chrono::milliseconds timeout) override;
void raiseEvent(ConnectionEvent event) override;

private:
void onTransportSocketConnectTimeout();

bool transport_connect_pending_{true};
// Implements a timeout for the transport socket signaling connection. The timer is enabled by a
// call to setTransportSocketConnectTimeout and is reset when the connection is established.
Event::TimerPtr transport_socket_connect_timer_;
};

/**
* libevent implementation of Network::ClientConnection.
*/
Expand Down
4 changes: 4 additions & 0 deletions source/server/admin/admin.h
Original file line number Diff line number Diff line change
Expand Up @@ -383,6 +383,10 @@ class AdminImpl : public Admin,
return transport_socket_factory_;
}

std::chrono::milliseconds transportSocketConnectTimeout() const override {
return std::chrono::milliseconds::zero();
}

const std::vector<Network::FilterFactoryCb>& networkFilterFactories() const override {
return empty_network_filter_factory_;
}
Expand Down
6 changes: 6 additions & 0 deletions source/server/connection_handler_impl.cc
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
#include "server/connection_handler_impl.h"

#include <chrono>

#include "envoy/event/dispatcher.h"
#include "envoy/event/timer.h"
#include "envoy/network/exception.h"
Expand Down Expand Up @@ -479,6 +481,10 @@ void ConnectionHandlerImpl::ActiveTcpListener::newConnection(
auto& active_connections = getOrCreateActiveConnections(*filter_chain);
auto server_conn_ptr = parent_.dispatcher_.createServerConnection(
std::move(socket), std::move(transport_socket), *stream_info);
if (const auto timeout = filter_chain->transportSocketConnectTimeout();
timeout != std::chrono::milliseconds::zero()) {
server_conn_ptr->setTransportSocketConnectTimeout(timeout);
}
ActiveTcpConnectionPtr active_connection(
new ActiveTcpConnection(active_connections, std::move(server_conn_ptr),
parent_.dispatcher_.timeSource(), std::move(stream_info)));
Expand Down
10 changes: 8 additions & 2 deletions source/server/filter_chain_manager_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -87,14 +87,19 @@ class PerFilterChainFactoryContextImpl : public Configuration::FilterChainFactor
class FilterChainImpl : public Network::DrainableFilterChain {
public:
FilterChainImpl(Network::TransportSocketFactoryPtr&& transport_socket_factory,
std::vector<Network::FilterFactoryCb>&& filters_factory)
std::vector<Network::FilterFactoryCb>&& filters_factory,
std::chrono::milliseconds transport_socket_connect_timeout)
: transport_socket_factory_(std::move(transport_socket_factory)),
filters_factory_(std::move(filters_factory)) {}
filters_factory_(std::move(filters_factory)),
transport_socket_connect_timeout_(transport_socket_connect_timeout) {}

// Network::FilterChain
const Network::TransportSocketFactory& transportSocketFactory() const override {
return *transport_socket_factory_;
}
std::chrono::milliseconds transportSocketConnectTimeout() const override {
return transport_socket_connect_timeout_;
}
const std::vector<Network::FilterFactoryCb>& networkFilterFactories() const override {
return filters_factory_;
}
Expand All @@ -110,6 +115,7 @@ class FilterChainImpl : public Network::DrainableFilterChain {
Configuration::FilterChainFactoryContextPtr factory_context_;
const Network::TransportSocketFactoryPtr transport_socket_factory_;
const std::vector<Network::FilterFactoryCb> filters_factory_;
const std::chrono::milliseconds transport_socket_connect_timeout_;
};

/**
Expand Down
13 changes: 8 additions & 5 deletions source/server/listener_manager_impl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1021,11 +1021,14 @@ Network::DrainableFilterChainSharedPtr ListenerFilterChainFactoryBuilder::buildF
std::vector<std::string> server_names(filter_chain.filter_chain_match().server_names().begin(),
filter_chain.filter_chain_match().server_names().end());

auto filter_chain_res =
std::make_unique<FilterChainImpl>(config_factory.createTransportSocketFactory(
*message, factory_context_, std::move(server_names)),
listener_component_factory_.createNetworkFilterFactoryList(
filter_chain.filters(), *filter_chain_factory_context));
auto filter_chain_res = std::make_unique<FilterChainImpl>(
config_factory.createTransportSocketFactory(*message, factory_context_,
std::move(server_names)),
listener_component_factory_.createNetworkFilterFactoryList(filter_chain.filters(),
*filter_chain_factory_context),
std::chrono::milliseconds(
PROTOBUF_GET_MS_OR_DEFAULT(filter_chain, transport_socket_connect_timeout, 0)));

filter_chain_res->setFilterChainFactoryContext(std::move(filter_chain_factory_context));
return filter_chain_res;
}
Expand Down
Loading

0 comments on commit 2d0a2f6

Please sign in to comment.