Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
9 changes: 9 additions & 0 deletions api/envoy/api/v2/cds.proto
Original file line number Diff line number Diff line change
Expand Up @@ -558,4 +558,13 @@ message UpstreamBindConfig {
message UpstreamConnectionOptions {
// If set then set SO_KEEPALIVE on the socket to enable TCP Keepalives.
core.TcpKeepalive tcp_keepalive = 1;

// If true, the source address of the origin is spoofed on the connection to the upstream.
// [#not-implemented-hide:] Hide from docs.
bool src_transparent = 2;

// If non-zero, set the SO_MARK option on the upstream connection's socket. Allows matching on
// e.g. 'ip rule' or 'iptables'.
// [#proto-status: draft] Still working on this.
uint32 mark = 3;
}
27 changes: 27 additions & 0 deletions include/envoy/network/listen_socket.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,15 @@
#include "envoy/network/address.h"

#include "absl/strings/string_view.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<std::pair<int, int>> SocketOptionName;

/**
* Base class for Sockets
*/
Expand Down Expand Up @@ -56,7 +61,29 @@ class Socket {
* not be modified.
*/
virtual void hashKey(std::vector<uint8_t>& key) const PURE;

/**
* Contains details about what this option applies to a socket.
*/
struct Details {
SocketOptionName name_;
std::string value_; ///< Binary string representation of an option's value.

bool operator==(const Details& other) const {
return name_ == other.name_ && value_ == other.value_;
}
};

/**
* @param socket The socket for which we want to know the options that would be applied.
* @param state The state at which we would apply the options.
* @return What we would apply to the socket at the provided state. Empty if we'd apply nothing.
*/
virtual absl::optional<Details>
getOptionDetails(const Socket& socket,
envoy::api::v2::core::SocketOption::SocketState state) const PURE;
};

typedef std::shared_ptr<const Option> OptionConstSharedPtr;
typedef std::vector<OptionConstSharedPtr> Options;
typedef std::shared_ptr<Options> OptionsSharedPtr;
Expand Down
78 changes: 53 additions & 25 deletions source/common/network/addr_family_aware_socket_option_impl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -10,50 +10,78 @@
namespace Envoy {
namespace Network {

bool AddrFamilyAwareSocketOptionImpl::setOption(
Socket& socket, envoy::api::v2::core::SocketOption::SocketState state) const {
return setIpSocketOption(socket, state, ipv4_option_, ipv6_option_);
}

bool AddrFamilyAwareSocketOptionImpl::setIpSocketOption(
Socket& socket, envoy::api::v2::core::SocketOption::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;
namespace {
absl::optional<Address::IpVersion> getVersionFromSocket(const Socket& socket) {
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();
return absl::optional<Address::IpVersion>(socket.localAddress()->ip()->version());
} else {
address = Address::addressFromFd(socket.fd());
ip = address->ip();
return absl::optional<Address::IpVersion>(
Address::addressFromFd(socket.fd())->ip()->version());
}
} 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;

return absl::nullopt;
}

absl::optional<std::reference_wrapper<SocketOptionImpl>>
getOptionForSocket(const Socket& socket, SocketOptionImpl& ipv4_option,
SocketOptionImpl& ipv6_option) {
auto version = getVersionFromSocket(socket);
if (!version.has_value()) {
return absl::nullopt;
}

// 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 (*version == Network::Address::IpVersion::v4) {
return absl::optional<std::reference_wrapper<SocketOptionImpl>>(ipv4_option);
}

// If the FD is v6, we first try the IPv6 variant if the platform 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);
ASSERT(*version == Network::Address::IpVersion::v6);
if (ipv6_option.isSupported()) {
return absl::optional<std::reference_wrapper<SocketOptionImpl>>(ipv6_option);
}
return ipv4_option->setOption(socket, state);
return absl::optional<std::reference_wrapper<SocketOptionImpl>>(ipv4_option);
}

} // namespace

bool AddrFamilyAwareSocketOptionImpl::setOption(
Socket& socket, envoy::api::v2::core::SocketOption::SocketState state) const {
return setIpSocketOption(socket, state, ipv4_option_, ipv6_option_);
}

absl::optional<Socket::Option::Details> AddrFamilyAwareSocketOptionImpl::getOptionDetails(
const Socket& socket, envoy::api::v2::core::SocketOption::SocketState state) const {
auto option = getOptionForSocket(socket, *ipv4_option_, *ipv6_option_);

if (!option.has_value()) {
return absl::nullopt;
}

return option->get().getOptionDetails(socket, state);
}

bool AddrFamilyAwareSocketOptionImpl::setIpSocketOption(
Socket& socket, envoy::api::v2::core::SocketOption::SocketState state,
const std::unique_ptr<SocketOptionImpl>& ipv4_option,
const std::unique_ptr<SocketOptionImpl>& ipv6_option) {
auto option = getOptionForSocket(socket, *ipv4_option, *ipv6_option);

if (!option.has_value()) {
ENVOY_LOG(warn, "Failed to set IP socket option on non-IP socket");
return false;
}

return option->get().setOption(socket, state);
}

} // namespace Network
Expand Down
4 changes: 4 additions & 0 deletions source/common/network/addr_family_aware_socket_option_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@ class AddrFamilyAwareSocketOptionImpl : public Socket::Option,
// The common socket options don't require a hash key.
void hashKey(std::vector<uint8_t>&) const override {}

