Skip to content
Merged
Show file tree
Hide file tree
Changes from 15 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 @@ -427,6 +427,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 @@ -435,3 +438,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.

@JonathanO JonathanO Apr 18, 2018

Copy link
Copy Markdown
Contributor Author

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 @@ -40,6 +40,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
8 changes: 8 additions & 0 deletions include/envoy/network/listen_socket.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
#include "envoy/common/pure.h"
#include "envoy/network/address.h"

#include "absl/types/optional.h"

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Unneeded include


namespace Envoy {
namespace Network {

Expand Down Expand Up @@ -118,5 +120,11 @@ class ConnectionSocket : public virtual Socket {

typedef std::unique_ptr<ConnectionSocket> ConnectionSocketPtr;

struct TcpKeepaliveConfig {

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.

I think this can be moved closer to the _impl.cc, since it's an implementation detail essentially and not needed by any interfaces in include/envoy.

absl::optional<uint32_t> keepalive_probes_;
absl::optional<uint32_t> keepalive_time_;

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.

Comments would be good, in particular providing the units of time being used.

absl::optional<uint32_t> keepalive_interval_;
};

} // namespace Network
} // namespace Envoy
9 changes: 7 additions & 2 deletions include/envoy/upstream/upstream.h
Original file line number Diff line number Diff line change
Expand Up @@ -396,8 +396,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 @@ -520,6 +518,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
13 changes: 13 additions & 0 deletions source/common/network/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,19 @@ envoy_cc_library(
],
)

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

