diff --git a/api/peer_connection_interface.h b/api/peer_connection_interface.h index a3c420f80b..a9fd5e3501 100644 --- a/api/peer_connection_interface.h +++ b/api/peer_connection_interface.h @@ -653,6 +653,14 @@ class RTC_EXPORT PeerConnectionInterface : public rtc::RefCountInterface { // The ping interval (ms) when the connection is stable and writable. This // parameter overrides the default value in the ICE implementation if set. absl::optional stable_writable_connection_ping_interval_ms; + + // Whether this PeerConnection will avoid VPNs (kAvoidVpn), prefer VPNs + // (kPreferVpn), only work over VPN (kOnlyUseVpn) or only work over non-VPN + // (kNeverUseVpn) interfaces. This controls which local interfaces the + // PeerConnection will prefer to connect over. Since VPN detection is not + // perfect, adherence to this preference cannot be guaranteed. + VpnPreference vpn_preference = VpnPreference::kDefault; + // // Don't forget to update operator== if adding something. // diff --git a/api/transport/enums.h b/api/transport/enums.h index eb33e919a9..3bc8fd1529 100644 --- a/api/transport/enums.h +++ b/api/transport/enums.h @@ -34,6 +34,16 @@ enum PortPrunePolicy { // on the same network. }; +enum class VpnPreference { + kDefault, // No VPN preference. + kOnlyUseVpn, // only use VPN connections. + kNeverUseVpn, // never use VPN connections + kPreferVpn, // use a VPN connection if possible, + // i.e VPN connections sorts first. + kAvoidVpn, // only use VPN if there is no other connections, + // i.e VPN connections sorts last. +}; + } // namespace webrtc #endif // API_TRANSPORT_ENUMS_H_ diff --git a/p2p/base/basic_ice_controller.cc b/p2p/base/basic_ice_controller.cc index dca04ca053..ac22e1d941 100644 --- a/p2p/base/basic_ice_controller.cc +++ b/p2p/base/basic_ice_controller.cc @@ -739,6 +739,31 @@ int BasicIceController::CompareCandidatePairNetworks( return compare_a_b_by_network_preference; } + bool a_vpn = a->network()->IsVpn(); + bool b_vpn = b->network()->IsVpn(); + switch (config_.vpn_preference) { + case webrtc::VpnPreference::kDefault: + break; + case webrtc::VpnPreference::kOnlyUseVpn: + case webrtc::VpnPreference::kPreferVpn: + if (a_vpn && !b_vpn) { + return a_is_better; + } else if (!a_vpn && b_vpn) { + return b_is_better; + } + break; + case webrtc::VpnPreference::kNeverUseVpn: + case webrtc::VpnPreference::kAvoidVpn: + if (a_vpn && !b_vpn) { + return b_is_better; + } else if (!a_vpn && b_vpn) { + return a_is_better; + } + break; + default: + break; + } + uint32_t a_cost = a->ComputeNetworkCost(); uint32_t b_cost = b->ComputeNetworkCost(); // Prefer lower network cost. diff --git a/p2p/base/ice_transport_internal.h b/p2p/base/ice_transport_internal.h index 796978ff17..20730e1cfd 100644 --- a/p2p/base/ice_transport_internal.h +++ b/p2p/base/ice_transport_internal.h @@ -175,6 +175,8 @@ struct IceConfig { absl::optional network_preference; + webrtc::VpnPreference vpn_preference = webrtc::VpnPreference::kDefault; + IceConfig(); IceConfig(int receiving_timeout_ms, int backup_connection_ping_interval, diff --git a/p2p/base/p2p_transport_channel.cc b/p2p/base/p2p_transport_channel.cc index f30783ba34..5587a84636 100644 --- a/p2p/base/p2p_transport_channel.cc +++ b/p2p/base/p2p_transport_channel.cc @@ -796,6 +796,9 @@ void P2PTransportChannel::SetIceConfig(const IceConfig& config) { config_.regather_on_failed_networks_interval_or_default(); regathering_controller_->SetConfig(regathering_config); + config_.vpn_preference = config.vpn_preference; + allocator_->SetVpnPreference(config_.vpn_preference); + ice_controller_->SetIceConfig(config_); RTC_DCHECK(ValidateIceConfig(config_).ok()); diff --git a/p2p/base/p2p_transport_channel_unittest.cc b/p2p/base/p2p_transport_channel_unittest.cc index e4f4fa17fe..0ddeb2df72 100644 --- a/p2p/base/p2p_transport_channel_unittest.cc +++ b/p2p/base/p2p_transport_channel_unittest.cc @@ -529,9 +529,11 @@ class P2PTransportChannelTestBase : public ::testing::Test, void AddAddress(int endpoint, const SocketAddress& addr, const std::string& ifname, - rtc::AdapterType adapter_type) { - GetEndpoint(endpoint)->network_manager_.AddInterface(addr, ifname, - adapter_type); + rtc::AdapterType adapter_type, + absl::optional underlying_vpn_adapter_type = + absl::nullopt) { + GetEndpoint(endpoint)->network_manager_.AddInterface( + addr, ifname, adapter_type, underlying_vpn_adapter_type); } void RemoveAddress(int endpoint, const SocketAddress& addr) { GetEndpoint(endpoint)->network_manager_.RemoveInterface(addr); @@ -3169,6 +3171,119 @@ TEST_F(P2PTransportChannelMultihomedTest, TestRestoreBackupConnection) { DestroyChannels(); } +TEST_F(P2PTransportChannelMultihomedTest, TestVpnDefault) { + rtc::ScopedFakeClock clock; + AddAddress(0, kPublicAddrs[0], "eth0", rtc::ADAPTER_TYPE_ETHERNET); + AddAddress(0, kAlternateAddrs[0], "vpn0", rtc::ADAPTER_TYPE_VPN); + AddAddress(1, kPublicAddrs[1]); + + IceConfig config; + CreateChannels(config, config, false); + EXPECT_TRUE_SIMULATED_WAIT( + CheckConnected(ep1_ch1(), ep2_ch1()) && + !ep1_ch1()->selected_connection()->network()->IsVpn(), + kDefaultTimeout, clock); +} + +TEST_F(P2PTransportChannelMultihomedTest, TestVpnPreferVpn) { + rtc::ScopedFakeClock clock; + AddAddress(0, kPublicAddrs[0], "eth0", rtc::ADAPTER_TYPE_ETHERNET); + AddAddress(0, kAlternateAddrs[0], "vpn0", rtc::ADAPTER_TYPE_VPN, + rtc::ADAPTER_TYPE_CELLULAR); + AddAddress(1, kPublicAddrs[1]); + + IceConfig config; + config.vpn_preference = webrtc::VpnPreference::kPreferVpn; + RTC_LOG(LS_INFO) << "KESO: config.vpn_preference: " << config.vpn_preference; + CreateChannels(config, config, false); + EXPECT_TRUE_SIMULATED_WAIT( + CheckConnected(ep1_ch1(), ep2_ch1()) && + ep1_ch1()->selected_connection()->network()->IsVpn(), + kDefaultTimeout, clock); + + // Block VPN. + fw()->AddRule(false, rtc::FP_ANY, rtc::FD_ANY, kAlternateAddrs[0]); + + // Check that it switches to non-VPN + EXPECT_TRUE_SIMULATED_WAIT( + CheckConnected(ep1_ch1(), ep2_ch1()) && + !ep1_ch1()->selected_connection()->network()->IsVpn(), + kDefaultTimeout, clock); +} + +TEST_F(P2PTransportChannelMultihomedTest, TestVpnAvoidVpn) { + rtc::ScopedFakeClock clock; + AddAddress(0, kPublicAddrs[0], "eth0", rtc::ADAPTER_TYPE_CELLULAR); + AddAddress(0, kAlternateAddrs[0], "vpn0", rtc::ADAPTER_TYPE_VPN, + rtc::ADAPTER_TYPE_ETHERNET); + AddAddress(1, kPublicAddrs[1]); + + IceConfig config; + config.vpn_preference = webrtc::VpnPreference::kAvoidVpn; + CreateChannels(config, config, false); + EXPECT_TRUE_SIMULATED_WAIT( + CheckConnected(ep1_ch1(), ep2_ch1()) && + !ep1_ch1()->selected_connection()->network()->IsVpn(), + kDefaultTimeout, clock); + + // Block non-VPN. + fw()->AddRule(false, rtc::FP_ANY, rtc::FD_ANY, kPublicAddrs[0]); + + // Check that it switches to VPN + EXPECT_TRUE_SIMULATED_WAIT( + CheckConnected(ep1_ch1(), ep2_ch1()) && + ep1_ch1()->selected_connection()->network()->IsVpn(), + kDefaultTimeout, clock); +} + +TEST_F(P2PTransportChannelMultihomedTest, TestVpnNeverVpn) { + rtc::ScopedFakeClock clock; + AddAddress(0, kPublicAddrs[0], "eth0", rtc::ADAPTER_TYPE_CELLULAR); + AddAddress(0, kAlternateAddrs[0], "vpn0", rtc::ADAPTER_TYPE_VPN, + rtc::ADAPTER_TYPE_ETHERNET); + AddAddress(1, kPublicAddrs[1]); + + IceConfig config; + config.vpn_preference = webrtc::VpnPreference::kNeverUseVpn; + CreateChannels(config, config, false); + EXPECT_TRUE_SIMULATED_WAIT( + CheckConnected(ep1_ch1(), ep2_ch1()) && + !ep1_ch1()->selected_connection()->network()->IsVpn(), + kDefaultTimeout, clock); + + // Block non-VPN. + fw()->AddRule(false, rtc::FP_ANY, rtc::FD_ANY, kPublicAddrs[0]); + + // Check that it does not switches to VPN + clock.AdvanceTime(webrtc::TimeDelta::Millis(kDefaultTimeout)); + EXPECT_TRUE_SIMULATED_WAIT(!CheckConnected(ep1_ch1(), ep2_ch1()), + kDefaultTimeout, clock); +} + +TEST_F(P2PTransportChannelMultihomedTest, TestVpnOnlyVpn) { + rtc::ScopedFakeClock clock; + AddAddress(0, kPublicAddrs[0], "eth0", rtc::ADAPTER_TYPE_CELLULAR); + AddAddress(0, kAlternateAddrs[0], "vpn0", rtc::ADAPTER_TYPE_VPN, + rtc::ADAPTER_TYPE_ETHERNET); + AddAddress(1, kPublicAddrs[1]); + + IceConfig config; + config.vpn_preference = webrtc::VpnPreference::kOnlyUseVpn; + CreateChannels(config, config, false); + EXPECT_TRUE_SIMULATED_WAIT( + CheckConnected(ep1_ch1(), ep2_ch1()) && + ep1_ch1()->selected_connection()->network()->IsVpn(), + kDefaultTimeout, clock); + + // Block VPN. + fw()->AddRule(false, rtc::FP_ANY, rtc::FD_ANY, kAlternateAddrs[0]); + + // Check that it does not switch to non-VPN + clock.AdvanceTime(webrtc::TimeDelta::Millis(kDefaultTimeout)); + EXPECT_TRUE_SIMULATED_WAIT(!CheckConnected(ep1_ch1(), ep2_ch1()), + kDefaultTimeout, clock); +} + // A collection of tests which tests a single P2PTransportChannel by sending // pings. class P2PTransportChannelPingTest : public ::testing::Test, diff --git a/p2p/base/port_allocator.h b/p2p/base/port_allocator.h index 78b0c70833..e9b1d1ef54 100644 --- a/p2p/base/port_allocator.h +++ b/p2p/base/port_allocator.h @@ -400,6 +400,12 @@ class RTC_EXPORT PortAllocator : public sigslot::has_slots<> { // loopback interfaces. virtual void SetNetworkIgnoreMask(int network_ignore_mask) = 0; + // Set whether VPN connections should be preferred, avoided, mandated or + // blocked. + virtual void SetVpnPreference(webrtc::VpnPreference preference) { + vpn_preference_ = preference; + } + std::unique_ptr CreateSession( const std::string& content_name, int component, @@ -638,6 +644,7 @@ class RTC_EXPORT PortAllocator : public sigslot::has_slots<> { uint32_t candidate_filter_; std::string origin_; webrtc::SequenceChecker thread_checker_; + webrtc::VpnPreference vpn_preference_ = webrtc::VpnPreference::kDefault; private: ServerAddresses stun_servers_; diff --git a/p2p/client/basic_port_allocator.cc b/p2p/client/basic_port_allocator.cc index 748be90908..0a1b996892 100644 --- a/p2p/client/basic_port_allocator.cc +++ b/p2p/client/basic_port_allocator.cc @@ -213,6 +213,22 @@ void BasicPortAllocator::SetNetworkIgnoreMask(int network_ignore_mask) { network_ignore_mask_ = network_ignore_mask; } +int BasicPortAllocator::GetNetworkIgnoreMask() const { + CheckRunOnValidThreadIfInitialized(); + int mask = network_ignore_mask_; + switch (vpn_preference_) { + case webrtc::VpnPreference::kOnlyUseVpn: + mask |= ~static_cast(rtc::ADAPTER_TYPE_VPN); + break; + case webrtc::VpnPreference::kNeverUseVpn: + mask |= static_cast(rtc::ADAPTER_TYPE_VPN); + break; + default: + break; + } + return mask; +} + PortAllocatorSession* BasicPortAllocator::CreateSessionInternal( const std::string& content_name, int component, @@ -708,7 +724,7 @@ std::vector BasicPortAllocatorSession::GetNetworks() { // costly networks" flag. NetworkFilter ignored_filter( [this](rtc::Network* network) { - return allocator_->network_ignore_mask() & network->type(); + return allocator_->GetNetworkIgnoreMask() & network->type(); }, "ignored"); FilterNetworks(&networks, ignored_filter); @@ -731,6 +747,7 @@ std::vector BasicPortAllocatorSession::GetNetworks() { "costly"); FilterNetworks(&networks, costly_filter); } + // Lastly, if we have a limit for the number of IPv6 network interfaces (by // default, it's 5), remove networks to ensure that limit is satisfied. // diff --git a/p2p/client/basic_port_allocator.h b/p2p/client/basic_port_allocator.h index 3fca9be688..c3858f71a3 100644 --- a/p2p/client/basic_port_allocator.h +++ b/p2p/client/basic_port_allocator.h @@ -46,10 +46,7 @@ class RTC_EXPORT BasicPortAllocator : public PortAllocator { // Set to kDefaultNetworkIgnoreMask by default. void SetNetworkIgnoreMask(int network_ignore_mask) override; - int network_ignore_mask() const { - CheckRunOnValidThreadIfInitialized(); - return network_ignore_mask_; - } + int GetNetworkIgnoreMask() const; rtc::NetworkManager* network_manager() const { CheckRunOnValidThreadIfInitialized(); diff --git a/pc/peer_connection.cc b/pc/peer_connection.cc index 5bcd940758..5d10107858 100644 --- a/pc/peer_connection.cc +++ b/pc/peer_connection.cc @@ -336,6 +336,7 @@ bool PeerConnectionInterface::RTCConfiguration::operator==( absl::optional allow_codec_switching; absl::optional report_usage_pattern_delay_ms; absl::optional stable_writable_connection_ping_interval_ms; + webrtc::VpnPreference vpn_preference; }; static_assert(sizeof(stuff_being_tested_for_equality) == sizeof(*this), "Did you add something to RTCConfiguration and forget to " @@ -397,7 +398,8 @@ bool PeerConnectionInterface::RTCConfiguration::operator==( allow_codec_switching == o.allow_codec_switching && report_usage_pattern_delay_ms == o.report_usage_pattern_delay_ms && stable_writable_connection_ping_interval_ms == - o.stable_writable_connection_ping_interval_ms; + o.stable_writable_connection_ping_interval_ms && + vpn_preference == o.vpn_preference; } bool PeerConnectionInterface::RTCConfiguration::operator!=( diff --git a/rtc_base/fake_network.h b/rtc_base/fake_network.h index 1bbdd460a0..53664cb8f8 100644 --- a/rtc_base/fake_network.h +++ b/rtc_base/fake_network.h @@ -36,7 +36,12 @@ class FakeNetworkManager : public NetworkManagerBase, public: FakeNetworkManager() {} - typedef std::vector> IfaceList; + struct Iface { + SocketAddress socket_address; + AdapterType adapter_type; + absl::optional underlying_vpn_adapter_type; + }; + typedef std::vector IfaceList; void AddInterface(const SocketAddress& iface) { // Ensure a unique name for the interface if its name is not given. @@ -47,18 +52,20 @@ class FakeNetworkManager : public NetworkManagerBase, AddInterface(iface, if_name, ADAPTER_TYPE_UNKNOWN); } - void AddInterface(const SocketAddress& iface, - const std::string& if_name, - AdapterType type) { + void AddInterface( + const SocketAddress& iface, + const std::string& if_name, + AdapterType type, + absl::optional underlying_vpn_adapter_type = absl::nullopt) { SocketAddress address(if_name, 0); address.SetResolvedIP(iface.ipaddr()); - ifaces_.push_back(std::make_pair(address, type)); + ifaces_.push_back({address, type, underlying_vpn_adapter_type}); DoUpdateNetworks(); } void RemoveInterface(const SocketAddress& iface) { for (IfaceList::iterator it = ifaces_.begin(); it != ifaces_.end(); ++it) { - if (it->first.EqualIPs(iface)) { + if (it->socket_address.EqualIPs(iface)) { ifaces_.erase(it); break; } @@ -112,17 +119,20 @@ class FakeNetworkManager : public NetworkManagerBase, std::vector networks; for (IfaceList::iterator it = ifaces_.begin(); it != ifaces_.end(); ++it) { int prefix_length = 0; - if (it->first.ipaddr().family() == AF_INET) { + if (it->socket_address.ipaddr().family() == AF_INET) { prefix_length = kFakeIPv4NetworkPrefixLength; - } else if (it->first.ipaddr().family() == AF_INET6) { + } else if (it->socket_address.ipaddr().family() == AF_INET6) { prefix_length = kFakeIPv6NetworkPrefixLength; } - IPAddress prefix = TruncateIP(it->first.ipaddr(), prefix_length); - std::unique_ptr net(new Network(it->first.hostname(), - it->first.hostname(), prefix, - prefix_length, it->second)); + IPAddress prefix = TruncateIP(it->socket_address.ipaddr(), prefix_length); + std::unique_ptr net(new Network( + it->socket_address.hostname(), it->socket_address.hostname(), prefix, + prefix_length, it->adapter_type)); + if (it->underlying_vpn_adapter_type.has_value()) { + net->set_underlying_type_for_vpn(*it->underlying_vpn_adapter_type); + } net->set_default_local_address_provider(this); - net->AddIP(it->first.ipaddr()); + net->AddIP(it->socket_address.ipaddr()); networks.push_back(net.release()); } bool changed;