absl::optional<Details>
getOptionDetails(const Socket& socket,
envoy::api::v2::core::SocketOption::SocketState state) 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
Expand Down
9 changes: 9 additions & 0 deletions source/common/network/socket_option_factory.cc
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,15 @@ std::unique_ptr<Socket::Options> SocketOptionFactory::buildIpTransparentOptions(
return options;
}

std::unique_ptr<Socket::Options> SocketOptionFactory::buildSocketMarkOptions(uint32_t mark) {
std::unique_ptr<Socket::Options> options = absl::make_unique<Socket::Options>();
// we need this to happen prior to binding or prior to connecting. In both cases, PREBIND will
// fire.
options->push_back(std::make_shared<Network::SocketOptionImpl>(
envoy::api::v2::core::SocketOption::STATE_PREBIND, ENVOY_SOCKET_SO_MARK, mark));
return options;
}

std::unique_ptr<Socket::Options> SocketOptionFactory::buildLiteralOptions(
const Protobuf::RepeatedPtrField<envoy::api::v2::core::SocketOption>& socket_options) {
auto options = absl::make_unique<Socket::Options>();
Expand Down
1 change: 1 addition & 0 deletions source/common/network/socket_option_factory.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ class SocketOptionFactory : Logger::Loggable<Logger::Id::connection> {
buildTcpKeepaliveOptions(Network::TcpKeepaliveConfig keepalive_config);
static std::unique_ptr<Socket::Options> buildIpFreebindOptions();
static std::unique_ptr<Socket::Options> buildIpTransparentOptions();
static std::unique_ptr<Socket::Options> buildSocketMarkOptions(uint32_t mark);
static std::unique_ptr<Socket::Options> buildTcpFastOpenOptions(uint32_t queue_length);
static std::unique_ptr<Socket::Options> buildLiteralOptions(
const Protobuf::RepeatedPtrField<envoy::api::v2::core::SocketOption>& socket_options);
Expand Down
12 changes: 12 additions & 0 deletions source/common/network/socket_option_impl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,18 @@ bool SocketOptionImpl::setOption(Socket& socket,
return true;
}

absl::optional<Socket::Option::Details>
SocketOptionImpl::getOptionDetails(const Socket&,
envoy::api::v2::core::SocketOption::SocketState state) const {
if (state != in_state_) {
return absl::nullopt;
}
Socket::Option::Details info;
info.name_ = optname_;
info.value_ = value_;
return absl::optional<Option::Details>(std::move(info));
}

bool SocketOptionImpl::isSupported() const { return optname_.has_value(); }

Api::SysCallIntResult SocketOptionImpl::setSocketOption(Socket& socket,
Expand Down
16 changes: 10 additions & 6 deletions source/common/network/socket_option_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,9 @@

#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<std::pair<int, int>> SocketOptionName;

#ifdef IP_TRANSPARENT
#define ENVOY_SOCKET_IP_TRANSPARENT \
Network::SocketOptionName(std::make_pair(IPPROTO_IP, IP_TRANSPARENT))
Expand Down Expand Up @@ -53,6 +47,12 @@ typedef absl::optional<std::pair<int, int>> SocketOptionName;
#define ENVOY_SOCKET_SO_KEEPALIVE Network::SocketOptionName()
#endif

#ifdef SO_MARK
#define ENVOY_SOCKET_SO_MARK Network::SocketOptionName(std::make_pair(SOL_SOCKET, SO_MARK))
#else
#define ENVOY_SOCKET_SO_MARK Network::SocketOptionName()
#endif

#ifdef TCP_KEEPCNT
#define ENVOY_SOCKET_TCP_KEEPCNT Network::SocketOptionName(std::make_pair(IPPROTO_TCP, TCP_KEEPCNT))
#else
Expand Down Expand Up @@ -101,6 +101,10 @@ class SocketOptionImpl : public Socket::Option, Logger::Loggable<Logger::Id::con
// The common socket options don't require a hash key.
void hashKey(std::vector<uint8_t>&) const override {}

absl::optional<Details>
getOptionDetails(const Socket& socket,
envoy::api::v2::core::SocketOption::SocketState state) const override;

bool isSupported() const;

/**
Expand Down
9 changes: 9 additions & 0 deletions source/common/upstream/upstream_impl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,15 @@ parseClusterSocketOptions(const envoy::api::v2::Cluster& config,
Network::Socket::appendOptions(cluster_options,
Network::SocketOptionFactory::buildIpFreebindOptions());
}
if (config.upstream_connection_options().src_transparent()) {
Network::Socket::appendOptions(cluster_options,
Network::SocketOptionFactory::buildIpTransparentOptions());
}
const uint32_t mark = config.upstream_connection_options().mark();
if (mark != 0) {
Network::Socket::appendOptions(cluster_options,
Network::SocketOptionFactory::buildSocketMarkOptions(mark));
}
if (config.upstream_connection_options().has_tcp_keepalive()) {
Network::Socket::appendOptions(
cluster_options,
Expand Down
13 changes: 13 additions & 0 deletions test/common/network/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,19 @@ envoy_cc_test(
],
)

envoy_cc_test(
name = "socket_option_factory_test",
srcs = ["socket_option_factory_test.cc"],
deps = [
"//source/common/network:address_lib",
"//source/common/network:socket_option_factory_lib",
"//source/common/network:socket_option_lib",
"//test/mocks/api:api_mocks",
"//test/mocks/network:network_mocks",
"//test/test_common:environment_lib",
],
)

envoy_cc_test(
name = "addr_family_aware_socket_option_impl_test",
srcs = ["addr_family_aware_socket_option_impl_test.cc"],
Expand Down
54 changes: 54 additions & 0 deletions test/common/network/addr_family_aware_socket_option_impl_test.cc
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#include "common/network/addr_family_aware_socket_option_impl.h"
#include "common/network/utility.h"

#include "test/common/network/socket_option_test.h"

Expand Down Expand Up @@ -119,6 +120,59 @@ TEST_F(AddrFamilyAwareSocketOptionImplTest, V6Precedence) {
{envoy::api::v2::core::SocketOption::STATE_PREBIND});
}

// GetSocketOptionName returns the v4 information for a v4 address
TEST_F(AddrFamilyAwareSocketOptionImplTest, V4GetSocketOptionName) {
socket_.local_address_ = Utility::parseInternetAddress("1.2.3.4", 5678);

AddrFamilyAwareSocketOptionImpl socket_option{envoy::api::v2::core::SocketOption::STATE_PREBIND,
Network::SocketOptionName(std::make_pair(5, 10)),
Network::SocketOptionName(std::make_pair(6, 11)),
1};
auto result =
socket_option.getOptionDetails(socket_, envoy::api::v2::core::SocketOption::STATE_PREBIND);
ASSERT_TRUE(result.has_value());
EXPECT_EQ(result.value(), makeDetails(std::make_pair(5, 10), 1));
}

// GetSocketOptionName returns the v4 information for a v6 address
TEST_F(AddrFamilyAwareSocketOptionImplTest, V6GetSocketOptionName) {
socket_.local_address_ = Utility::parseInternetAddress("2::1", 5678);

AddrFamilyAwareSocketOptionImpl socket_option{envoy::api::v2::core::SocketOption::STATE_PREBIND,
Network::SocketOptionName(std::make_pair(5, 10)),
Network::SocketOptionName(std::make_pair(6, 11)),
5};
auto result =
socket_option.getOptionDetails(socket_, envoy::api::v2::core::SocketOption::STATE_PREBIND);
ASSERT_TRUE(result.has_value());
EXPECT_EQ(result.value(), makeDetails(std::make_pair(6, 11), 5));
}

// GetSocketOptionName returns nullopt if the state is wrong
TEST_F(AddrFamilyAwareSocketOptionImplTest, GetSocketOptionWrongState) {
socket_.local_address_ = Utility::parseInternetAddress("2::1", 5678);

AddrFamilyAwareSocketOptionImpl socket_option{envoy::api::v2::core::SocketOption::STATE_PREBIND,
Network::SocketOptionName(std::make_pair(5, 10)),
Network::SocketOptionName(std::make_pair(6, 11)),
5};
auto result =
socket_option.getOptionDetails(socket_, envoy::api::v2::core::SocketOption::STATE_BOUND);
EXPECT_FALSE(result.has_value());
}

// GetSocketOptionName returns nullopt if the version could not be determined
TEST_F(AddrFamilyAwareSocketOptionImplTest, GetSocketOptionCannotDetermineVersion) {
AddrFamilyAwareSocketOptionImpl socket_option{envoy::api::v2::core::SocketOption::STATE_PREBIND,
Network::SocketOptionName(std::make_pair(5, 10)),
Network::SocketOptionName(std::make_pair(6, 11)),
5};

EXPECT_CALL(socket_, fd()).WillOnce(Return(-1));
auto result =
socket_option.getOptionDetails(socket_, envoy::api::v2::core::SocketOption::STATE_PREBIND);
EXPECT_FALSE(result.has_value());
}
} // namespace
} // namespace Network
} // namespace Envoy
Loading