Skip to content
Closed
Show file tree
Hide file tree
Changes from 8 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
11 changes: 10 additions & 1 deletion api/envoy/api/v2/cds.proto
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,8 @@ service ClusterDiscoveryService {
// [#protodoc-title: Clusters]

// Configuration for a single upstream cluster.
// [#comment:next free field: 38]

// [#comment:next free field: 40]
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 @@ -546,6 +547,14 @@ message Cluster {
// If this flag is not set to true, Envoy will wait until the hosts fail active health
// checking before removing it from the cluster.
bool drain_connections_on_host_removal = 32;

Comment thread
klarose marked this conversation as resolved.
Outdated
// 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 = 38;

// if non-zero, set the MARK field. Allows matching on e.g. 'ip rule' or 'iptables'.
Comment thread
klarose marked this conversation as resolved.
Outdated
// [#proto-status: draft] Still working on this.
uint32 mark = 39;
}

// An extensible structure containing the address Envoy should bind to when
Expand Down
2 changes: 1 addition & 1 deletion source/common/json/config_schemas.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1615,7 +1615,7 @@ const std::string Json::Schema::CLUSTER_SCHEMA(R"EOF(
}
},
"additionalProperties" : false
}
}
},
"required" : ["name", "type", "connect_timeout_ms", "lb_type"],
"additionalProperties" : false
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
6 changes: 6 additions & 0 deletions source/common/network/socket_option_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,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
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.src_transparent()) {
Network::Socket::appendOptions(cluster_options,
Network::SocketOptionFactory::buildIpTransparentOptions());
}
const uint32_t mark = config.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
14 changes: 14 additions & 0 deletions test/common/network/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,20 @@ 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",
"//test/test_common:threadsafe_singleton_injector_lib",
],
)

