Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
0d71615
Add SO_KEEPALIVE option for upstream connections.
JonathanO Apr 9, 2018
7017ffc
Remove incorrect import
JonathanO Apr 10, 2018
bd79a31
Hack data-plane-api version.
JonathanO Apr 10, 2018
c5ddd1b
Correct commit hash for data-plane-api
JonathanO Apr 10, 2018
61537b7
Fix MacOS constant naming conflict
JonathanO Apr 10, 2018
fad89fd
Updated for data-plane-api changes.
JonathanO Apr 11, 2018
cb216a1
Parse tcp keepalive settings into a struct at construct time.
JonathanO Apr 11, 2018
77aee92
Use new keepalive config struct in the option.
JonathanO Apr 11, 2018
23acd5c
Fix format build failure.
JonathanO Apr 11, 2018
894a6d4
Code review fixes.
JonathanO Apr 13, 2018
c813195
Refactoring to avoid use of Features.
JonathanO Apr 13, 2018
8a88054
Updated to new proposed data-plane-api version
JonathanO Apr 13, 2018
313743c
Merge branch 'master' into add-tcp-keepalive-option
JonathanO Apr 16, 2018
937a793
Moved API changes from data-plane-api repo.
JonathanO Apr 16, 2018
15d52ce
Code review changes.
JonathanO Apr 18, 2018
43d117d
Merge remote-tracking branch 'upstream/master' into add-tcp-keepalive…
JonathanO Apr 19, 2018
238894e
Include "level" in SocketOptionName
JonathanO Apr 18, 2018
d85f3dd
Generic socket options holder and factories.
JonathanO Apr 20, 2018
0c81bbb
Code review update and style fixes.
JonathanO Apr 24, 2018
3d15a3d
Merge remote-tracking branch 'upstream/master' into add-tcp-keepalive…
JonathanO Apr 24, 2018
e275246
Removed SocketOptionsImpl
JonathanO Apr 25, 2018
c0f4cdc
Renamed misnamed class, added test coverage.
JonathanO Apr 26, 2018
c1246bf
Merge remote-tracking branch 'upstream/master' into add-tcp-keepalive…
JonathanO Apr 26, 2018
99ac729
Code style fixes.
JonathanO Apr 26, 2018
588d234
Fix BUILD file format.
JonathanO Apr 26, 2018
7a37ab7
Merge remote-tracking branch 'upstream/master' into add-tcp-keepalive…
JonathanO Apr 28, 2018
4c40a6b
Added test case for all Keepalive options
JonathanO Apr 28, 2018
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion api/envoy/api/v2/cds.proto
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ service ClusterDiscoveryService {
// [#protodoc-title: Clusters]

// Configuration for a single upstream cluster.
// [#comment:next free field: 30]
// [#comment:next free field: 31]
message Cluster {
// Supplies the name of the cluster which must be unique across all clusters.
// The cluster name is used when emitting
Expand Down Expand Up @@ -428,6 +428,9 @@ message Cluster {

// Determines how Envoy selects the protocol used to speak to upstream hosts.
ClusterProtocolSelection protocol_selection = 26;

// Optional options for upstream connections.
envoy.api.v2.UpstreamConnectionOptions upstream_connection_options = 30;
}

// An extensible structure containing the address Envoy should bind to when
Expand All @@ -436,3 +439,8 @@ message UpstreamBindConfig {
// The address Envoy should bind to when establishing upstream connections.
core.Address source_address = 1;
}

message UpstreamConnectionOptions {
// If set then set SO_KEEPALIVE on the socket to enable TCP Keepalives.
core.TcpKeepalive tcp_keepalive = 1;
}
14 changes: 14 additions & 0 deletions api/envoy/api/v2/core/address.proto
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,20 @@ message SocketAddress {
bool ipv4_compat = 6;
}

message TcpKeepalive {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please document the macos behavior. It looks like you added support for the right socket option, but please document that the other two must be unset in the config on macos.

Copy link
Copy Markdown
Contributor Author

@JonathanO JonathanO Apr 18, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I swear I replied to this, but the comment seems to be missing now.
The reason for the single special case is that MacOS uses a different name for that one constant (TCP_KEEPIDLE vs TCP_KEEPALIVE.) The other constants all have the same name as for Linux. All of these settings are valid for both Linux and MacOS. I've added a comment to that effect in the header where they're defined.

// Maximum number of keepalive probes to send without response before deciding
// the connection is dead. Default is to use the OS level configuration (unless
// overridden, Linux defaults to 9.)
google.protobuf.UInt32Value keepalive_probes = 1;
// The number of seconds a connection needs to be idle before keep-alive probes
// start being sent. Default is to use the OS level configuration (unless
// overridden, Linux defaults to 7200s (ie 2 hours.)
google.protobuf.UInt32Value keepalive_time = 2;
// The number of seconds between keep-alive probes. Default is to use the OS
// level configuration (unless overridden, Linux defaults to 75s.)
google.protobuf.UInt32Value keepalive_interval = 3;
}

message BindConfig {
// The address to bind to when creating a socket.
SocketAddress source_address = 1
Expand Down
2 changes: 2 additions & 0 deletions docs/root/intro/version_history.rst
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ Version history
:ref:`cluster specific <envoy_api_field_Cluster.upstream_bind_config>` options.
* sockets: added `IP_TRANSPARENT` socket option support for :ref:`listeners
<envoy_api_field_Listener.transparent>`.
* sockets: added `SO_KEEPALIVE` socket option for upstream connections
:ref:`per cluster <envoy_api_field_Cluster.upstream_connection_options>`.
* tracing: the sampling decision is now delegated to the tracers, allowing the tracer to decide when and if
to use it. For example, if the :ref:`x-b3-sampled <config_http_conn_man_headers_x-b3-sampled>` header
is supplied with the client request, its value will override any sampling decision made by the Envoy proxy.
Expand Down
24 changes: 23 additions & 1 deletion include/envoy/network/listen_socket.h
Original file line number Diff line number Diff line change
Expand Up @@ -68,13 +68,35 @@ class Socket {
typedef std::vector<OptionConstSharedPtr> Options;
typedef std::shared_ptr<Options> OptionsSharedPtr;

static OptionsSharedPtr& appendOptions(OptionsSharedPtr& to, const OptionsSharedPtr& from) {
to->insert(to->end(), from->begin(), from->end());
return to;
}

static bool applyOptions(const OptionsSharedPtr& options, Socket& socket, SocketState state) {
if (options == nullptr) {
return true;
}
for (const auto& option : *options) {
if (!option->setOption(socket, state)) {
return false;
}
}
return true;
}

/**
* Add a socket option visitor for later retrieval with options().
*/
virtual void addOption(const OptionConstSharedPtr&) PURE;

/**
* @return the socket options stored earlier with addOption() calls, if any.
* Add socket option visitors for later retrieval with options().
*/
virtual void addOptions(const OptionsSharedPtr&) PURE;

/**
* @return the socket options stored earlier with addOption() and addOptions() calls, if any.
*/
virtual const OptionsSharedPtr& options() const PURE;
};
Expand Down
2 changes: 2 additions & 0 deletions include/envoy/server/filter_config.h
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,8 @@ class ListenerFactoryContext : public FactoryContext {
* Store socket options to be set on the listen socket before listening.
*/
virtual void addListenSocketOption(const Network::Socket::OptionConstSharedPtr& option) PURE;

virtual void addListenSocketOptions(const Network::Socket::OptionsSharedPtr& options) PURE;
};

/**
Expand Down
9 changes: 7 additions & 2 deletions include/envoy/upstream/upstream.h
Original file line number Diff line number Diff line change
Expand Up @@ -397,8 +397,6 @@ class ClusterInfo {
// Use the downstream protocol (HTTP1.1, HTTP2) for upstream connections as well, if available.
// This is used when creating connection pools.
static const uint64_t USE_DOWNSTREAM_PROTOCOL = 0x2;
// Use IP_FREEBIND socket option when binding.
static const uint64_t FREEBIND = 0x4;
};

virtual ~ClusterInfo() {}
Expand Down Expand Up @@ -521,6 +519,13 @@ class ClusterInfo {
* @return const envoy::api::v2::core::Metadata& the configuration metadata for this cluster.
*/
virtual const envoy::api::v2::core::Metadata& metadata() const PURE;

/**
*
* @return const Network::ConnectionSocket::OptionsSharedPtr& socket options for all
* connections for this cluster.
*/
virtual const Network::ConnectionSocket::OptionsSharedPtr& clusterSocketOptions() const PURE;
};

typedef std::shared_ptr<const ClusterInfo> ClusterInfoConstSharedPtr;
Expand Down
29 changes: 29 additions & 0 deletions source/common/network/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,35 @@ envoy_cc_library(
],
)

envoy_cc_library(
name = "addr_family_aware_socket_option_lib",
srcs = ["addr_family_aware_socket_option_impl.cc"],
hdrs = ["addr_family_aware_socket_option_impl.h"],
external_deps = ["abseil_optional"],
deps = [
":address_lib",
":socket_option_lib",
"//include/envoy/network:listen_socket_interface",
"//source/common/api:os_sys_calls_lib",
"//source/common/common:assert_lib",
"//source/common/common:logger_lib",
],
)

envoy_cc_library(
name = "socket_option_factory_lib",
srcs = ["socket_option_factory.cc"],
hdrs = ["socket_option_factory.h"],
external_deps = ["abseil_optional"],
deps = [
":addr_family_aware_socket_option_lib",
":address_lib",
":socket_option_lib",
"//include/envoy/network:listen_socket_interface",
"//source/common/common:logger_lib",
],
)

envoy_cc_library(
name = "utility_lib",
srcs = ["utility.cc"],
Expand Down
58 changes: 58 additions & 0 deletions source/common/network/addr_family_aware_socket_option_impl.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
#include "common/network/addr_family_aware_socket_option_impl.h"

#include "envoy/common/exception.h"

#include "common/api/os_sys_calls_impl.h"
#include "common/common/assert.h"
#include "common/network/address_impl.h"
#include "common/network/socket_option_impl.h"

namespace Envoy {
namespace Network {

bool AddrFailyAwareSocketOptionImpl::setOption(Socket& socket, Socket::SocketState state) const {
return setIpSocketOption(socket, state, ipv4_option_, ipv6_option_);
}

bool AddrFailyAwareSocketOptionImpl::setIpSocketOption(
Socket& socket, Socket::SocketState state, const std::unique_ptr<SocketOptionImpl>& ipv4_option,
const std::unique_ptr<SocketOptionImpl>& ipv6_option) {
// If this isn't IP, we're out of luck.
Address::InstanceConstSharedPtr address;
const Address::Ip* ip = nullptr;
try {
// We have local address when the socket is used in a listener but have to
// infer the IP from the socket FD when initiating connections.
// TODO(htuch): Figure out a way to obtain a consistent interface for IP
// version from socket.
if (socket.localAddress()) {
ip = socket.localAddress()->ip();
} else {
address = Address::addressFromFd(socket.fd());
ip = address->ip();
}
} catch (const EnvoyException&) {
// Ignore, we get here because we failed in getsockname().
// TODO(htuch): We should probably clean up this logic to avoid relying on exceptions.
}
if (ip == nullptr) {
ENVOY_LOG(warn, "Failed to set IP socket option on non-IP socket");
return false;
}

// If the FD is v4, we can only try the IPv4 variant.
if (ip->version() == Network::Address::IpVersion::v4) {
return ipv4_option->setOption(socket, state);
}

// If the FD is v6, we first try the IPv6 variant if the platfrom supports it and fallback to the
// IPv4 variant otherwise.
ASSERT(ip->version() == Network::Address::IpVersion::v6);
if (ipv6_option->isSupported()) {
return ipv6_option->setOption(socket, state);
}
return ipv4_option->setOption(socket, state);
}

} // namespace Network
} // namespace Envoy
55 changes: 55 additions & 0 deletions source/common/network/addr_family_aware_socket_option_impl.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
#pragma once

#include <netinet/in.h>
#include <netinet/ip.h>
#include <sys/socket.h>

#include "envoy/network/listen_socket.h"

#include "common/common/logger.h"
#include "common/network/socket_option_impl.h"

#include "absl/types/optional.h"

namespace Envoy {
namespace Network {

class AddrFailyAwareSocketOptionImpl : public Socket::Option,
Logger::Loggable<Logger::Id::connection> {
public:
AddrFailyAwareSocketOptionImpl(Socket::SocketState in_state, SocketOptionName ipv4_optname,
SocketOptionName ipv6_optname, int value)
: ipv4_option_(absl::make_unique<SocketOptionImpl>(in_state, ipv4_optname, value)),
ipv6_option_(absl::make_unique<SocketOptionImpl>(in_state, ipv6_optname, value)) {}

// Socket::Option
bool setOption(Socket& socket, Socket::SocketState state) const override;
// The common socket options don't require a hash key.
void hashKey(std::vector<uint8_t>&) const override {}

/**
* Set a socket option that applies at both IPv4 and IPv6 socket levels. When the underlying FD
* is IPv6, this function will attempt to set at IPv6 unless the platform only supports the
* option at the IPv4 level.
* @param socket.
* @param ipv4_optname SocketOptionName for IPv4 level. Set to empty if not supported on
* platform.
* @param ipv6_optname SocketOptionName for IPv6 level. Set to empty if not supported on
* platform.
* @param optval as per setsockopt(2).
* @param optlen as per setsockopt(2).
* @return int as per setsockopt(2). ENOTSUP is returned if the option is not supported on the
* platform for fd after the above option level fallback semantics are taken into account or the
* socket is non-IP.
*/
static bool setIpSocketOption(Socket& socket, Socket::SocketState state,
const std::unique_ptr<SocketOptionImpl>& ipv4_option,
const std::unique_ptr<SocketOptionImpl>& ipv6_option);

private:
const std::unique_ptr<SocketOptionImpl> ipv4_option_;
const std::unique_ptr<SocketOptionImpl> ipv6_option_;
};

} // namespace Network
} // namespace Envoy
19 changes: 8 additions & 11 deletions source/common/network/connection_impl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -536,18 +536,15 @@ ClientConnectionImpl::ClientConnectionImpl(
const Network::ConnectionSocket::OptionsSharedPtr& options)
: ConnectionImpl(dispatcher, std::make_unique<ClientSocketImpl>(remote_address),
std::move(transport_socket), false) {
if (options) {
for (const auto& option : *options) {
if (!option->setOption(*socket_, Socket::SocketState::PreBind)) {
// Set a special error state to ensure asynchronous close to give the owner of the
// ConnectionImpl a chance to add callbacks and detect the "disconnect".
immediate_error_event_ = ConnectionEvent::LocalClose;
// Trigger a write event to close this connection out-of-band.
file_event_->activate(Event::FileReadyType::Write);
return;
}
}
if (!Network::Socket::applyOptions(options, *socket_, Socket::SocketState::PreBind)) {
// Set a special error state to ensure asynchronous close to give the owner of the
// ConnectionImpl a chance to add callbacks and detect the "disconnect".
immediate_error_event_ = ConnectionEvent::LocalClose;
// Trigger a write event to close this connection out-of-band.
file_event_->activate(Event::FileReadyType::Write);
return;
}

if (source_address != nullptr) {
const int rc = source_address->bind(fd());
if (rc < 0) {
Expand Down
8 changes: 2 additions & 6 deletions source/common/network/listen_socket_impl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,8 @@ void ListenSocketImpl::doBind() {
}

void ListenSocketImpl::setListenSocketOptions(const Network::Socket::OptionsSharedPtr& options) {
if (options) {
for (const auto& option : *options) {
if (!option->setOption(*this, SocketState::PreBind)) {
throw EnvoyException("ListenSocket: Setting socket options failed");
}
}
if (!Network::Socket::applyOptions(options, *this, Socket::SocketState::PreBind)) {
throw EnvoyException("ListenSocket: Setting socket options failed");
}
}

Expand Down
9 changes: 8 additions & 1 deletion source/common/network/listen_socket_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,19 @@ class SocketImpl : public virtual Socket {
fd_ = -1;
}
}
void addOption(const OptionConstSharedPtr& option) override {
void ensureOptions() {
if (!options_) {
options_ = std::make_shared<std::vector<OptionConstSharedPtr>>();
}
}
void addOption(const OptionConstSharedPtr& option) override {
ensureOptions();
options_->emplace_back(std::move(option));
}
void addOptions(const OptionsSharedPtr& options) override {
ensureOptions();
Network::Socket::appendOptions(options_, options);
}
const OptionsSharedPtr& options() const override { return options_; }

protected:
Expand Down
12 changes: 3 additions & 9 deletions source/common/network/listener_impl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -66,15 +66,9 @@ ListenerImpl::ListenerImpl(Event::DispatcherImpl& dispatcher, Socket& socket, Li
fmt::format("cannot listen on socket: {}", socket.localAddress()->asString()));
}

Socket::OptionsSharedPtr options = socket.options();
if (options != nullptr) {
for (auto& option : *options) {
if (!option->setOption(socket, Socket::SocketState::Listening)) {
throw CreateListenerException(
fmt::format("cannot set post-listen socket option on socket: {}",
socket.localAddress()->asString()));
}
}
if (!Network::Socket::applyOptions(socket.options(), socket, Socket::SocketState::Listening)) {
throw CreateListenerException(fmt::format(
"cannot set post-listen socket option on socket: {}", socket.localAddress()->asString()));
}

evconnlistener_set_error_cb(listener_.get(), errorCallback);
Expand Down
Loading