Skip to content

Commit

Permalink
Update network utility getoriginaldst for IPv6 (#3933)
Browse files Browse the repository at this point in the history
This PR adds IP6T_SO_ORIGINAL_DST to the network utility for retaining the original_dest.

Risk Level:
Medium: Because we have to hardcode this with a define, there is a risk that upstream this gets changed and we would have to adjust. The define is necessary without the unmerged patch from Oct 2016. The TLDR is that the C code does not compile well with our compiler as shown here.

Testing:
Current tests exist

Docs Changes:
In progress

Release Notes:
Support IPv6 for original_dest in network utility

Fixes #1094

Signed-off-by: Christopher M. Luciano <cmluciano@us.ibm.com>
  • Loading branch information
cmluciano authored and htuch committed Jul 24, 2018
1 parent 445e746 commit 0a43ae8
Show file tree
Hide file tree
Showing 3 changed files with 170 additions and 5 deletions.
1 change: 1 addition & 0 deletions source/common/network/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,7 @@ envoy_cc_library(
":address_lib",
"//include/envoy/network:connection_interface",
"//include/envoy/stats:stats_interface",
"//source/common/api:os_sys_calls_lib",
"//source/common/common:assert_lib",
"//source/common/common:utility_lib",
"//source/common/protobuf",
Expand Down
37 changes: 32 additions & 5 deletions source/common/network/utility.cc
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@
#include <linux/netfilter_ipv4.h>
#endif

#ifndef IP6T_SO_ORIGINAL_DST
// From linux/netfilter_ipv6/ip6_tables.h
#define IP6T_SO_ORIGINAL_DST 80
#endif

#include <netinet/ip.h>
#include <sys/socket.h>

Expand All @@ -20,6 +25,7 @@
#include "envoy/network/connection.h"
#include "envoy/stats/stats.h"

#include "common/api/os_sys_calls_impl.h"
#include "common/common/assert.h"
#include "common/common/utility.h"
#include "common/network/address_impl.h"
Expand Down Expand Up @@ -292,14 +298,35 @@ Address::InstanceConstSharedPtr Utility::getOriginalDst(int fd) {
#ifdef SOL_IP
sockaddr_storage orig_addr;
socklen_t addr_len = sizeof(sockaddr_storage);
int status = getsockopt(fd, SOL_IP, SO_ORIGINAL_DST, &orig_addr, &addr_len);
int socket_domain;
socklen_t domain_len = sizeof(socket_domain);
auto& os_syscalls = Api::OsSysCallsSingleton::get();
int status = os_syscalls.getsockopt(fd, SOL_SOCKET, SO_DOMAIN, &socket_domain, &domain_len);

if (status != 0) {
return nullptr;
}

if (status == 0) {
// TODO(mattklein123): IPv6 support. See github issue #1094.
ASSERT(orig_addr.ss_family == AF_INET);
if (socket_domain == AF_INET) {
status = os_syscalls.getsockopt(fd, SOL_IP, SO_ORIGINAL_DST, &orig_addr, &addr_len);
} else if (socket_domain == AF_INET6) {
status = os_syscalls.getsockopt(fd, SOL_IPV6, IP6T_SO_ORIGINAL_DST, &orig_addr, &addr_len);
} else {
return nullptr;
}

if (status != 0) {
return nullptr;
}

switch (orig_addr.ss_family) {
case AF_INET:
return Address::InstanceConstSharedPtr{
new Address::Ipv4Instance(reinterpret_cast<sockaddr_in*>(&orig_addr))};
} else {
case AF_INET6:
return Address::InstanceConstSharedPtr{
new Address::Ipv6Instance(reinterpret_cast<sockaddr_in6&>(orig_addr))};
default:
return nullptr;
}
#else
Expand Down
137 changes: 137 additions & 0 deletions test/server/listener_manager_impl_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -2269,6 +2269,143 @@ TEST_F(ListenerManagerImplWithRealFiltersTest, OriginalDstTestFilterOptionFail)
EXPECT_EQ(0U, manager_->listeners().size());
}

class OriginalDstTestFilterIPv6
: public Extensions::ListenerFilters::OriginalDst::OriginalDstFilter {
Network::Address::InstanceConstSharedPtr getOriginalDst(int) override {
return Network::Address::InstanceConstSharedPtr{
new Network::Address::Ipv6Instance("1::2", 2345)};
}
};

TEST_F(ListenerManagerImplWithRealFiltersTest, OriginalDstTestFilterIPv6) {
static int fd;
fd = -1;
EXPECT_CALL(*listener_factory_.socket_, fd()).WillOnce(Return(0));

class OriginalDstTestConfigFactory : public Configuration::NamedListenerFilterConfigFactory {
public:
// NamedListenerFilterConfigFactory
Network::ListenerFilterFactoryCb
createFilterFactoryFromProto(const Protobuf::Message&,
Configuration::ListenerFactoryContext& context) override {
auto option = std::make_unique<Network::MockSocketOption>();
EXPECT_CALL(*option, setOption(_, envoy::api::v2::core::SocketOption::STATE_PREBIND))
.WillOnce(Return(true));
EXPECT_CALL(*option, setOption(_, envoy::api::v2::core::SocketOption::STATE_BOUND))
.WillOnce(Invoke(
[](Network::Socket& socket, envoy::api::v2::core::SocketOption::SocketState) -> bool {
fd = socket.fd();
return true;
}));
context.addListenSocketOption(std::move(option));
return [](Network::ListenerFilterManager& filter_manager) -> void {
filter_manager.addAcceptFilter(std::make_unique<OriginalDstTestFilterIPv6>());
};
}

ProtobufTypes::MessagePtr createEmptyConfigProto() override {
return std::make_unique<Envoy::ProtobufWkt::Empty>();
}

std::string name() override { return "test.listener.original_dstipv6"; }
};

/**
* Static registration for the original dst filter. @see RegisterFactory.
*/
static Registry::RegisterFactory<OriginalDstTestConfigFactory,
Configuration::NamedListenerFilterConfigFactory>
registered_;

const std::string yaml = TestEnvironment::substitute(R"EOF(
address:
socket_address: { address: ::0001, port_value: 1111 }
filter_chains: {}
listener_filters:
- name: "test.listener.original_dstipv6"
config: {}
)EOF",
Network::Address::IpVersion::v6);

EXPECT_CALL(server_.random_, uuid());
EXPECT_CALL(listener_factory_, createListenSocket(_, _, true));
manager_->addOrUpdateListener(parseListenerFromV2Yaml(yaml), "", true);
EXPECT_EQ(1U, manager_->listeners().size());

Network::ListenerConfig& listener = manager_->listeners().back().get();

Network::FilterChainFactory& filterChainFactory = listener.filterChainFactory();
Network::MockListenerFilterManager manager;

NiceMock<Network::MockListenerFilterCallbacks> callbacks;
Network::AcceptedSocketImpl socket(
-1, std::make_unique<Network::Address::Ipv6Instance>("::0001", 1234),
std::make_unique<Network::Address::Ipv6Instance>("::0001", 5678));

EXPECT_CALL(callbacks, socket()).WillOnce(Invoke([&]() -> Network::ConnectionSocket& {
return socket;
}));

EXPECT_CALL(manager, addAcceptFilter_(_))
.WillOnce(Invoke([&](Network::ListenerFilterPtr& filter) -> void {
EXPECT_EQ(Network::FilterStatus::Continue, filter->onAccept(callbacks));
}));

EXPECT_TRUE(filterChainFactory.createListenerFilterChain(manager));
EXPECT_TRUE(socket.localAddressRestored());
EXPECT_EQ("[1::2]:2345", socket.localAddress()->asString());
EXPECT_NE(fd, -1);
}

TEST_F(ListenerManagerImplWithRealFiltersTest, OriginalDstTestFilterOptionFailIPv6) {
class OriginalDstTestConfigFactory : public Configuration::NamedListenerFilterConfigFactory {
public:
// NamedListenerFilterConfigFactory
Network::ListenerFilterFactoryCb
createFilterFactoryFromProto(const Protobuf::Message&,
Configuration::ListenerFactoryContext& context) override {
auto option = std::make_unique<Network::MockSocketOption>();
EXPECT_CALL(*option, setOption(_, envoy::api::v2::core::SocketOption::STATE_PREBIND))
.WillOnce(Return(false));
context.addListenSocketOption(std::move(option));
return [](Network::ListenerFilterManager& filter_manager) -> void {
filter_manager.addAcceptFilter(std::make_unique<OriginalDstTestFilterIPv6>());
};
}

ProtobufTypes::MessagePtr createEmptyConfigProto() override {
return std::make_unique<Envoy::ProtobufWkt::Empty>();
}

std::string name() override { return "testfail.listener.original_dstipv6"; }
};

/**
* Static registration for the original dst filter. @see RegisterFactory.
*/
static Registry::RegisterFactory<OriginalDstTestConfigFactory,
Configuration::NamedListenerFilterConfigFactory>
registered_;

const std::string yaml = TestEnvironment::substitute(R"EOF(
name: "socketOptionFailListener"
address:
socket_address: { address: ::0001, port_value: 1111 }
filter_chains: {}
listener_filters:
- name: "testfail.listener.original_dstipv6"
config: {}
)EOF",
Network::Address::IpVersion::v6);

EXPECT_CALL(listener_factory_, createListenSocket(_, _, true));

EXPECT_THROW_WITH_MESSAGE(manager_->addOrUpdateListener(parseListenerFromV2Yaml(yaml), "", true),
EnvoyException,
"MockListenerComponentFactory: Setting socket options failed");
EXPECT_EQ(0U, manager_->listeners().size());
}

// Validate that when neither transparent nor freebind is not set in the
// Listener, we see no socket option set.
TEST_F(ListenerManagerImplWithRealFiltersTest, TransparentFreebindListenerDisabled) {
Expand Down

0 comments on commit 0a43ae8

Please sign in to comment.