envoy_cc_test(
name = "addr_family_aware_socket_option_impl_test",
srcs = ["addr_family_aware_socket_option_impl_test.cc"],
Expand Down
124 changes: 124 additions & 0 deletions test/common/network/socket_option_factory_test.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
#include "common/network/address_impl.h"
#include "common/network/socket_option_factory.h"
#include "common/network/socket_option_impl.h"

#include "test/mocks/api/mocks.h"
#include "test/mocks/network/mocks.h"
#include "test/test_common/threadsafe_singleton_injector.h"

#include "gtest/gtest.h"

using testing::_;

namespace Envoy {
namespace Network {

class SocketOptionFactoryTest : public testing::Test {
public:
SocketOptionFactoryTest() = default;

TestThreadsafeSingletonInjector<Api::OsSysCallsImpl> os_calls_{[this]() {
// Before injecting OsSysCallsImpl, make sure validateIpv{4,6}Supported is called so the static
// bool is initialized without requiring to mock ::socket and ::close. :( :(
std::make_unique<Address::Ipv4Instance>("1.2.3.4", 5678);
std::make_unique<Address::Ipv6Instance>("::1:2:3:4", 5678);
return &os_sys_calls_mock_;
}()};

protected:
testing::NiceMock<MockListenSocket> socket_mock_;
Api::MockOsSysCalls os_sys_calls_mock_;

void SetUp() override { socket_mock_.local_address_.reset(); }
void makeSocketV4() {
socket_mock_.local_address_ = std::make_unique<Address::Ipv4Instance>("1.2.3.4", 5678);
}
void makeSocketV6() {
socket_mock_.local_address_ = std::make_unique<Address::Ipv6Instance>("::1:2:3:4", 5678);
}
};

#define CHECK_OPTION_SUPPORTED(option) \
if (!option.has_value()) { \
return; \
}

TEST_F(SocketOptionFactoryTest, TestBuildSocketMarkOptions) {

// use a shared_ptr due to applyOptions requiring one
std::shared_ptr<Socket::Options> options = SocketOptionFactory::buildSocketMarkOptions(100);

const auto expected_option = ENVOY_SOCKET_SO_MARK;
CHECK_OPTION_SUPPORTED(expected_option);

const int type = expected_option.value().first;
const int option = expected_option.value().second;
EXPECT_CALL(os_sys_calls_mock_, setsockopt_(_, _, _, _, sizeof(int)))
.WillOnce(Invoke([type, option](int, int input_type, int input_option, const void* optval,
socklen_t) -> int {
EXPECT_EQ(100, *static_cast<const int*>(optval));
EXPECT_EQ(type, input_type);
EXPECT_EQ(option, input_option);
return 0;
}));

EXPECT_TRUE(Network::Socket::applyOptions(options, socket_mock_,
envoy::api::v2::core::SocketOption::STATE_PREBIND));
}

TEST_F(SocketOptionFactoryTest, TestBuildIpv4TransparentOptions) {
makeSocketV4();

// use a shared_ptr due to applyOptions requiring one
std::shared_ptr<Socket::Options> options = SocketOptionFactory::buildIpTransparentOptions();

const auto expected_option = ENVOY_SOCKET_IP_TRANSPARENT;
CHECK_OPTION_SUPPORTED(expected_option);

const int type = expected_option.value().first;
const int option = expected_option.value().second;
EXPECT_CALL(os_sys_calls_mock_, setsockopt_(_, _, _, _, sizeof(int)))
.Times(2)
.WillRepeatedly(Invoke([type, option](int, int input_type, int input_option,
const void* optval, socklen_t) -> int {
EXPECT_EQ(type, input_type);
EXPECT_EQ(option, input_option);
EXPECT_EQ(1, *static_cast<const int*>(optval));
return 0;
}));

EXPECT_TRUE(Network::Socket::applyOptions(options, socket_mock_,
envoy::api::v2::core::SocketOption::STATE_PREBIND));
EXPECT_TRUE(Network::Socket::applyOptions(options, socket_mock_,
envoy::api::v2::core::SocketOption::STATE_BOUND));
}

TEST_F(SocketOptionFactoryTest, TestBuildIpv6TransparentOptions) {
makeSocketV6();

// use a shared_ptr due to applyOptions requiring one
std::shared_ptr<Socket::Options> options = SocketOptionFactory::buildIpTransparentOptions();

const auto expected_option = ENVOY_SOCKET_IPV6_TRANSPARENT;
CHECK_OPTION_SUPPORTED(expected_option);

const int type = expected_option.value().first;
const int option = expected_option.value().second;
EXPECT_CALL(os_sys_calls_mock_, setsockopt_(_, _, _, _, sizeof(int)))
.Times(2)
.WillRepeatedly(Invoke([type, option](int, int input_type, int input_option,
const void* optval, socklen_t) -> int {
EXPECT_EQ(type, input_type);
EXPECT_EQ(option, input_option);
EXPECT_EQ(1, *static_cast<const int*>(optval));
return 0;
}));

EXPECT_TRUE(Network::Socket::applyOptions(options, socket_mock_,
envoy::api::v2::core::SocketOption::STATE_PREBIND));
EXPECT_TRUE(Network::Socket::applyOptions(options, socket_mock_,
envoy::api::v2::core::SocketOption::STATE_BOUND));
}

} // namespace Network
} // namespace Envoy
4 changes: 4 additions & 0 deletions test/common/upstream/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -355,18 +355,22 @@ envoy_cc_test(
"//source/common/event:dispatcher_lib",
"//source/common/json:config_schemas_lib",
"//source/common/json:json_loader_lib",
"//source/common/network:address_lib",
"//source/common/network:socket_option_lib",
"//source/common/network:utility_lib",
"//source/common/upstream:upstream_includes",
"//source/common/upstream:upstream_lib",
"//source/extensions/transport_sockets/raw_buffer:config",
"//source/server:transport_socket_config_lib",
"//test/mocks:common_lib",
"//test/mocks/api:api_mocks",
"//test/mocks/local_info:local_info_mocks",
"//test/mocks/network:network_mocks",
"//test/mocks/runtime:runtime_mocks",
"//test/mocks/ssl:ssl_mocks",
"//test/mocks/upstream:upstream_mocks",
"//test/test_common:registry_lib",
"//test/test_common:threadsafe_singleton_injector_lib",
"//test/test_common:utility_lib",
],
)
Expand Down
74 changes: 74 additions & 0 deletions test/common/upstream/upstream_impl_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -13,19 +13,23 @@
#include "common/config/metadata.h"
#include "common/json/config_schemas.h"
#include "common/json/json_loader.h"
#include "common/network/address_impl.h"
#include "common/network/socket_option_impl.h"
#include "common/network/utility.h"
#include "common/upstream/upstream_impl.h"

#include "server/transport_socket_config_impl.h"

#include "test/common/upstream/utility.h"
#include "test/mocks/api/mocks.h"
#include "test/mocks/common.h"
#include "test/mocks/local_info/mocks.h"
#include "test/mocks/network/mocks.h"
#include "test/mocks/runtime/mocks.h"
#include "test/mocks/ssl/mocks.h"
#include "test/mocks/upstream/mocks.h"
#include "test/test_common/registry.h"
#include "test/test_common/threadsafe_singleton_injector.h"
#include "test/test_common/utility.h"

