Skip to content
Merged
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
27 changes: 27 additions & 0 deletions include/envoy/network/listen_socket.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,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 @@ -57,7 +62,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.
Comment thread
klarose marked this conversation as resolved.

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
13 changes: 13 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,19 @@ 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_ || !isSupported()) {
return absl::nullopt;
}

Socket::Option::Details info;
info.name_ = optname_;
info.value_ = value_;
return absl::optional<Option::Details>(std::move(info));
}
Comment thread
klarose marked this conversation as resolved.

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

Api::SysCallIntResult SocketOptionImpl::setSocketOption(Socket& socket,
Expand Down
10 changes: 4 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 @@ -101,6 +95,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
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
37 changes: 37 additions & 0 deletions test/common/network/socket_option_impl_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,43 @@ TEST_F(SocketOptionImplTest, SetOptionSuccessTrue) {
EXPECT_TRUE(socket_option.setOption(socket_, envoy::api::v2::core::SocketOption::STATE_PREBIND));
}

TEST_F(SocketOptionImplTest, GetOptionDetailsCorrectState) {
SocketOptionImpl socket_option{envoy::api::v2::core::SocketOption::STATE_PREBIND,
Network::SocketOptionName(std::make_pair(5, 10)), 1};

auto result =
socket_option.getOptionDetails(socket_, envoy::api::v2::core::SocketOption::STATE_PREBIND);
ASSERT_TRUE(result.has_value());
EXPECT_EQ(*result, makeDetails(std::make_pair(5, 10), 1));
}

TEST_F(SocketOptionImplTest, GetMoreOptionDetailsCorrectState) {
SocketOptionImpl socket_option{envoy::api::v2::core::SocketOption::STATE_LISTENING,
Network::SocketOptionName(std::make_pair(7, 9)), 5};

auto result =
socket_option.getOptionDetails(socket_, envoy::api::v2::core::SocketOption::STATE_LISTENING);
ASSERT_TRUE(result.has_value());
EXPECT_EQ(*result, makeDetails(std::make_pair(7, 9), 5));
}

TEST_F(SocketOptionImplTest, GetOptionDetailsFailureWrongState) {
SocketOptionImpl socket_option{envoy::api::v2::core::SocketOption::STATE_LISTENING,
Network::SocketOptionName(std::make_pair(7, 9)), 5};

auto result =
socket_option.getOptionDetails(socket_, envoy::api::v2::core::SocketOption::STATE_BOUND);
EXPECT_FALSE(result.has_value());
}

TEST_F(SocketOptionImplTest, GetUnsupportedOptReturnsNullopt) {
SocketOptionImpl socket_option{envoy::api::v2::core::SocketOption::STATE_LISTENING,
Network::SocketOptionName(absl::nullopt), 5};

auto result =
socket_option.getOptionDetails(socket_, envoy::api::v2::core::SocketOption::STATE_LISTENING);
EXPECT_FALSE(result.has_value());
}
} // namespace
} // namespace Network
} // namespace Envoy
10 changes: 10 additions & 0 deletions test/common/network/socket_option_test.h
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,16 @@ class SocketOptionTest : public testing::Test {
EXPECT_TRUE(socket_option.setOption(socket_, state));
}
}

Socket::Option::Details makeDetails(Network::SocketOptionName name, int value) {
absl::string_view value_as_bstr(reinterpret_cast<const char*>(&value), sizeof(value));

Socket::Option::Details expected_info;
expected_info.name_ = name;
expected_info.value_ = std::string(value_as_bstr);

return expected_info;
}
};

} // namespace
Expand Down
3 changes: 3 additions & 0 deletions test/mocks/network/mocks.h
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,9 @@ class MockSocketOption : public Socket::Option {
MOCK_CONST_METHOD2(setOption,
bool(Socket&, envoy::api::v2::core::SocketOption::SocketState state));
MOCK_CONST_METHOD1(hashKey, void(std::vector<uint8_t>&));
MOCK_CONST_METHOD2(getOptionDetails,
absl::optional<Socket::Option::Details>(
const Socket&, envoy::api::v2::core::SocketOption::SocketState state));
};

class MockConnectionSocket : public ConnectionSocket {
Expand Down