From da2fd2a2b25ee4bd7b383424cb26d51fb6cc7716 Mon Sep 17 00:00:00 2001 From: Jonas Oreland Date: Wed, 10 Mar 2021 15:33:31 +0100 Subject: [PATCH] Fix problem with ipv4 over ipv6 on Android This patch fixes a problem with using ipv4 over ipv6 addresses on Android. These addresses are discovered using 'getifaddr' with interfaces called 'v4-wlan0' or 'v4-rmnet' but the Android API does not report them. This leads to failure when BasicPortAllocator tries to bind a socket to the ip-address, making the ipv4 address unusable. This solution does the following 1) Insert BasicNetworkManager as NetworkBinderInterface rather than AndroidNetworkManager. 2) When SocketServer calls BindSocketToNetwork, BasicNetworkManager first lookup the interface name, and then calls AndroidNetworkManager. 3) AndroidNetworkManager will then first try to bind using the known ip-addresses, and if it can't find the network it will instead match the interface names. The patch has been tested on real android devices, and works fine. And everything is disabled by default, and is enabled by field trial. My plan is to rollout the feature, checking that it does not introduce any problems, and if so, enabled for all. Bug: webrtc:10707 Change-Id: I7081ba43d4ce17077acfa5fbab44eda127ac3971 Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/211003 Commit-Queue: Jonas Oreland Reviewed-by: Harald Alvestrand Cr-Commit-Position: refs/heads/master@{#33422} --- rtc_base/network.cc | 41 ++++++++-- rtc_base/network.h | 17 +++- rtc_base/network_monitor.h | 15 ++++ rtc_base/network_unittest.cc | 70 ++++++++++++++++ .../android_network_monitor_unittest.cc | 38 +++++++-- .../src/jni/android_network_monitor.cc | 79 ++++++++++++++----- sdk/android/src/jni/android_network_monitor.h | 19 +++-- 7 files changed, 242 insertions(+), 37 deletions(-) diff --git a/rtc_base/network.cc b/rtc_base/network.cc index 07c39ae5c1..1b0ba367e6 100644 --- a/rtc_base/network.cc +++ b/rtc_base/network.cc @@ -212,7 +212,8 @@ AdapterType GetAdapterTypeFromName(const char* network_name) { return ADAPTER_TYPE_ETHERNET; } - if (MatchTypeNameWithIndexPattern(network_name, "wlan")) { + if (MatchTypeNameWithIndexPattern(network_name, "wlan") || + MatchTypeNameWithIndexPattern(network_name, "v4-wlan")) { return ADAPTER_TYPE_WIFI; } @@ -478,15 +479,15 @@ Network* NetworkManagerBase::GetNetworkFromAddress( return nullptr; } -BasicNetworkManager::BasicNetworkManager() - : allow_mac_based_ipv6_( - webrtc::field_trial::IsEnabled("WebRTC-AllowMACBasedIPv6")) {} +BasicNetworkManager::BasicNetworkManager() : BasicNetworkManager(nullptr) {} BasicNetworkManager::BasicNetworkManager( NetworkMonitorFactory* network_monitor_factory) : network_monitor_factory_(network_monitor_factory), allow_mac_based_ipv6_( - webrtc::field_trial::IsEnabled("WebRTC-AllowMACBasedIPv6")) {} + webrtc::field_trial::IsEnabled("WebRTC-AllowMACBasedIPv6")), + bind_using_ifname_( + webrtc::field_trial::IsEnabled("WebRTC-BindUsingInterfaceName")) {} BasicNetworkManager::~BasicNetworkManager() {} @@ -865,6 +866,15 @@ void BasicNetworkManager::StartNetworkMonitor() { network_monitor_->SignalNetworksChanged.connect( this, &BasicNetworkManager::OnNetworksChanged); } + + if (network_monitor_->SupportsBindSocketToNetwork()) { + // Set NetworkBinder on SocketServer so that + // PhysicalSocket::Bind will call + // BasicNetworkManager::BindSocketToNetwork(), (that will lookup interface + // name and then call network_monitor_->BindSocketToNetwork()). + thread_->socketserver()->set_network_binder(this); + } + network_monitor_->Start(); } @@ -873,6 +883,13 @@ void BasicNetworkManager::StopNetworkMonitor() { return; } network_monitor_->Stop(); + + if (network_monitor_->SupportsBindSocketToNetwork()) { + // Reset NetworkBinder on SocketServer. + if (thread_->socketserver()->network_binder() == this) { + thread_->socketserver()->set_network_binder(nullptr); + } + } } void BasicNetworkManager::OnMessage(Message* msg) { @@ -954,6 +971,20 @@ void BasicNetworkManager::DumpNetworks() { } } +NetworkBindingResult BasicNetworkManager::BindSocketToNetwork( + int socket_fd, + const IPAddress& address) { + RTC_DCHECK_RUN_ON(thread_); + std::string if_name; + if (bind_using_ifname_) { + Network* net = GetNetworkFromAddress(address); + if (net != nullptr) { + if_name = net->name(); + } + } + return network_monitor_->BindSocketToNetwork(socket_fd, address, if_name); +} + Network::Network(const std::string& name, const std::string& desc, const IPAddress& prefix, diff --git a/rtc_base/network.h b/rtc_base/network.h index d2e3bc22a7..8b6b6235fa 100644 --- a/rtc_base/network.h +++ b/rtc_base/network.h @@ -194,11 +194,11 @@ class RTC_EXPORT NetworkManagerBase : public NetworkManager { void set_default_local_addresses(const IPAddress& ipv4, const IPAddress& ipv6); + Network* GetNetworkFromAddress(const rtc::IPAddress& ip) const; + private: friend class NetworkTest; - Network* GetNetworkFromAddress(const rtc::IPAddress& ip) const; - EnumerationPermission enumeration_permission_; NetworkList networks_; @@ -225,6 +225,7 @@ class RTC_EXPORT NetworkManagerBase : public NetworkManager { // of networks using OS APIs. class RTC_EXPORT BasicNetworkManager : public NetworkManagerBase, public MessageHandlerAutoCleanup, + public NetworkBinderInterface, public sigslot::has_slots<> { public: BasicNetworkManager(); @@ -248,6 +249,15 @@ class RTC_EXPORT BasicNetworkManager : public NetworkManagerBase, network_ignore_list_ = list; } + // Bind a socket to interface that ip address belong to. + // Implementation look up interface name and calls + // BindSocketToNetwork on NetworkMonitor. + // The interface name is needed as e.g ipv4 over ipv6 addresses + // are not exposed using Android functions, but it is possible + // bind an ipv4 address to the interface. + NetworkBindingResult BindSocketToNetwork(int socket_fd, + const IPAddress& address) override; + protected: #if defined(WEBRTC_POSIX) // Separated from CreateNetworks for tests. @@ -293,7 +303,8 @@ class RTC_EXPORT BasicNetworkManager : public NetworkManagerBase, nullptr; std::unique_ptr network_monitor_ RTC_GUARDED_BY(thread_); - bool allow_mac_based_ipv6_ = false; + bool allow_mac_based_ipv6_ RTC_GUARDED_BY(thread_) = false; + bool bind_using_ifname_ RTC_GUARDED_BY(thread_) = false; }; // Represents a Unix-type network interface, with a name and single address. diff --git a/rtc_base/network_monitor.h b/rtc_base/network_monitor.h index 4a3002f427..dddc2f60f4 100644 --- a/rtc_base/network_monitor.h +++ b/rtc_base/network_monitor.h @@ -36,6 +36,8 @@ enum class NetworkPreference { const char* NetworkPreferenceToString(NetworkPreference preference); +// This interface is set onto a socket server, +// where only the ip address is known at the time of binding. class NetworkBinderInterface { public: // Binds a socket to the network that is attached to |address| so that all @@ -83,6 +85,19 @@ class NetworkMonitorInterface { virtual NetworkPreference GetNetworkPreference( const std::string& interface_name) = 0; + // Does |this| NetworkMonitorInterface implement BindSocketToNetwork? + // Only Android returns true. + virtual bool SupportsBindSocketToNetwork() const { return false; } + + // Bind a socket to an interface specified by ip address and/or interface + // name. Only implemented on Android. + virtual NetworkBindingResult BindSocketToNetwork( + int socket_fd, + const IPAddress& address, + const std::string& interface_name) { + return NetworkBindingResult::NOT_IMPLEMENTED; + } + // Is this interface available to use? WebRTC shouldn't attempt to use it if // this returns false. // diff --git a/rtc_base/network_unittest.cc b/rtc_base/network_unittest.cc index 73ddd81ce8..abad4796fe 100644 --- a/rtc_base/network_unittest.cc +++ b/rtc_base/network_unittest.cc @@ -76,9 +76,35 @@ class FakeNetworkMonitor : public NetworkMonitorInterface { unavailable_adapters_ = unavailable_adapters; } + bool SupportsBindSocketToNetwork() const override { return true; } + + NetworkBindingResult BindSocketToNetwork( + int socket_fd, + const IPAddress& address, + const std::string& if_name) override { + if (absl::c_count(addresses_, address) > 0) { + return NetworkBindingResult::SUCCESS; + } + + for (auto const& iter : adapters_) { + if (if_name.find(iter) != std::string::npos) { + return NetworkBindingResult::SUCCESS; + } + } + return NetworkBindingResult::ADDRESS_NOT_FOUND; + } + + void set_ip_addresses(std::vector addresses) { + addresses_ = addresses; + } + + void set_adapters(std::vector adapters) { adapters_ = adapters; } + private: bool started_ = false; + std::vector adapters_; std::vector unavailable_adapters_; + std::vector addresses_; }; class FakeNetworkMonitorFactory : public NetworkMonitorFactory { @@ -1279,4 +1305,48 @@ TEST_F(NetworkTest, WebRTC_AllowMACBasedIPv6Address) { } #endif +#if defined(WEBRTC_POSIX) +TEST_F(NetworkTest, WebRTC_BindUsingInterfaceName) { + webrtc::test::ScopedFieldTrials field_trials( + "WebRTC-BindUsingInterfaceName/Enabled/"); + + char if_name1[20] = "wlan0"; + char if_name2[20] = "v4-wlan0"; + ifaddrs* list = nullptr; + list = AddIpv6Address(list, if_name1, "1000:2000:3000:4000:0:0:0:1", + "FFFF:FFFF:FFFF:FFFF::", 0); + list = AddIpv4Address(list, if_name2, "192.168.0.2", "255.255.255.255"); + NetworkManager::NetworkList result; + + // Sanity check that both interfaces are included by default. + FakeNetworkMonitorFactory factory; + BasicNetworkManager manager(&factory); + manager.StartUpdating(); + CallConvertIfAddrs(manager, list, /*include_ignored=*/false, &result); + EXPECT_EQ(2u, result.size()); + ReleaseIfAddrs(list); + bool changed; + // This ensures we release the objects created in CallConvertIfAddrs. + MergeNetworkList(manager, result, &changed); + result.clear(); + + FakeNetworkMonitor* network_monitor = GetNetworkMonitor(manager); + + IPAddress ipv6; + EXPECT_TRUE(IPFromString("1000:2000:3000:4000:0:0:0:1", &ipv6)); + IPAddress ipv4; + EXPECT_TRUE(IPFromString("192.168.0.2", &ipv4)); + + // The network monitor only knwos about the ipv6 address, interface. + network_monitor->set_adapters({"wlan0"}); + network_monitor->set_ip_addresses({ipv6}); + EXPECT_EQ(manager.BindSocketToNetwork(/* fd */ 77, ipv6), + NetworkBindingResult::SUCCESS); + + // But it will bind anyway using string matching... + EXPECT_EQ(manager.BindSocketToNetwork(/* fd */ 77, ipv4), + NetworkBindingResult::SUCCESS); +} +#endif + } // namespace rtc diff --git a/sdk/android/native_unittests/android_network_monitor_unittest.cc b/sdk/android/native_unittests/android_network_monitor_unittest.cc index 5c17d44fb2..b596123d86 100644 --- a/sdk/android/native_unittests/android_network_monitor_unittest.cc +++ b/sdk/android/native_unittests/android_network_monitor_unittest.cc @@ -69,7 +69,7 @@ TEST_F(AndroidNetworkMonitorTest, TestFindNetworkHandleUsingIpv4Address) { network_monitor_->SetNetworkInfos(net_infos); auto network_handle = - network_monitor_->FindNetworkHandleFromAddress(ipv4_address); + network_monitor_->FindNetworkHandleFromAddressOrName(ipv4_address, ""); ASSERT_TRUE(network_handle.has_value()); EXPECT_EQ(ipv4_handle, *network_handle); @@ -86,9 +86,9 @@ TEST_F(AndroidNetworkMonitorTest, TestFindNetworkHandleUsingFullIpv6Address) { network_monitor_->SetNetworkInfos(net_infos); auto network_handle1 = - network_monitor_->FindNetworkHandleFromAddress(ipv6_address1); + network_monitor_->FindNetworkHandleFromAddressOrName(ipv6_address1, ""); auto network_handle2 = - network_monitor_->FindNetworkHandleFromAddress(ipv6_address2); + network_monitor_->FindNetworkHandleFromAddressOrName(ipv6_address2, ""); ASSERT_TRUE(network_handle1.has_value()); EXPECT_EQ(ipv6_handle, *network_handle1); @@ -111,9 +111,9 @@ TEST_F(AndroidNetworkMonitorTest, network_monitor_->SetNetworkInfos(net_infos); auto network_handle1 = - network_monitor_->FindNetworkHandleFromAddress(ipv6_address1); + network_monitor_->FindNetworkHandleFromAddressOrName(ipv6_address1, ""); auto network_handle2 = - network_monitor_->FindNetworkHandleFromAddress(ipv6_address2); + network_monitor_->FindNetworkHandleFromAddressOrName(ipv6_address2, ""); ASSERT_TRUE(network_handle1.has_value()); EXPECT_EQ(ipv6_handle, *network_handle1); @@ -121,5 +121,33 @@ TEST_F(AndroidNetworkMonitorTest, EXPECT_EQ(ipv6_handle, *network_handle2); } +TEST_F(AndroidNetworkMonitorTest, TestFindNetworkHandleUsingIfName) { + ScopedFieldTrials field_trials("WebRTC-BindUsingInterfaceName/Enabled/"); + // Start() updates the states introduced by the field trial. + network_monitor_->Start(); + jni::NetworkHandle ipv6_handle = 200; + rtc::IPAddress ipv6_address1 = GetIpAddressFromIpv6String(kTestIpv6Address1); + + // Set up an IPv6 network. + jni::NetworkInformation net_info = + CreateNetworkInformation("wlan0", ipv6_handle, ipv6_address1); + std::vector net_infos(1, net_info); + network_monitor_->SetNetworkInfos(net_infos); + + rtc::IPAddress ipv4_address(kTestIpv4Address); + + // Search using ip address only... + auto network_handle1 = + network_monitor_->FindNetworkHandleFromAddressOrName(ipv4_address, ""); + + // Search using ip address AND if_name (for typical ipv4 over ipv6 tunnel). + auto network_handle2 = network_monitor_->FindNetworkHandleFromAddressOrName( + ipv4_address, "v4-wlan0"); + + ASSERT_FALSE(network_handle1.has_value()); + ASSERT_TRUE(network_handle2.has_value()); + EXPECT_EQ(ipv6_handle, *network_handle2); +} + } // namespace test } // namespace webrtc diff --git a/sdk/android/src/jni/android_network_monitor.cc b/sdk/android/src/jni/android_network_monitor.cc index af5c46b666..7f71ec7f0e 100644 --- a/sdk/android/src/jni/android_network_monitor.cc +++ b/sdk/android/src/jni/android_network_monitor.cc @@ -243,11 +243,8 @@ void AndroidNetworkMonitor::Start() { find_network_handle_without_ipv6_temporary_part_ = webrtc::field_trial::IsEnabled( "WebRTC-FindNetworkHandleWithoutIpv6TemporaryPart"); - - // This is kind of magic behavior, but doing this allows the SocketServer to - // use this as a NetworkBinder to bind sockets on a particular network when - // it creates sockets. - network_thread_->socketserver()->set_network_binder(this); + bind_using_ifname_ = + webrtc::field_trial::IsEnabled("WebRTC-BindUsingInterfaceName"); JNIEnv* env = AttachCurrentThreadIfNeeded(); Java_NetworkMonitor_startMonitoring( @@ -262,12 +259,6 @@ void AndroidNetworkMonitor::Stop() { started_ = false; find_network_handle_without_ipv6_temporary_part_ = false; - // Once the network monitor stops, it will clear all network information and - // it won't find the network handle to bind anyway. - if (network_thread_->socketserver()->network_binder() == this) { - network_thread_->socketserver()->set_network_binder(nullptr); - } - JNIEnv* env = AttachCurrentThreadIfNeeded(); Java_NetworkMonitor_stopMonitoring(env, j_network_monitor_, jlongFromPointer(this)); @@ -280,7 +271,8 @@ void AndroidNetworkMonitor::Stop() { // https://cs.chromium.org/chromium/src/net/udp/udp_socket_posix.cc rtc::NetworkBindingResult AndroidNetworkMonitor::BindSocketToNetwork( int socket_fd, - const rtc::IPAddress& address) { + const rtc::IPAddress& address, + const std::string& if_name) { RTC_DCHECK_RUN_ON(network_thread_); // Android prior to Lollipop didn't have support for binding sockets to @@ -298,12 +290,18 @@ rtc::NetworkBindingResult AndroidNetworkMonitor::BindSocketToNetwork( } absl::optional network_handle = - FindNetworkHandleFromAddress(address); + FindNetworkHandleFromAddressOrName(address, if_name); if (!network_handle) { + RTC_LOG(LS_WARNING) + << "BindSocketToNetwork unable to find network handle for" + << " addr: " << address.ToSensitiveString() << " ifname: " << if_name; return rtc::NetworkBindingResult::ADDRESS_NOT_FOUND; } if (*network_handle == 0 /* NETWORK_UNSPECIFIED */) { + RTC_LOG(LS_WARNING) << "BindSocketToNetwork 0 network handle for" + << " addr: " << address.ToSensitiveString() + << " ifname: " << if_name; return rtc::NetworkBindingResult::NOT_IMPLEMENTED; } @@ -370,11 +368,19 @@ rtc::NetworkBindingResult AndroidNetworkMonitor::BindSocketToNetwork( // ERR_NETWORK_CHANGED, rather than MapSystemError(ENONET) which gives back // the less descriptive ERR_FAILED. if (rv == 0) { + RTC_LOG(LS_VERBOSE) << "BindSocketToNetwork bound network handle for" + << " addr: " << address.ToSensitiveString() + << " ifname: " << if_name; return rtc::NetworkBindingResult::SUCCESS; } + + RTC_LOG(LS_WARNING) << "BindSocketToNetwork got error: " << rv + << " addr: " << address.ToSensitiveString() + << " ifname: " << if_name; if (rv == ENONET) { return rtc::NetworkBindingResult::NETWORK_CHANGED; } + return rtc::NetworkBindingResult::FAILURE; } @@ -397,8 +403,9 @@ void AndroidNetworkMonitor::OnNetworkConnected_n( } absl::optional -AndroidNetworkMonitor::FindNetworkHandleFromAddress( - const rtc::IPAddress& ip_address) const { +AndroidNetworkMonitor::FindNetworkHandleFromAddressOrName( + const rtc::IPAddress& ip_address, + const std::string& if_name) const { RTC_DCHECK_RUN_ON(network_thread_); RTC_LOG(LS_INFO) << "Find network handle."; if (find_network_handle_without_ipv6_temporary_part_) { @@ -412,14 +419,29 @@ AndroidNetworkMonitor::FindNetworkHandleFromAddress( return absl::make_optional(iter.first); } } - return absl::nullopt; } else { auto iter = network_handle_by_address_.find(ip_address); - if (iter == network_handle_by_address_.end()) { - return absl::nullopt; + if (iter != network_handle_by_address_.end()) { + return absl::make_optional(iter->second); } - return absl::make_optional(iter->second); } + + return FindNetworkHandleFromIfname(if_name); +} + +absl::optional +AndroidNetworkMonitor::FindNetworkHandleFromIfname( + const std::string& if_name) const { + RTC_DCHECK_RUN_ON(network_thread_); + if (bind_using_ifname_) { + for (auto const& iter : network_info_by_handle_) { + if (if_name.find(iter.second.interface_name) != std::string::npos) { + return absl::make_optional(iter.first); + } + } + } + + return absl::nullopt; } void AndroidNetworkMonitor::OnNetworkDisconnected_n(NetworkHandle handle) { @@ -465,6 +487,16 @@ rtc::AdapterType AndroidNetworkMonitor::GetAdapterType( rtc::AdapterType type = (iter == adapter_type_by_name_.end()) ? rtc::ADAPTER_TYPE_UNKNOWN : iter->second; + + if (type == rtc::ADAPTER_TYPE_UNKNOWN && bind_using_ifname_) { + for (auto const& iter : adapter_type_by_name_) { + if (if_name.find(iter.first) != std::string::npos) { + type = iter.second; + break; + } + } + } + if (type == rtc::ADAPTER_TYPE_UNKNOWN) { RTC_LOG(LS_WARNING) << "Get an unknown type for the interface " << if_name; } @@ -478,6 +510,15 @@ rtc::AdapterType AndroidNetworkMonitor::GetVpnUnderlyingAdapterType( rtc::AdapterType type = (iter == vpn_underlying_adapter_type_by_name_.end()) ? rtc::ADAPTER_TYPE_UNKNOWN : iter->second; + if (type == rtc::ADAPTER_TYPE_UNKNOWN && bind_using_ifname_) { + for (auto const& iter : adapter_type_by_name_) { + if (if_name.find(iter.first) != std::string::npos) { + type = iter.second; + break; + } + } + } + return type; } diff --git a/sdk/android/src/jni/android_network_monitor.h b/sdk/android/src/jni/android_network_monitor.h index 2d9522bfcc..5549f3171d 100644 --- a/sdk/android/src/jni/android_network_monitor.h +++ b/sdk/android/src/jni/android_network_monitor.h @@ -62,8 +62,7 @@ struct NetworkInformation { std::string ToString() const; }; -class AndroidNetworkMonitor : public rtc::NetworkMonitorInterface, - public rtc::NetworkBinderInterface { +class AndroidNetworkMonitor : public rtc::NetworkMonitorInterface { public: AndroidNetworkMonitor(JNIEnv* env, const JavaRef& j_application_context); @@ -75,9 +74,14 @@ class AndroidNetworkMonitor : public rtc::NetworkMonitorInterface, void Start() override; void Stop() override; + // Does |this| NetworkMonitorInterface implement BindSocketToNetwork? + // Only Android returns true. + virtual bool SupportsBindSocketToNetwork() const override { return true; } + rtc::NetworkBindingResult BindSocketToNetwork( int socket_fd, - const rtc::IPAddress& address) override; + const rtc::IPAddress& address, + const std::string& if_name) override; rtc::AdapterType GetAdapterType(const std::string& if_name) override; rtc::AdapterType GetVpnUnderlyingAdapterType( const std::string& if_name) override; @@ -104,8 +108,9 @@ class AndroidNetworkMonitor : public rtc::NetworkMonitorInterface, jint preference); // Visible for testing. - absl::optional FindNetworkHandleFromAddress( - const rtc::IPAddress& address) const; + absl::optional FindNetworkHandleFromAddressOrName( + const rtc::IPAddress& address, + const std::string& ifname) const; private: void OnNetworkConnected_n(const NetworkInformation& network_info); @@ -113,6 +118,9 @@ class AndroidNetworkMonitor : public rtc::NetworkMonitorInterface, void OnNetworkPreference_n(NetworkType type, rtc::NetworkPreference preference); + absl::optional FindNetworkHandleFromIfname( + const std::string& ifname) const; + const int android_sdk_int_; ScopedJavaGlobalRef j_application_context_; ScopedJavaGlobalRef j_network_monitor_; @@ -131,6 +139,7 @@ class AndroidNetworkMonitor : public rtc::NetworkMonitorInterface, bool find_network_handle_without_ipv6_temporary_part_ RTC_GUARDED_BY(network_thread_) = false; bool surface_cellular_types_ RTC_GUARDED_BY(network_thread_) = false; + bool bind_using_ifname_ RTC_GUARDED_BY(network_thread_) = true; }; class AndroidNetworkMonitorFactory : public rtc::NetworkMonitorFactory {