#include "gmock/gmock.h"
Expand Down Expand Up @@ -1557,6 +1561,11 @@ TEST(PrioritySet, Extend) {
}
}

#define CHECK_OPTION_SUPPORTED(option) \
if (!option.has_value()) { \
return; \
}

class ClusterInfoImplTest : public testing::Test {
public:
std::unique_ptr<StrictDnsClusterImpl> makeCluster(const std::string& yaml) {
Expand All @@ -1583,6 +1592,18 @@ class ClusterInfoImplTest : public testing::Test {
envoy::api::v2::Cluster cluster_config_;
Envoy::Stats::ScopePtr scope_;
std::unique_ptr<Server::Configuration::TransportSocketFactoryContextImpl> factory_context_;

protected:
TestThreadsafeSingletonInjector<Api::OsSysCallsImpl> os_calls_{[this]() {
// Before injecting OsSysCallsImpl, make sure validateIpv{4,6}Supported is called so the static
// bool is initialized without requiring to mock ::socket and ::close. :( :(
std::make_unique<Envoy::Network::Address::Ipv4Instance>("1.2.3.4", 5678);
std::make_unique<Envoy::Network::Address::Ipv6Instance>("::1:2:3:4", 5678);
return &os_sys_calls_mock_;
}()};

testing::NiceMock<Envoy::Network::MockListenSocket> socket_mock_;
Api::MockOsSysCalls os_sys_calls_mock_;
};

struct Foo : public Envoy::Config::TypedMetadata::Object {};
Expand Down Expand Up @@ -1838,6 +1859,59 @@ TEST_F(ClusterInfoImplTest, ExtensionProtocolOptionsForFilterWithOptions) {
}
}

Comment thread
klarose marked this conversation as resolved.
TEST_F(ClusterInfoImplTest, TestNoSocketOptionsDefault) {
auto cluster = makeCluster(strictDnsClusterWithAttrYaml("" /* no extra options */));
auto options = cluster->info()->clusterSocketOptions();

EXPECT_EQ(nullptr, options);
}

TEST_F(ClusterInfoImplTest, TestMarkSocketOptions) {
auto cluster = makeCluster(strictDnsClusterWithAttrYaml("mark: 145"));
auto options = cluster->info()->clusterSocketOptions();

const auto expected_option = ENVOY_SOCKET_SO_MARK;
CHECK_OPTION_SUPPORTED(expected_option);
const int type = expected_option.value().first;
const int option = expected_option.value().second;

EXPECT_CALL(os_sys_calls_mock_, setsockopt_(_, _, _, _, sizeof(int)))
.WillOnce(Invoke([type, option](int, int input_type, int input_option, const void* optval,
socklen_t) -> int {
EXPECT_EQ(145, *static_cast<const int*>(optval));
EXPECT_EQ(type, input_type);
EXPECT_EQ(option, input_option);
return 0;
}));

Envoy::Network::Socket::applyOptions(options, socket_mock_,
envoy::api::v2::core::SocketOption::STATE_PREBIND);
}

TEST_F(ClusterInfoImplTest, TestTransparentSocketOptions) {
auto cluster = makeCluster(strictDnsClusterWithAttrYaml("src_transparent: true"));
auto options = cluster->info()->clusterSocketOptions();

const auto expected_option = ENVOY_SOCKET_IP_TRANSPARENT;
CHECK_OPTION_SUPPORTED(expected_option);
const int type = expected_option.value().first;
const int option = expected_option.value().second;
socket_mock_.local_address_ =
std::make_unique<Envoy::Network::Address::Ipv4Instance>("1.2.3.4", 5678);

EXPECT_CALL(os_sys_calls_mock_, setsockopt_(_, _, _, _, sizeof(int)))
.WillOnce(Invoke([type, option](int, int input_type, int input_option, const void* optval,
socklen_t) -> int {
EXPECT_EQ(type, input_type);
EXPECT_EQ(option, input_option);
EXPECT_EQ(1, *static_cast<const int*>(optval));
return 0;
}));

Envoy::Network::Socket::applyOptions(options, socket_mock_,
envoy::api::v2::core::SocketOption::STATE_PREBIND);
}

// Validate empty singleton for HostsPerLocalityImpl.
TEST(HostsPerLocalityImpl, Empty) {
EXPECT_FALSE(HostsPerLocalityImpl::empty()->hasLocalLocality());
Expand Down
Loading