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}
This commit is contained in:
Jonas Oreland 2021-08-25 08:58:04 +02:00 committed by WebRTC LUCI CQ
parent c2113a3fef
commit c8fa1eeb75
11 changed files with 218 additions and 22 deletions

View File

@ -653,6 +653,14 @@ class RTC_EXPORT PeerConnectionInterface : public rtc::RefCountInterface {
// The ping interval (ms) when the connection is stable and writable. This // The ping interval (ms) when the connection is stable and writable. This
// parameter overrides the default value in the ICE implementation if set. // parameter overrides the default value in the ICE implementation if set.
absl::optional<int> stable_writable_connection_ping_interval_ms; 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. // Don't forget to update operator== if adding something.
// //

View File

@ -34,6 +34,16 @@ enum PortPrunePolicy {
// on the same network. // 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 } // namespace webrtc
#endif // API_TRANSPORT_ENUMS_H_ #endif // API_TRANSPORT_ENUMS_H_

View File

@ -739,6 +739,31 @@ int BasicIceController::CompareCandidatePairNetworks(
return compare_a_b_by_network_preference; 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 a_cost = a->ComputeNetworkCost();
uint32_t b_cost = b->ComputeNetworkCost(); uint32_t b_cost = b->ComputeNetworkCost();
// Prefer lower network cost. // Prefer lower network cost.

View File

@ -175,6 +175,8 @@ struct IceConfig {
absl::optional<rtc::AdapterType> network_preference; absl::optional<rtc::AdapterType> network_preference;
webrtc::VpnPreference vpn_preference = webrtc::VpnPreference::kDefault;
IceConfig(); IceConfig();
IceConfig(int receiving_timeout_ms, IceConfig(int receiving_timeout_ms,
int backup_connection_ping_interval, int backup_connection_ping_interval,

View File

@ -796,6 +796,9 @@ void P2PTransportChannel::SetIceConfig(const IceConfig& config) {
config_.regather_on_failed_networks_interval_or_default(); config_.regather_on_failed_networks_interval_or_default();
regathering_controller_->SetConfig(regathering_config); regathering_controller_->SetConfig(regathering_config);
config_.vpn_preference = config.vpn_preference;
allocator_->SetVpnPreference(config_.vpn_preference);
ice_controller_->SetIceConfig(config_); ice_controller_->SetIceConfig(config_);
RTC_DCHECK(ValidateIceConfig(config_).ok()); RTC_DCHECK(ValidateIceConfig(config_).ok());

View File

@ -529,9 +529,11 @@ class P2PTransportChannelTestBase : public ::testing::Test,
void AddAddress(int endpoint, void AddAddress(int endpoint,
const SocketAddress& addr, const SocketAddress& addr,
const std::string& ifname, const std::string& ifname,
rtc::AdapterType adapter_type) { rtc::AdapterType adapter_type,
GetEndpoint(endpoint)->network_manager_.AddInterface(addr, ifname, absl::optional<rtc::AdapterType> underlying_vpn_adapter_type =
adapter_type); absl::nullopt) {
GetEndpoint(endpoint)->network_manager_.AddInterface(
addr, ifname, adapter_type, underlying_vpn_adapter_type);
} }
void RemoveAddress(int endpoint, const SocketAddress& addr) { void RemoveAddress(int endpoint, const SocketAddress& addr) {
GetEndpoint(endpoint)->network_manager_.RemoveInterface(addr); GetEndpoint(endpoint)->network_manager_.RemoveInterface(addr);
@ -3169,6 +3171,119 @@ TEST_F(P2PTransportChannelMultihomedTest, TestRestoreBackupConnection) {
DestroyChannels(); 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 // A collection of tests which tests a single P2PTransportChannel by sending
// pings. // pings.
class P2PTransportChannelPingTest : public ::testing::Test, class P2PTransportChannelPingTest : public ::testing::Test,

View File

@ -400,6 +400,12 @@ class RTC_EXPORT PortAllocator : public sigslot::has_slots<> {
// loopback interfaces. // loopback interfaces.
virtual void SetNetworkIgnoreMask(int network_ignore_mask) = 0; 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( std::unique_ptr<PortAllocatorSession> CreateSession(
const std::string& content_name, const std::string& content_name,
int component, int component,
@ -638,6 +644,7 @@ class RTC_EXPORT PortAllocator : public sigslot::has_slots<> {
uint32_t candidate_filter_; uint32_t candidate_filter_;
std::string origin_; std::string origin_;
webrtc::SequenceChecker thread_checker_; webrtc::SequenceChecker thread_checker_;
webrtc::VpnPreference vpn_preference_ = webrtc::VpnPreference::kDefault;
private: private:
ServerAddresses stun_servers_; ServerAddresses stun_servers_;

View File

@ -213,6 +213,22 @@ void BasicPortAllocator::SetNetworkIgnoreMask(int network_ignore_mask) {
network_ignore_mask_ = 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<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( PortAllocatorSession* BasicPortAllocator::CreateSessionInternal(
const std::string& content_name, const std::string& content_name,
int component, int component,
@ -708,7 +724,7 @@ std::vector<rtc::Network*> BasicPortAllocatorSession::GetNetworks() {
// costly networks" flag. // costly networks" flag.
NetworkFilter ignored_filter( NetworkFilter ignored_filter(
[this](rtc::Network* network) { [this](rtc::Network* network) {
return allocator_->network_ignore_mask() & network->type(); return allocator_->GetNetworkIgnoreMask() & network->type();
}, },
"ignored"); "ignored");
FilterNetworks(&networks, ignored_filter); FilterNetworks(&networks, ignored_filter);
@ -731,6 +747,7 @@ std::vector<rtc::Network*> BasicPortAllocatorSession::GetNetworks() {
"costly"); "costly");
FilterNetworks(&networks, costly_filter); FilterNetworks(&networks, costly_filter);
} }
// Lastly, if we have a limit for the number of IPv6 network interfaces (by // 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. // default, it's 5), remove networks to ensure that limit is satisfied.
// //

View File

@ -46,10 +46,7 @@ class RTC_EXPORT BasicPortAllocator : public PortAllocator {
// Set to kDefaultNetworkIgnoreMask by default. // Set to kDefaultNetworkIgnoreMask by default.
void SetNetworkIgnoreMask(int network_ignore_mask) override; void SetNetworkIgnoreMask(int network_ignore_mask) override;
int network_ignore_mask() const { int GetNetworkIgnoreMask() const;
CheckRunOnValidThreadIfInitialized();
return network_ignore_mask_;
}
rtc::NetworkManager* network_manager() const { rtc::NetworkManager* network_manager() const {
CheckRunOnValidThreadIfInitialized(); CheckRunOnValidThreadIfInitialized();

View File

@ -336,6 +336,7 @@ bool PeerConnectionInterface::RTCConfiguration::operator==(
absl::optional<bool> allow_codec_switching; absl::optional<bool> allow_codec_switching;
absl::optional<int> report_usage_pattern_delay_ms; absl::optional<int> report_usage_pattern_delay_ms;
absl::optional<int> stable_writable_connection_ping_interval_ms; absl::optional<int> stable_writable_connection_ping_interval_ms;
webrtc::VpnPreference vpn_preference;
}; };
static_assert(sizeof(stuff_being_tested_for_equality) == sizeof(*this), static_assert(sizeof(stuff_being_tested_for_equality) == sizeof(*this),
"Did you add something to RTCConfiguration and forget to " "Did you add something to RTCConfiguration and forget to "
@ -397,7 +398,8 @@ bool PeerConnectionInterface::RTCConfiguration::operator==(
allow_codec_switching == o.allow_codec_switching && allow_codec_switching == o.allow_codec_switching &&
report_usage_pattern_delay_ms == o.report_usage_pattern_delay_ms && report_usage_pattern_delay_ms == o.report_usage_pattern_delay_ms &&
stable_writable_connection_ping_interval_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!=( bool PeerConnectionInterface::RTCConfiguration::operator!=(

View File

@ -36,7 +36,12 @@ class FakeNetworkManager : public NetworkManagerBase,
public: public:
FakeNetworkManager() {} 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) { void AddInterface(const SocketAddress& iface) {
// Ensure a unique name for the interface if its name is not given. // 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); AddInterface(iface, if_name, ADAPTER_TYPE_UNKNOWN);
} }
void AddInterface(const SocketAddress& iface, void AddInterface(
const SocketAddress& iface,
const std::string& if_name, const std::string& if_name,
AdapterType type) { AdapterType type,
absl::optional<AdapterType> underlying_vpn_adapter_type = absl::nullopt) {
SocketAddress address(if_name, 0); SocketAddress address(if_name, 0);
address.SetResolvedIP(iface.ipaddr()); address.SetResolvedIP(iface.ipaddr());
ifaces_.push_back(std::make_pair(address, type)); ifaces_.push_back({address, type, underlying_vpn_adapter_type});
DoUpdateNetworks(); DoUpdateNetworks();
} }
void RemoveInterface(const SocketAddress& iface) { void RemoveInterface(const SocketAddress& iface) {
for (IfaceList::iterator it = ifaces_.begin(); it != ifaces_.end(); ++it) { for (IfaceList::iterator it = ifaces_.begin(); it != ifaces_.end(); ++it) {
if (it->first.EqualIPs(iface)) { if (it->socket_address.EqualIPs(iface)) {
ifaces_.erase(it); ifaces_.erase(it);
break; break;
} }
@ -112,17 +119,20 @@ class FakeNetworkManager : public NetworkManagerBase,
std::vector<Network*> networks; std::vector<Network*> networks;
for (IfaceList::iterator it = ifaces_.begin(); it != ifaces_.end(); ++it) { for (IfaceList::iterator it = ifaces_.begin(); it != ifaces_.end(); ++it) {
int prefix_length = 0; int prefix_length = 0;
if (it->first.ipaddr().family() == AF_INET) { if (it->socket_address.ipaddr().family() == AF_INET) {
prefix_length = kFakeIPv4NetworkPrefixLength; prefix_length = kFakeIPv4NetworkPrefixLength;
} else if (it->first.ipaddr().family() == AF_INET6) { } else if (it->socket_address.ipaddr().family() == AF_INET6) {
prefix_length = kFakeIPv6NetworkPrefixLength; prefix_length = kFakeIPv6NetworkPrefixLength;
} }
IPAddress prefix = TruncateIP(it->first.ipaddr(), prefix_length); IPAddress prefix = TruncateIP(it->socket_address.ipaddr(), prefix_length);
std::unique_ptr<Network> net(new Network(it->first.hostname(), std::unique_ptr<Network> net(new Network(
it->first.hostname(), prefix, it->socket_address.hostname(), it->socket_address.hostname(), prefix,
prefix_length, it->second)); 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->set_default_local_address_provider(this);
net->AddIP(it->first.ipaddr()); net->AddIP(it->socket_address.ipaddr());
networks.push_back(net.release()); networks.push_back(net.release());
} }
bool changed; bool changed;