From 06129af9753ca142b18178e8312f8c3204d41b24 Mon Sep 17 00:00:00 2001 From: Kyle Larose Date: Fri, 9 Nov 2018 16:30:38 -0500 Subject: [PATCH 1/3] Add SO_MARK as a socket option We can use this to control whether or not a packet is marked when emitted by Envoy. Signed-off-by: Kyle Larose --- .../common/network/socket_option_factory.cc | 9 ++ source/common/network/socket_option_factory.h | 1 + source/common/network/socket_option_impl.h | 6 + test/common/network/BUILD | 14 ++ .../network/socket_option_factory_test.cc | 124 ++++++++++++++++++ 5 files changed, 154 insertions(+) create mode 100644 test/common/network/socket_option_factory_test.cc diff --git a/source/common/network/socket_option_factory.cc b/source/common/network/socket_option_factory.cc index 2bbd9e6d43cb5..65706376c3eff 100644 --- a/source/common/network/socket_option_factory.cc +++ b/source/common/network/socket_option_factory.cc @@ -49,6 +49,15 @@ std::unique_ptr SocketOptionFactory::buildIpTransparentOptions( return options; } +std::unique_ptr SocketOptionFactory::buildSocketMarkOptions(uint32_t mark) { + std::unique_ptr options = absl::make_unique(); + // we need this to happen prior to binding or prior to connecting. In both cases, PREBIND will + // fire. + options->push_back(std::make_shared( + envoy::api::v2::core::SocketOption::STATE_PREBIND, ENVOY_SOCKET_SO_MARK, mark)); + return options; +} + std::unique_ptr SocketOptionFactory::buildLiteralOptions( const Protobuf::RepeatedPtrField& socket_options) { auto options = absl::make_unique(); diff --git a/source/common/network/socket_option_factory.h b/source/common/network/socket_option_factory.h index 7ba832e157730..3d49beb717e16 100644 --- a/source/common/network/socket_option_factory.h +++ b/source/common/network/socket_option_factory.h @@ -29,6 +29,7 @@ class SocketOptionFactory : Logger::Loggable { buildTcpKeepaliveOptions(Network::TcpKeepaliveConfig keepalive_config); static std::unique_ptr buildIpFreebindOptions(); static std::unique_ptr buildIpTransparentOptions(); + static std::unique_ptr buildSocketMarkOptions(uint32_t mark); static std::unique_ptr buildTcpFastOpenOptions(uint32_t queue_length); static std::unique_ptr buildLiteralOptions( const Protobuf::RepeatedPtrField& socket_options); diff --git a/source/common/network/socket_option_impl.h b/source/common/network/socket_option_impl.h index cd233e109c5bf..1d6dc4e777872 100644 --- a/source/common/network/socket_option_impl.h +++ b/source/common/network/socket_option_impl.h @@ -53,6 +53,12 @@ typedef absl::optional> 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 diff --git a/test/common/network/BUILD b/test/common/network/BUILD index 13d44e123d6c8..c63b36e6758c4 100644 --- a/test/common/network/BUILD +++ b/test/common/network/BUILD @@ -183,6 +183,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"], diff --git a/test/common/network/socket_option_factory_test.cc b/test/common/network/socket_option_factory_test.cc new file mode 100644 index 0000000000000..50547fbc3f9a9 --- /dev/null +++ b/test/common/network/socket_option_factory_test.cc @@ -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 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("1.2.3.4", 5678); + std::make_unique("::1:2:3:4", 5678); + return &os_sys_calls_mock_; + }()}; + +protected: + testing::NiceMock 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("1.2.3.4", 5678); + } + void makeSocketV6() { + socket_mock_.local_address_ = std::make_unique("::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 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(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 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(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 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(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 From f34529ace2485ae0e4ef88c4275e581991716576 Mon Sep 17 00:00:00 2001 From: Kyle Larose Date: Fri, 21 Dec 2018 10:21:49 -0500 Subject: [PATCH 2/3] Add todo to UT We can simplify the tests in the future. Leave a TODO. Signed-off-by: Kyle Larose --- test/common/network/socket_option_factory_test.cc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/common/network/socket_option_factory_test.cc b/test/common/network/socket_option_factory_test.cc index 50547fbc3f9a9..7ba8f53d1e518 100644 --- a/test/common/network/socket_option_factory_test.cc +++ b/test/common/network/socket_option_factory_test.cc @@ -43,6 +43,8 @@ class SocketOptionFactoryTest : public testing::Test { return; \ } +// TODO(klarose): Simplify these tests once https://github.com/envoyproxy/envoy/pull/5351 is merged. + TEST_F(SocketOptionFactoryTest, TestBuildSocketMarkOptions) { // use a shared_ptr due to applyOptions requiring one From 5045f59a5fc2100815f19a935b5dd6266b738056 Mon Sep 17 00:00:00 2001 From: Kyle Larose Date: Fri, 21 Dec 2018 15:18:55 -0500 Subject: [PATCH 3/3] Use std::make_unique instead of absl::make_unique As requested in review. Signed-off-by: Kyle Larose --- source/common/network/socket_option_factory.cc | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/source/common/network/socket_option_factory.cc b/source/common/network/socket_option_factory.cc index 65706376c3eff..44c113bb408b9 100644 --- a/source/common/network/socket_option_factory.cc +++ b/source/common/network/socket_option_factory.cc @@ -8,7 +8,7 @@ namespace Network { std::unique_ptr SocketOptionFactory::buildTcpKeepaliveOptions(Network::TcpKeepaliveConfig keepalive_config) { - std::unique_ptr options = absl::make_unique(); + std::unique_ptr options = std::make_unique(); options->push_back(std::make_shared( envoy::api::v2::core::SocketOption::STATE_PREBIND, ENVOY_SOCKET_SO_KEEPALIVE, 1)); @@ -31,7 +31,7 @@ SocketOptionFactory::buildTcpKeepaliveOptions(Network::TcpKeepaliveConfig keepal } std::unique_ptr SocketOptionFactory::buildIpFreebindOptions() { - std::unique_ptr options = absl::make_unique(); + std::unique_ptr options = std::make_unique(); options->push_back(std::make_shared( envoy::api::v2::core::SocketOption::STATE_PREBIND, ENVOY_SOCKET_IP_FREEBIND, ENVOY_SOCKET_IPV6_FREEBIND, 1)); @@ -39,7 +39,7 @@ std::unique_ptr SocketOptionFactory::buildIpFreebindOptions() { } std::unique_ptr SocketOptionFactory::buildIpTransparentOptions() { - std::unique_ptr options = absl::make_unique(); + std::unique_ptr options = std::make_unique(); options->push_back(std::make_shared( envoy::api::v2::core::SocketOption::STATE_PREBIND, ENVOY_SOCKET_IP_TRANSPARENT, ENVOY_SOCKET_IPV6_TRANSPARENT, 1)); @@ -50,7 +50,7 @@ std::unique_ptr SocketOptionFactory::buildIpTransparentOptions( } std::unique_ptr SocketOptionFactory::buildSocketMarkOptions(uint32_t mark) { - std::unique_ptr options = absl::make_unique(); + std::unique_ptr options = std::make_unique(); // we need this to happen prior to binding or prior to connecting. In both cases, PREBIND will // fire. options->push_back(std::make_shared( @@ -60,7 +60,7 @@ std::unique_ptr SocketOptionFactory::buildSocketMarkOptions(uin std::unique_ptr SocketOptionFactory::buildLiteralOptions( const Protobuf::RepeatedPtrField& socket_options) { - auto options = absl::make_unique(); + auto options = std::make_unique(); for (const auto& socket_option : socket_options) { std::string buf; int int_value; @@ -87,7 +87,7 @@ std::unique_ptr SocketOptionFactory::buildLiteralOptions( std::unique_ptr SocketOptionFactory::buildTcpFastOpenOptions(uint32_t queue_length) { - std::unique_ptr options = absl::make_unique(); + std::unique_ptr options = std::make_unique(); options->push_back(std::make_shared( envoy::api::v2::core::SocketOption::STATE_LISTENING, ENVOY_SOCKET_TCP_FASTOPEN, queue_length));