diff --git a/source/common/network/BUILD b/source/common/network/BUILD index 04354a75e6c86..8fcb9d7793752 100644 --- a/source/common/network/BUILD +++ b/source/common/network/BUILD @@ -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", diff --git a/source/common/network/utility.cc b/source/common/network/utility.cc index a887af86ea951..c0e40917ed362 100644 --- a/source/common/network/utility.cc +++ b/source/common/network/utility.cc @@ -7,6 +7,11 @@ #include #endif +#ifndef IP6T_SO_ORIGINAL_DST +// From linux/netfilter_ipv6/ip6_tables.h +#define IP6T_SO_ORIGINAL_DST 80 +#endif + #include #include @@ -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" @@ -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(&orig_addr))}; - } else { + case AF_INET6: + return Address::InstanceConstSharedPtr{ + new Address::Ipv6Instance(reinterpret_cast(orig_addr))}; + default: return nullptr; } #else diff --git a/test/server/listener_manager_impl_test.cc b/test/server/listener_manager_impl_test.cc index 94698c3d76d3d..956b8f288450b 100644 --- a/test/server/listener_manager_impl_test.cc +++ b/test/server/listener_manager_impl_test.cc @@ -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(); + 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()); + }; + } + + ProtobufTypes::MessagePtr createEmptyConfigProto() override { + return std::make_unique(); + } + + std::string name() override { return "test.listener.original_dstipv6"; } + }; + + /** + * Static registration for the original dst filter. @see RegisterFactory. + */ + static Registry::RegisterFactory + 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 callbacks; + Network::AcceptedSocketImpl socket( + -1, std::make_unique("::0001", 1234), + std::make_unique("::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(); + 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()); + }; + } + + ProtobufTypes::MessagePtr createEmptyConfigProto() override { + return std::make_unique(); + } + + std::string name() override { return "testfail.listener.original_dstipv6"; } + }; + + /** + * Static registration for the original dst filter. @see RegisterFactory. + */ + static Registry::RegisterFactory + 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) {