diff --git a/contrib/vcl/source/vcl_io_handle.h b/contrib/vcl/source/vcl_io_handle.h index dd1e69690112b..f878c3647fdf1 100644 --- a/contrib/vcl/source/vcl_io_handle.h +++ b/contrib/vcl/source/vcl_io_handle.h @@ -72,6 +72,8 @@ class VclIoHandle : public Envoy::Network::IoHandle, void resetFileEvents() override; IoHandlePtr duplicate() override; + absl::optional interfaceName() override { return absl::nullopt; } + void cb(uint32_t events) { cb_(events); } void setCb(Event::FileReadyCb cb) { cb_ = cb; } void updateEvents(uint32_t events); diff --git a/envoy/network/io_handle.h b/envoy/network/io_handle.h index 12a45cf0c0d3b..87a53a7709c36 100644 --- a/envoy/network/io_handle.h +++ b/envoy/network/io_handle.h @@ -326,6 +326,11 @@ class IoHandle { * returned. */ virtual absl::optional lastRoundTripTime() PURE; + + /** + * @return the interface name for the socket, if the OS supports it. Otherwise, absl::nullopt. + */ + virtual absl::optional interfaceName() PURE; }; using IoHandlePtr = std::unique_ptr; diff --git a/source/common/network/io_socket_handle_impl.cc b/source/common/network/io_socket_handle_impl.cc index 020bd4b55c474..c40e9f03d77e0 100644 --- a/source/common/network/io_socket_handle_impl.cc +++ b/source/common/network/io_socket_handle_impl.cc @@ -576,5 +576,54 @@ absl::optional IoSocketHandleImpl::lastRoundTripTime( return std::chrono::duration_cast(info.tcpi_rtt); } +absl::optional IoSocketHandleImpl::interfaceName() { + auto& os_syscalls_singleton = Api::OsSysCallsSingleton::get(); + if (!os_syscalls_singleton.supportsGetifaddrs()) { + return absl::nullopt; + } + + Address::InstanceConstSharedPtr socket_address = localAddress(); + if (!socket_address || socket_address->type() != Address::Type::Ip) { + return absl::nullopt; + } + + Api::InterfaceAddressVector interface_addresses{}; + const Api::SysCallIntResult rc = os_syscalls_singleton.getifaddrs(interface_addresses); + RELEASE_ASSERT(!rc.return_value_, fmt::format("getiffaddrs error: {}", rc.errno_)); + + absl::optional selected_interface_name{}; + for (const auto& interface_address : interface_addresses) { + if (!interface_address.interface_addr_) { + continue; + } + + if (socket_address->ip()->version() == interface_address.interface_addr_->ip()->version()) { + // Compare address _without port_. + // TODO: create common addressAsStringWithoutPort method to simplify code here. + absl::uint128 socket_address_value; + absl::uint128 interface_address_value; + switch (socket_address->ip()->version()) { + case Address::IpVersion::v4: + socket_address_value = socket_address->ip()->ipv4()->address(); + interface_address_value = interface_address.interface_addr_->ip()->ipv4()->address(); + break; + case Address::IpVersion::v6: + socket_address_value = socket_address->ip()->ipv6()->address(); + interface_address_value = interface_address.interface_addr_->ip()->ipv6()->address(); + break; + default: + ENVOY_BUG(false, fmt::format("unexpected IP family {}", socket_address->ip()->version())); + } + + if (socket_address_value == interface_address_value) { + selected_interface_name = interface_address.interface_name_; + break; + } + } + } + + return selected_interface_name; +} + } // namespace Network } // namespace Envoy diff --git a/source/common/network/io_socket_handle_impl.h b/source/common/network/io_socket_handle_impl.h index ae4129de73e5a..fe57fff4073a9 100644 --- a/source/common/network/io_socket_handle_impl.h +++ b/source/common/network/io_socket_handle_impl.h @@ -80,6 +80,7 @@ class IoSocketHandleImpl : public IoHandle, protected Logger::Loggable lastRoundTripTime() override; + absl::optional interfaceName() override; protected: // Converts a SysCallSizeResult to IoCallUint64Result. diff --git a/source/common/quic/quic_io_handle_wrapper.h b/source/common/quic/quic_io_handle_wrapper.h index 15247bb4184c6..db9087daff10e 100644 --- a/source/common/quic/quic_io_handle_wrapper.h +++ b/source/common/quic/quic_io_handle_wrapper.h @@ -138,6 +138,7 @@ class QuicIoHandleWrapper : public Network::IoHandle { void activateFileEvents(uint32_t events) override { io_handle_.activateFileEvents(events); } void enableFileEvents(uint32_t events) override { io_handle_.enableFileEvents(events); } void resetFileEvents() override { return io_handle_.resetFileEvents(); }; + absl::optional interfaceName() override { return io_handle_.interfaceName(); } Api::SysCallIntResult shutdown(int how) override { return io_handle_.shutdown(how); } absl::optional lastRoundTripTime() override { return {}; } diff --git a/source/extensions/io_socket/user_space/io_handle_impl.h b/source/extensions/io_socket/user_space/io_handle_impl.h index 71d2161be17f3..9f7ab9010b8b4 100644 --- a/source/extensions/io_socket/user_space/io_handle_impl.h +++ b/source/extensions/io_socket/user_space/io_handle_impl.h @@ -88,6 +88,7 @@ class IoHandleImpl final : public Network::IoHandle, Api::SysCallIntResult shutdown(int how) override; absl::optional lastRoundTripTime() override { return absl::nullopt; } + absl::optional interfaceName() override { return absl::nullopt; } void setWatermarks(uint32_t watermark) { pending_received_data_.setWatermarks(watermark); } void onBelowLowWatermark() { diff --git a/test/common/network/BUILD b/test/common/network/BUILD index 34fcb5c4a8536..f2390df3ceced 100644 --- a/test/common/network/BUILD +++ b/test/common/network/BUILD @@ -404,6 +404,7 @@ envoy_cc_test( "//source/common/network:address_lib", "//test/mocks/api:api_mocks", "//test/test_common:threadsafe_singleton_injector_lib", + "//test/test_common:utility_lib", ], ) diff --git a/test/common/network/io_socket_handle_impl_test.cc b/test/common/network/io_socket_handle_impl_test.cc index 6ce655b63029a..fa44a6efa53a8 100644 --- a/test/common/network/io_socket_handle_impl_test.cc +++ b/test/common/network/io_socket_handle_impl_test.cc @@ -8,6 +8,7 @@ #include "test/test_common/environment.h" #include "test/test_common/network_utility.h" #include "test/test_common/threadsafe_singleton_injector.h" +#include "test/test_common/utility.h" #include "gmock/gmock.h" #include "gtest/gtest.h" @@ -23,7 +24,7 @@ namespace Envoy { namespace Network { namespace { -TEST(IoSocketHandleImplTest, TestIoSocketError) { +TEST(IoSocketHandleImpl, TestIoSocketError) { EXPECT_DEBUG_DEATH(IoSocketError(SOCKET_ERROR_AGAIN), ".*assert failure: .* Details: Didn't use getIoSocketEagainInstance.*"); EXPECT_EQ(errorDetails(SOCKET_ERROR_AGAIN), @@ -98,6 +99,80 @@ TEST(IoSocketHandleImpl, LastRoundTripTimeReturnsRttIfSuccessful) { EXPECT_THAT(io_handle.lastRoundTripTime(), Eq(std::chrono::duration_cast(rtt))); } + +TEST(IoSocketHandleImpl, InterfaceNameWithPipe) { + std::string path = TestEnvironment::unixDomainSocketPath("foo.sock"); + + const mode_t mode = 0777; + Address::PipeInstance pipe(path, mode); + Address::InstanceConstSharedPtr address = std::make_shared(pipe); + SocketImpl socket(Socket::Type::Stream, address, nullptr, {}); + + EXPECT_TRUE(socket.ioHandle().isOpen()) << pipe.asString(); + + Api::SysCallIntResult result = socket.bind(address); + ASSERT_EQ(result.return_value_, 0); + + EXPECT_FALSE(socket.ioHandle().interfaceName().has_value()); +} + +TEST(IoSocketHandleImpl, ExplicitDoesNotSupportGetifaddrs) { + + auto socket = std::make_shared( + Network::Test::getCanonicalLoopbackAddress(Address::IpVersion::v4)); + + NiceMock os_sys_calls; + TestThreadsafeSingletonInjector os_calls(&os_sys_calls); + + EXPECT_CALL(os_sys_calls, supportsGetifaddrs()).WillOnce(Return(false)); + const auto maybe_interface_name = socket->ioHandle().interfaceName(); + EXPECT_FALSE(maybe_interface_name.has_value()); +} + +TEST(IoSocketHandleImpl, NullptrIfaddrs) { + auto& os_syscalls_singleton = Api::OsSysCallsSingleton::get(); + auto socket = std::make_shared( + Network::Test::getCanonicalLoopbackAddress(Address::IpVersion::v4)); + + NiceMock os_sys_calls; + TestThreadsafeSingletonInjector os_calls(&os_sys_calls); + + EXPECT_CALL(os_sys_calls, supportsGetifaddrs()).WillRepeatedly(Return(true)); + EXPECT_CALL(os_sys_calls, getsockname(_, _, _)) + .WillOnce( + Invoke([&](os_fd_t sockfd, sockaddr* addr, socklen_t* addrlen) -> Api::SysCallIntResult { + os_syscalls_singleton.getsockname(sockfd, addr, addrlen); + return {0, 0}; + })); + EXPECT_CALL(os_sys_calls, getifaddrs(_)) + .WillOnce(Invoke([&](Api::InterfaceAddressVector&) -> Api::SysCallIntResult { + return {0, 0}; + })); + + const auto maybe_interface_name = socket->ioHandle().interfaceName(); + EXPECT_FALSE(maybe_interface_name.has_value()); +} + +class IoSocketHandleImplTest : public testing::TestWithParam {}; +INSTANTIATE_TEST_SUITE_P(IpVersions, IoSocketHandleImplTest, + testing::ValuesIn({Network::Address::IpVersion::v4, + Network::Address::IpVersion::v6}), + TestUtility::ipTestParamsToString); + +TEST_P(IoSocketHandleImplTest, InterfaceNameForLoopback) { + auto socket = std::make_shared( + Network::Test::getCanonicalLoopbackAddress(GetParam())); + + const auto maybe_interface_name = socket->ioHandle().interfaceName(); + + if (Api::OsSysCallsSingleton::get().supportsGetifaddrs()) { + EXPECT_TRUE(maybe_interface_name.has_value()); + EXPECT_TRUE(absl::StrContains(maybe_interface_name.value(), "lo")); + } else { + EXPECT_FALSE(maybe_interface_name.has_value()); + } +} + } // namespace } // namespace Network } // namespace Envoy diff --git a/test/extensions/io_socket/user_space/io_handle_impl_test.cc b/test/extensions/io_socket/user_space/io_handle_impl_test.cc index ab4303dd0e62b..da461d72e4d9d 100644 --- a/test/extensions/io_socket/user_space/io_handle_impl_test.cc +++ b/test/extensions/io_socket/user_space/io_handle_impl_test.cc @@ -57,6 +57,8 @@ class IoHandleImplTest : public testing::Test { absl::FixedArray buf_; }; +TEST_F(IoHandleImplTest, InterfaceName) { ASSERT_FALSE(io_handle_->interfaceName().has_value()); } + // Test recv side effects. TEST_F(IoHandleImplTest, BasicRecv) { Buffer::OwnedImpl buf_to_write("0123456789"); diff --git a/test/mocks/network/io_handle.h b/test/mocks/network/io_handle.h index 3c661d836b05f..1ccb724d53fec 100644 --- a/test/mocks/network/io_handle.h +++ b/test/mocks/network/io_handle.h @@ -63,6 +63,7 @@ class MockIoHandle : public IoHandle { MOCK_METHOD(absl::optional, lastRoundTripTime, ()); MOCK_METHOD(Api::SysCallIntResult, ioctl, (unsigned long, void*, unsigned long, void*, unsigned long, unsigned long*)); + MOCK_METHOD(absl::optional, interfaceName, ()); }; } // namespace Network