diff --git a/envoy/api/BUILD b/envoy/api/BUILD index cbdc13440690c..3ac0872aac617 100644 --- a/envoy/api/BUILD +++ b/envoy/api/BUILD @@ -36,4 +36,8 @@ envoy_cc_library( "os_sys_calls_hot_restart.h", "os_sys_calls_linux.h", ], + external_deps = ["abseil_optional"], + deps = [ + "//envoy/network:address_interface", + ], ) diff --git a/envoy/api/os_sys_calls.h b/envoy/api/os_sys_calls.h index 19a25d3a55eb8..3c9b6f9698ac4 100644 --- a/envoy/api/os_sys_calls.h +++ b/envoy/api/os_sys_calls.h @@ -5,10 +5,14 @@ #include #include #include +#include #include "envoy/api/os_sys_calls_common.h" #include "envoy/common/platform.h" #include "envoy/common/pure.h" +#include "envoy/network/address.h" + +#include "absl/types/optional.h" namespace Envoy { namespace Api { @@ -17,6 +21,23 @@ struct EnvoyTcpInfo { std::chrono::microseconds tcpi_rtt; }; +// Small struct to avoid exposing ifaddrs -- which is not defined in all platforms -- to the +// codebase. +struct InterfaceAddress { + InterfaceAddress(absl::string_view interface_name, unsigned int interface_flags, + Envoy::Network::Address::InstanceConstSharedPtr interface_addr) + : interface_name_(interface_name), interface_flags_(interface_flags), + interface_addr_(interface_addr) {} + + std::string interface_name_; + unsigned int interface_flags_; + Envoy::Network::Address::InstanceConstSharedPtr interface_addr_; +}; + +using InterfaceAddressVector = std::vector; + +using AlternateGetifaddrs = std::function; + class OsSysCalls { public: virtual ~OsSysCalls() = default; @@ -195,6 +216,29 @@ class OsSysCalls { * @see man TCP_INFO. Get the tcp info for the socket. */ virtual SysCallBoolResult socketTcpInfo(os_fd_t sockfd, EnvoyTcpInfo* tcp_info) PURE; + + /** + * return true if the OS supports getifaddrs. + */ + virtual bool supportsGetifaddrs() const PURE; + + /** + * @see man getifaddrs + */ + virtual SysCallIntResult getifaddrs(InterfaceAddressVector& interfaces) PURE; + + /** + * allows a platform to override getifaddrs or provide an implementation if one does not exist + * natively. + * + * @arg alternate_getifaddrs function pointer to implementation. + */ + virtual void setAlternateGetifaddrs(AlternateGetifaddrs alternate_getifaddrs) { + alternate_getifaddrs_ = alternate_getifaddrs; + } + +protected: + absl::optional alternate_getifaddrs_{}; }; using OsSysCallsPtr = std::unique_ptr; diff --git a/envoy/common/platform.h b/envoy/common/platform.h index cefe0da63a31e..ed9a8f74d021a 100644 --- a/envoy/common/platform.h +++ b/envoy/common/platform.h @@ -299,18 +299,6 @@ struct mmsghdr { }; #endif -#define SUPPORTS_GETIFADDRS -#ifdef WIN32 -#undef SUPPORTS_GETIFADDRS -#endif - -// https://android.googlesource.com/platform/prebuilts/ndk/+/dev/platform/sysroot/usr/include/ifaddrs.h -#ifdef __ANDROID_API__ -#if __ANDROID_API__ < 24 -#undef SUPPORTS_GETIFADDRS -#endif // __ANDROID_API__ < 24 -#endif // ifdef __ANDROID_API__ - // TODO: Remove once bazel supports NDKs > 21 #define SUPPORTS_CPP_17_CONTIGUOUS_ITERATOR #ifdef __ANDROID_API__ diff --git a/envoy/network/BUILD b/envoy/network/BUILD index d454518fbdbf4..77c31f791aa81 100644 --- a/envoy/network/BUILD +++ b/envoy/network/BUILD @@ -11,9 +11,6 @@ envoy_package() envoy_cc_library( name = "address_interface", hdrs = ["address.h"], - deps = [ - "//envoy/api:os_sys_calls_interface", - ], ) envoy_cc_library( diff --git a/envoy/network/address.h b/envoy/network/address.h index bd28205bd6305..2b50b0728168e 100644 --- a/envoy/network/address.h +++ b/envoy/network/address.h @@ -7,7 +7,6 @@ #include #include -#include "envoy/api/os_sys_calls.h" #include "envoy/common/platform.h" #include "envoy/common/pure.h" diff --git a/envoy/network/io_handle.h b/envoy/network/io_handle.h index a960887a56a1c..12a45cf0c0d3b 100644 --- a/envoy/network/io_handle.h +++ b/envoy/network/io_handle.h @@ -4,6 +4,7 @@ #include #include "envoy/api/io_error.h" +#include "envoy/api/os_sys_calls_common.h" #include "envoy/common/platform.h" #include "envoy/common/pure.h" #include "envoy/event/file_event.h" diff --git a/source/common/api/BUILD b/source/common/api/BUILD index 07443785ab89f..7ed8334ae8823 100644 --- a/source/common/api/BUILD +++ b/source/common/api/BUILD @@ -48,6 +48,7 @@ envoy_cc_library( }), deps = [ "//envoy/api:os_sys_calls_interface", + "//source/common/network:address_lib", "//source/common/singleton:threadsafe_singleton", ], ) diff --git a/source/common/api/posix/os_sys_calls_impl.cc b/source/common/api/posix/os_sys_calls_impl.cc index d4c05f77123b5..5411580a212b8 100644 --- a/source/common/api/posix/os_sys_calls_impl.cc +++ b/source/common/api/posix/os_sys_calls_impl.cc @@ -6,6 +6,7 @@ #include #include "source/common/api/os_sys_calls_impl.h" +#include "source/common/network/address_impl.h" namespace Envoy { namespace Api { @@ -296,5 +297,68 @@ SysCallBoolResult OsSysCallsImpl::socketTcpInfo([[maybe_unused]] os_fd_t sockfd, return {false, EOPNOTSUPP}; } +bool OsSysCallsImpl::supportsGetifaddrs() const { +// TODO: eliminate this branching by upstreaming an alternative Android implementation +// e.g.: https://github.com/envoyproxy/envoy-mobile/blob/main/third_party/android/ifaddrs-android.h +#if defined(__ANDROID_API__) && __ANDROID_API__ < 24 + if (alternate_getifaddrs_.has_value()) { + return true; + } + return false; +#else + // Note: posix defaults to true regardless of whether an alternate getifaddrs has been set or not. + // This is because as far as we are aware only Android<24 lacks an implementation and thus another + // posix based platform that lacks a native getifaddrs implementation should be a programming + // error. + // + // That being said, if an alternate getifaddrs impl is set, that will be used in calls to + // OsSysCallsImpl::getifaddrs as seen below. + return true; +#endif +} + +SysCallIntResult OsSysCallsImpl::getifaddrs([[maybe_unused]] InterfaceAddressVector& interfaces) { + if (alternate_getifaddrs_.has_value()) { + return alternate_getifaddrs_.value()(interfaces); + } + +// TODO: eliminate this branching by upstreaming an alternative Android implementation +// e.g.: https://github.com/envoyproxy/envoy-mobile/blob/main/third_party/android/ifaddrs-android.h +#if defined(__ANDROID_API__) && __ANDROID_API__ < 24 + NOT_IMPLEMENTED_GCOVR_EXCL_LINE; +#else + struct ifaddrs* ifaddr; + struct ifaddrs* ifa; + + const int rc = ::getifaddrs(&ifaddr); + if (rc == -1) { + return {rc, errno}; + } + + for (ifa = ifaddr; ifa != nullptr; ifa = ifa->ifa_next) { + if (ifa->ifa_addr == nullptr) { + continue; + } + + if (ifa->ifa_addr->sa_family == AF_INET || ifa->ifa_addr->sa_family == AF_INET6) { + const sockaddr_storage* ss = reinterpret_cast(ifa->ifa_addr); + size_t ss_len = + ifa->ifa_addr->sa_family == AF_INET ? sizeof(sockaddr_in) : sizeof(sockaddr_in6); + StatusOr address = + Network::Address::addressFromSockAddr(*ss, ss_len, ifa->ifa_addr->sa_family == AF_INET6); + if (address.ok()) { + interfaces.emplace_back(ifa->ifa_name, ifa->ifa_flags, *address); + } + } + } + + if (ifaddr) { + ::freeifaddrs(ifaddr); + } + + return {rc, 0}; +#endif +} + } // namespace Api } // namespace Envoy diff --git a/source/common/api/posix/os_sys_calls_impl.h b/source/common/api/posix/os_sys_calls_impl.h index f03bcc24bc7d9..20b9adf56ef24 100644 --- a/source/common/api/posix/os_sys_calls_impl.h +++ b/source/common/api/posix/os_sys_calls_impl.h @@ -50,6 +50,8 @@ class OsSysCallsImpl : public OsSysCalls { SysCallSocketResult duplicate(os_fd_t oldfd) override; SysCallSocketResult accept(os_fd_t socket, sockaddr* addr, socklen_t* addrlen) override; SysCallBoolResult socketTcpInfo(os_fd_t sockfd, EnvoyTcpInfo* tcp_info) override; + bool supportsGetifaddrs() const override; + SysCallIntResult getifaddrs(InterfaceAddressVector& interfaces) override; }; using OsSysCallsSingleton = ThreadSafeSingleton; diff --git a/source/common/api/win32/os_sys_calls_impl.cc b/source/common/api/win32/os_sys_calls_impl.cc index 0ca257243f42e..5d172f88e6c05 100644 --- a/source/common/api/win32/os_sys_calls_impl.cc +++ b/source/common/api/win32/os_sys_calls_impl.cc @@ -409,5 +409,19 @@ SysCallBoolResult OsSysCallsImpl::socketTcpInfo([[maybe_unused]] os_fd_t sockfd, return {false, WSAEOPNOTSUPP}; } +bool OsSysCallsImpl::supportsGetifaddrs() const { + if (alternate_getifaddrs_.has_value()) { + return true; + } + return false; +} + +SysCallIntResult OsSysCallsImpl::getifaddrs([[maybe_unused]] InterfaceAddressVector& interfaces) { + if (alternate_getifaddrs_.has_value()) { + return alternate_getifaddrs_.value()(interfaces); + } + NOT_IMPLEMENTED_GCOVR_EXCL_LINE; +} + } // namespace Api } // namespace Envoy diff --git a/source/common/api/win32/os_sys_calls_impl.h b/source/common/api/win32/os_sys_calls_impl.h index 7b087be9533dc..5268643f47e26 100644 --- a/source/common/api/win32/os_sys_calls_impl.h +++ b/source/common/api/win32/os_sys_calls_impl.h @@ -52,6 +52,8 @@ class OsSysCallsImpl : public OsSysCalls { SysCallSocketResult duplicate(os_fd_t oldfd) override; SysCallSocketResult accept(os_fd_t socket, sockaddr* addr, socklen_t* addrlen) override; SysCallBoolResult socketTcpInfo(os_fd_t sockfd, EnvoyTcpInfo* tcp_info) override; + bool supportsGetifaddrs() const override; + SysCallIntResult getifaddrs(InterfaceAddressVector&) override; }; using OsSysCallsSingleton = ThreadSafeSingleton; diff --git a/source/common/network/BUILD b/source/common/network/BUILD index 98af3afd269a2..3abbf23fe204b 100644 --- a/source/common/network/BUILD +++ b/source/common/network/BUILD @@ -15,7 +15,6 @@ envoy_cc_library( deps = [ ":socket_interface_lib", "//envoy/network:address_interface", - "//source/common/api:os_sys_calls_lib", "//source/common/common:assert_lib", "//source/common/common:safe_memcpy_lib", "//source/common/common:statusor_lib", diff --git a/source/common/network/utility.cc b/source/common/network/utility.cc index 7cd7b19c00be4..965f3f0051e1d 100644 --- a/source/common/network/utility.cc +++ b/source/common/network/utility.cc @@ -239,36 +239,22 @@ void Utility::throwWithMalformedIp(absl::string_view ip_address) { // need to be updated in the future. Discussion can be found at Github issue #939. Address::InstanceConstSharedPtr Utility::getLocalAddress(const Address::IpVersion version) { Address::InstanceConstSharedPtr ret; -#ifdef SUPPORTS_GETIFADDRS - struct ifaddrs* ifaddr; - struct ifaddrs* ifa; + if (Api::OsSysCallsSingleton::get().supportsGetifaddrs()) { + Api::InterfaceAddressVector interface_addresses{}; - const int rc = getifaddrs(&ifaddr); - RELEASE_ASSERT(!rc, ""); + const Api::SysCallIntResult rc = + Api::OsSysCallsSingleton::get().getifaddrs(interface_addresses); + RELEASE_ASSERT(!rc.return_value_, fmt::format("getiffaddrs error: {}", rc.errno_)); - // man getifaddrs(3) - for (ifa = ifaddr; ifa != nullptr; ifa = ifa->ifa_next) { - if (ifa->ifa_addr == nullptr) { - continue; - } - - if ((ifa->ifa_addr->sa_family == AF_INET && version == Address::IpVersion::v4) || - (ifa->ifa_addr->sa_family == AF_INET6 && version == Address::IpVersion::v6)) { - const struct sockaddr_storage* addr = - reinterpret_cast(ifa->ifa_addr); - ret = Address::addressFromSockAddrOrThrow( - *addr, (version == Address::IpVersion::v4) ? sizeof(sockaddr_in) : sizeof(sockaddr_in6)); - if (!isLoopbackAddress(*ret)) { + // man getifaddrs(3) + for (const auto& interface_address : interface_addresses) { + if (!isLoopbackAddress(*interface_address.interface_addr_)) { + ret = interface_address.interface_addr_; break; } } } - if (ifaddr) { - freeifaddrs(ifaddr); - } -#endif - // If the local address is not found above, then return the loopback address by default. if (ret == nullptr) { if (version == Address::IpVersion::v4) { diff --git a/test/common/api/BUILD b/test/common/api/BUILD new file mode 100644 index 0000000000000..d8d514ba3a41c --- /dev/null +++ b/test/common/api/BUILD @@ -0,0 +1,17 @@ +load( + "//bazel:envoy_build_system.bzl", + "envoy_cc_test", + "envoy_package", +) + +licenses(["notice"]) # Apache 2 + +envoy_package() + +envoy_cc_test( + name = "os_sys_calls_test", + srcs = ["os_sys_calls_test.cc"], + deps = [ + "//source/common/api:os_sys_calls_lib", + ], +) diff --git a/test/common/api/os_sys_calls_test.cc b/test/common/api/os_sys_calls_test.cc new file mode 100644 index 0000000000000..5748b257ac791 --- /dev/null +++ b/test/common/api/os_sys_calls_test.cc @@ -0,0 +1,34 @@ +#include "source/common/api/os_sys_calls_impl.h" + +#include "gtest/gtest.h" + +namespace Envoy { + +TEST(OsSyscallsTest, SetAlternateGetifaddrs) { + auto& os_syscalls = Api::OsSysCallsSingleton::get(); + const bool pre_alternate_support = os_syscalls.supportsGetifaddrs(); + Api::InterfaceAddressVector interfaces{}; +#if defined(WIN32) || (defined(__ANDROID_API__) && __ANDROID_API__ < 24) + EXPECT_FALSE(pre_alternate_support); + EXPECT_DEATH(os_syscalls.getifaddrs(interfaces), "not implemented"); +#else + EXPECT_TRUE(pre_alternate_support); + const auto pre_alternate_rc = os_syscalls.getifaddrs(interfaces); + EXPECT_EQ(0, pre_alternate_rc.return_value_); + EXPECT_FALSE(interfaces.empty()); +#endif + + os_syscalls.setAlternateGetifaddrs( + [](Api::InterfaceAddressVector& interfaces) -> Api::SysCallIntResult { + interfaces.emplace_back("made_up_if", 0, nullptr); + return {0, 0}; + }); + interfaces.clear(); + + const bool post_alternate_support = os_syscalls.supportsGetifaddrs(); + EXPECT_TRUE(post_alternate_support); + EXPECT_EQ(0, os_syscalls.getifaddrs(interfaces).return_value_); + EXPECT_EQ(1, interfaces.size()); + EXPECT_EQ("made_up_if", interfaces.front().interface_name_); +} +} // namespace Envoy diff --git a/test/common/http/conn_pool_grid_test.cc b/test/common/http/conn_pool_grid_test.cc index 69528416002db..b0d722cac83fd 100644 --- a/test/common/http/conn_pool_grid_test.cc +++ b/test/common/http/conn_pool_grid_test.cc @@ -714,8 +714,24 @@ TEST_F(ConnectivityGridTest, ConnectionCloseDuringCreation) { ASSERT_TRUE(optional_it1.has_value()); EXPECT_EQ("HTTP/3", (**optional_it1)->protocolDescription()); + const bool supports_getifaddrs = Api::OsSysCallsSingleton::get().supportsGetifaddrs(); + Api::InterfaceAddressVector interfaces{}; + if (supports_getifaddrs) { + ASSERT_EQ(0, Api::OsSysCallsSingleton::get().getifaddrs(interfaces).return_value_); + } + Api::MockOsSysCalls os_sys_calls; TestThreadsafeSingletonInjector os_calls(&os_sys_calls); + EXPECT_CALL(os_sys_calls, supportsGetifaddrs()).WillOnce(Return(supports_getifaddrs)); + if (supports_getifaddrs) { + EXPECT_CALL(os_sys_calls, getifaddrs(_)) + .WillOnce( + Invoke([&](Api::InterfaceAddressVector& interface_vector) -> Api::SysCallIntResult { + interface_vector.insert(interface_vector.begin(), interfaces.begin(), + interfaces.end()); + return {0, 0}; + })); + } EXPECT_CALL(os_sys_calls, socket(_, _, _)).WillOnce(Return(Api::SysCallSocketResult{1, 0})); #if defined(__APPLE__) || defined(WIN32) EXPECT_CALL(os_sys_calls, setsocketblocking(1, false)) diff --git a/test/mocks/api/mocks.h b/test/mocks/api/mocks.h index 1df4c36dcabcb..5736dbd28e694 100644 --- a/test/mocks/api/mocks.h +++ b/test/mocks/api/mocks.h @@ -111,6 +111,9 @@ class MockOsSysCalls : public OsSysCallsImpl { MOCK_METHOD(bool, supportsUdpGro, (), (const)); MOCK_METHOD(bool, supportsIpTransparent, (), (const)); MOCK_METHOD(bool, supportsMptcp, (), (const)); + MOCK_METHOD(bool, supportsGetifaddrs, (), (const)); + MOCK_METHOD(void, setAlternateGetifaddrs, (AlternateGetifaddrs alternate_getifaddrs)); + MOCK_METHOD(SysCallIntResult, getifaddrs, (InterfaceAddressVector & interfaces)); // Map from (sockfd,level,optname) to boolean socket option. using SockOptKey = std::tuple; diff --git a/tools/spelling/spelling_dictionary.txt b/tools/spelling/spelling_dictionary.txt index b0da4a180dc89..e4665af315d63 100644 --- a/tools/spelling/spelling_dictionary.txt +++ b/tools/spelling/spelling_dictionary.txt @@ -11,6 +11,7 @@ ALS AMZ APC API +ARRAYSIZE ARN ASAN ASCII @@ -820,6 +821,7 @@ megamiss mem memcmp memcpy +memset memoize mergeable messagename