Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

upstream: add transparency options #5035

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

// Configuration for a single upstream cluster.
// [#comment:next free field: 37]
// [#comment:next free field: 39]
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 @@ -531,6 +531,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;

klarose marked this conversation as resolved.
Show resolved Hide resolved
// If true, the source address of the origin is spoofed on the connection to the upstream.
// [#not-implemented-hide:] Hide from docs.
google.protobuf.BoolValue src_transparent = 37;

// if non-zero, set the MARK field. Allows matching on e.g. 'ip rule' or 'iptables'.
klarose marked this conversation as resolved.
Show resolved Hide resolved
// [#proto-status: draft] Still working on this.
google.protobuf.UInt32Value mark = 38;
}

// An extensible structure containing the address Envoy should bind to when
Expand Down
2 changes: 2 additions & 0 deletions source/common/config/cds_json.cc
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,8 @@ void CdsJson::translateCluster(const Json::Object& json_cluster,
JSON_UTIL_SET_DURATION(json_cluster, cluster, cleanup_interval);
JSON_UTIL_SET_DURATION(json_cluster, cluster, connect_timeout);
JSON_UTIL_SET_INTEGER(json_cluster, cluster, per_connection_buffer_limit_bytes);
JSON_UTIL_SET_BOOL(json_cluster, cluster, src_transparent);
klarose marked this conversation as resolved.
Show resolved Hide resolved
JSON_UTIL_SET_INTEGER(json_cluster, cluster, mark);

const std::string lb_type = json_cluster.getString("lb_type");
if (lb_type == "round_robin") {
Expand Down
10 changes: 9 additions & 1 deletion source/common/json/config_schemas.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1615,7 +1615,15 @@ const std::string Json::Schema::CLUSTER_SCHEMA(R"EOF(
}
},
"additionalProperties" : false
}
},
"src_transparent": {
"type" : "boolean"
},
"mark": {
"type" : "integer",
"minimum" : 0,
"maximum" : 4294967295
}
},
"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 (PROTOBUF_GET_WRAPPED_OR_DEFAULT(config, src_transparent, false)) {
Network::Socket::appendOptions(cluster_options,
Network::SocketOptionFactory::buildIpTransparentOptions());
}
const uint32_t mark = PROTOBUF_GET_WRAPPED_OR_DEFAULT(config, mark, 0);
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/config/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,19 @@ load(

envoy_package()

envoy_cc_test(
name = "cds_json_test",
srcs = ["cds_json_test.cc"],
deps = [
"//source/common/config:cds_json_lib",
"//source/common/json:json_loader_lib",
"//source/common/protobuf:utility_lib",
"//source/common/stats:stats_options_lib",
"//test/common/upstream:utility_lib",
"//test/mocks/grpc:grpc_mocks",
],
)

envoy_cc_test(
name = "filesystem_subscription_impl_test",
srcs = ["filesystem_subscription_impl_test.cc"],
Expand Down
39 changes: 39 additions & 0 deletions test/common/config/cds_json_test.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
#include "common/config/cds_json.h"
#include "common/json/json_loader.h"
#include "common/protobuf/utility.h"
#include "common/stats/stats_options_impl.h"

#include "test/common/upstream/utility.h"

#include "gmock/gmock.h"
#include "gtest/gtest.h"

using Envoy::Upstream::makeClusterWithAttribute;
using testing::_;

namespace Envoy {
namespace Config {
namespace {

TEST(CdsJsonTest, TestClusterTranslationMark) {
const envoy::api::v2::Cluster cluster = makeClusterWithAttribute("\"mark\":5");
EXPECT_EQ(PROTOBUF_GET_WRAPPED_REQUIRED(cluster, mark), 5);
}

TEST(CdsJsonTest, TestClusterTranslatioMarkMax) {
const envoy::api::v2::Cluster cluster = makeClusterWithAttribute("\"mark\":4294967295");
EXPECT_EQ(PROTOBUF_GET_WRAPPED_REQUIRED(cluster, mark), 4294967295);
}

TEST(CdsJsonTest, TestClusterTranslationSrcTransparent) {
const envoy::api::v2::Cluster cluster = makeClusterWithAttribute("\"src_transparent\":true");
EXPECT_EQ(PROTOBUF_GET_WRAPPED_REQUIRED(cluster, src_transparent), true);
}

TEST(CdsJsonTest, TestClusterTranslationSrcTransparentFalse) {
const envoy::api::v2::Cluster cluster = makeClusterWithAttribute("\"src_transparent\":false");
EXPECT_EQ(PROTOBUF_GET_WRAPPED_REQUIRED(cluster, src_transparent), false);
}
} // namespace
} // namespace Config
} // namespace Envoy
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 @@ -353,18 +353,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
Loading