envoy_cc_library(
name = "utility_lib",
srcs = ["utility.cc"],
Expand Down
3 changes: 2 additions & 1 deletion source/common/network/socket_option_impl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,8 @@ int SocketOptionImpl::setIpSocketOption(Socket& socket, SocketOptionName ipv4_op
}
if (ip == nullptr) {
ENVOY_LOG(warn, "Failed to set IP socket option on non-IP socket");
return ENOTSUP;
errno = ENOTSUP;
return -1;
}

// If the FD is v4, we can only try the IPv4 variant.
Expand Down
66 changes: 66 additions & 0 deletions source/common/network/tcp_keepalive_option_impl.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
#include "common/network/tcp_keepalive_option_impl.h"

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

namespace Envoy {
namespace Network {
bool TcpKeepaliveOptionImpl::setOption(Network::Socket& socket,
Network::Socket::SocketState state) const {
if (state == Socket::SocketState::PreBind) {
return setTcpKeepalive(socket, keepalive_config_.keepalive_probes_,
keepalive_config_.keepalive_time_,
keepalive_config_.keepalive_interval_);
}
return true;
}

bool TcpKeepaliveOptionImpl::setTcpKeepalive(Socket& socket, absl::optional<int> keepalive_probes,
absl::optional<int> keepalive_time,
absl::optional<int> keepalive_interval) {
int error;
error = setSocketOption(socket, SOL_SOCKET, ENVOY_SOCKET_SO_KEEPALIVE, 1);

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.

Q: does there need to be a way to configure it to explicitly disable the socket option, ie set it to a value of zero? I think it would only be needed if there's a way to make the default (at the OS level) enabled. Is that a thing we need to do?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Under Linux there's no way to turn it on at the OS level, only tweak the parameters. It appears that under FreeBSD and OSX it's possible to turn it on for everyone with "net.inet.tcp.always_keepalive", but I don't know if it's possible to explicitly disable it using SO_KEEPALIVE=0 in that case.

if (error != 0) {
ENVOY_LOG(warn, "Setting SO_KEEPALIVE on socket failed: {}", strerror(errno));
return false;
}
error = setSocketOption(socket, IPPROTO_TCP, ENVOY_SOCKET_TCP_KEEPCNT, keepalive_probes);
if (error != 0) {
ENVOY_LOG(warn, "Setting keepalive_probes failed: {}", strerror(errno));
return false;
}
error = setSocketOption(socket, IPPROTO_TCP, ENVOY_SOCKET_TCP_KEEPIDLE, keepalive_time);
if (error != 0) {
ENVOY_LOG(warn, "Setting keepalive_time failed: {}", strerror(errno));
return false;
}
error = setSocketOption(socket, IPPROTO_TCP, ENVOY_SOCKET_TCP_KEEPINTVL, keepalive_interval);
if (error != 0) {
ENVOY_LOG(warn, "Setting keepalive_interval failed: {}", strerror(errno));
return false;
}
return true;
}

int TcpKeepaliveOptionImpl::setSocketOption(Socket& socket, int level,
Network::SocketOptionName optname,
absl::optional<int> optional_value) {
if (optional_value.has_value()) {
return setSocketOption(socket, level, optname, optional_value.value());
} else {
return 0;
}
}

int TcpKeepaliveOptionImpl::setSocketOption(Socket& socket, int level,
Network::SocketOptionName optname, int value) {
if (!optname.has_value()) {
errno = ENOTSUP;
return -1;
}
auto& os_syscalls = Api::OsSysCallsSingleton::get();
return os_syscalls.setsockopt(socket.fd(), level, optname.value(), &value, sizeof(value));

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.

These options should all be of type/size int, not uint32_t. See http://man7.org/linux/man-pages/man7/tcp.7.html

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.

I feel like we're redoing https://github.com/envoyproxy/envoy/blob/master/source/common/network/socket_option_impl.h here. I realize these are TCP options, there is also #2793 where I've asked to add in some equivalent support for TCP options. Do you think it's reasonable to come up with some refactoring to avoid redoing how socket options are set, platform features are handled and the various branches tested?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I'm going to change SocketOptionName to be absl::optional<std::pair<int, int>>, so it can contain the "level" as well as "optname." That should make it possible to clean up the socket option code a bit and maybe make a common Socket::Option implementation, which takes a SocketOptionName and value as constructor args, possible. That might then allow TCP Keepalive, and other simple options, to be implemented with as a factory that produces a vector of options.

SocketOptionImpl has special handling for checking if the socket it's being applied to is IP, and setting different options for IPV6 vs 4. I think that's going to have to remain slightly special (although it might be able to use the generic implementation under the hood.)

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.

Awesome. @bmetzdorf for visibility, so that you folks don't collide on this. I could merge #2793 as is if that works for you both.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yeah, that works for me.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

SGTM, I can address the open aesthetic items from #2793 in another PR.

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.

OK, will merge.

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.

#2793 merged.

}

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

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

#include "envoy/network/listen_socket.h"

#include "common/common/logger.h"

#include "absl/types/optional.h"

namespace Envoy {
namespace Network {

// Optional variant of setsockopt(2) optname. The idea here is that if the option is not supported
// on a platform, we can make this the empty value. This allows us to avoid proliferation of #ifdef.
typedef absl::optional<int> SocketOptionName;

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.

This should go in a common header somewhere, instead of copy/pasting it.

I think it might be nice to put all of the socket option preprocessor checking into a single header file. Or better yet, put the declarations in a header, and the definitions in a .cc so all the system headers for the constants don't need to be included in large parts of the codebase. Thoughts on that idea @htuch ?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I was considering making this an absl::optional<pair<int, int>> so that it can contain the "level" as well as "optname" value. I think I'd definitely want to do that if I make these a single set of definitions, which I agree might be cleaner.


#ifdef SO_KEEPALIVE
#define ENVOY_SOCKET_SO_KEEPALIVE Network::SocketOptionName(SO_KEEPALIVE)
#else
#define ENVOY_SOCKET_SO_KEEPALIVE Network::SocketOptionName()
#endif

#ifdef TCP_KEEPCNT
#define ENVOY_SOCKET_TCP_KEEPCNT Network::SocketOptionName(TCP_KEEPCNT)
#else
#define ENVOY_SOCKET_TCP_KEEPCNT Network::SocketOptionName()
#endif

#ifdef TCP_KEEPIDLE
#define ENVOY_SOCKET_TCP_KEEPIDLE Network::SocketOptionName(TCP_KEEPIDLE)
#elif TCP_KEEPALIVE // MacOS uses a different name from Linux for just this option.
#define ENVOY_SOCKET_TCP_KEEPIDLE Network::SocketOptionName(TCP_KEEPALIVE)
#else
#define ENVOY_SOCKET_TCP_KEEPIDLE Network::SocketOptionName()
#endif

#ifdef TCP_KEEPINTVL
#define ENVOY_SOCKET_TCP_KEEPINTVL Network::SocketOptionName(TCP_KEEPINTVL)
#else
#define ENVOY_SOCKET_TCP_KEEPINTVL Network::SocketOptionName()
#endif

class TcpKeepaliveOptionImpl : public Socket::Option, Logger::Loggable<Logger::Id::connection> {
public:
TcpKeepaliveOptionImpl(Network::TcpKeepaliveConfig keepalive_config)
: keepalive_config_(keepalive_config) {}

// Socket::Option
bool setOption(Socket& socket, Socket::SocketState state) const override;

// The tcp keepalive options don't require a hash key.
void hashKey(std::vector<uint8_t>&) const override {}

static bool setTcpKeepalive(Socket& socket, absl::optional<int> keepalive_probes,
absl::optional<int> keepalive_time,
absl::optional<int> keepalive_interval);

private:
Network::TcpKeepaliveConfig keepalive_config_;
static int setSocketOption(Socket& socket, int level, Network::SocketOptionName optname,
absl::optional<int> value);
static int setSocketOption(Socket& socket, int level, Network::SocketOptionName optname,
int value);
};
} // namespace Network
} // namespace Envoy
2 changes: 2 additions & 0 deletions source/common/upstream/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -338,6 +338,7 @@ envoy_cc_library(
"//source/common/network:address_lib",
"//source/common/network:resolver_lib",
"//source/common/network:socket_option_lib",
"//source/common/network:tcp_keepalive_option_lib",
"//source/common/network:utility_lib",
"//source/common/protobuf",
"//source/common/protobuf:utility_lib",
Expand Down Expand Up @@ -368,6 +369,7 @@ envoy_cc_library(
"//source/common/common:enum_to_int",
"//source/common/common:logger_lib",
"//source/common/config:metadata_lib",
"//source/common/network:tcp_keepalive_option_lib",
"//source/common/stats:stats_lib",
"//source/common/upstream:locality_lib",
"@envoy_api//envoy/api/v2/core:base_cc",
Expand Down
Loading