Add and implement VPN preference
This patch adds a vp preference field to RTCConfig.
DEFAULT, // No VPN preference.
ONLY_USE_VPN, // only use VPN connections.
NEVER_USE_VPN, // never use VPN connections
PREFER_VPN, // use a VPN connection if possible, i.e VPN connections sorts higher than all other connections.
AVOID_VPN, // only use VPN if there is no other connections, i.e VPN connections sorts last.
Bug: webrtc:13097
Change-Id: I3f95bdfa9134e082c7d389f803bd08facfb70262
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/229591
Reviewed-by: Harald Alvestrand <hta@webrtc.org>
Commit-Queue: Jonas Oreland <jonaso@webrtc.org>
Cr-Commit-Position: refs/heads/main@{#34842}
diff --git a/api/peer_connection_interface.h b/api/peer_connection_interface.h
index a3c420f..a9fd5e3 100644
--- a/api/peer_connection_interface.h
+++ b/api/peer_connection_interface.h
@@ -653,6 +653,14 @@
// 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<int> 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 eb33e91..3bc8fd1 100644
--- a/api/transport/enums.h
+++ b/api/transport/enums.h
@@ -34,6 +34,16 @@
// 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 dca04ca..ac22e1d 100644
--- a/p2p/base/basic_ice_controller.cc
+++ b/p2p/base/basic_ice_controller.cc
@@ -739,6 +739,31 @@
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 796978f..20730e1 100644
--- a/p2p/base/ice_transport_internal.h
+++ b/p2p/base/ice_transport_internal.h
@@ -175,6 +175,8 @@
absl::optional<rtc::AdapterType> 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 f30783b..5587a84 100644
--- a/p2p/base/p2p_transport_channel.cc
+++ b/p2p/base/p2p_transport_channel.cc
@@ -796,6 +796,9 @@
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 e4f4fa1..0ddeb2d 100644
--- a/p2p/base/p2p_transport_channel_unittest.cc
+++ b/p2p/base/p2p_transport_channel_unittest.cc
@@ -529,9 +529,11 @@
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<rtc::AdapterType> 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 @@
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 78b0c70..e9b1d1e 100644
--- a/p2p/base/port_allocator.h
+++ b/p2p/base/port_allocator.h
@@ -400,6 +400,12 @@
// 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<PortAllocatorSession> CreateSession(
const std::string& content_name,
int component,
@@ -638,6 +644,7 @@
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 748be90..0a1b996 100644
--- a/p2p/client/basic_port_allocator.cc
+++ b/p2p/client/basic_port_allocator.cc
@@ -213,6 +213,22 @@
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<int>(rtc::ADAPTER_TYPE_VPN);
+ break;
+ case webrtc::VpnPreference::kNeverUseVpn:
+ mask |= static_cast<int>(rtc::ADAPTER_TYPE_VPN);
+ break;
+ default:
+ break;
+ }
+ return mask;
+}
+
PortAllocatorSession* BasicPortAllocator::CreateSessionInternal(
const std::string& content_name,
int component,
@@ -708,7 +724,7 @@
// 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 @@
"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 3fca9be..c3858f7 100644
--- a/p2p/client/basic_port_allocator.h
+++ b/p2p/client/basic_port_allocator.h
@@ -46,10 +46,7 @@
// 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 5bcd940..5d10107 100644
--- a/pc/peer_connection.cc
+++ b/pc/peer_connection.cc
@@ -336,6 +336,7 @@
absl::optional<bool> allow_codec_switching;
absl::optional<int> report_usage_pattern_delay_ms;
absl::optional<int> 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 @@
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 1bbdd46..53664cb 100644
--- a/rtc_base/fake_network.h
+++ b/rtc_base/fake_network.h
@@ -36,7 +36,12 @@
public:
FakeNetworkManager() {}
- typedef std::vector<std::pair<SocketAddress, AdapterType>> IfaceList;
+ struct Iface {
+ SocketAddress socket_address;
+ AdapterType adapter_type;
+ absl::optional<AdapterType> underlying_vpn_adapter_type;
+ };
+ typedef std::vector<Iface> IfaceList;
void AddInterface(const SocketAddress& iface) {
// Ensure a unique name for the interface if its name is not given.
@@ -47,18 +52,20 @@
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<AdapterType> 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 @@
std::vector<Network*> 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<Network> 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<Network> 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;