diff --git a/api/peerconnectioninterface.h b/api/peerconnectioninterface.h index 7087de6a9f..0125455ee4 100644 --- a/api/peerconnectioninterface.h +++ b/api/peerconnectioninterface.h @@ -491,6 +491,12 @@ class PeerConnectionInterface : public rtc::RefCountInterface { // called. webrtc::TurnCustomizer* turn_customizer = nullptr; + // Preferred network interface. + // A candidate pair on a preferred network has a higher precedence in ICE + // than one on an un-preferred network, regardless of priority or network + // cost. + rtc::Optional network_preference; + // Configure the SDP semantics used by this PeerConnection. Note that the // WebRTC 1.0 specification requires kUnifiedPlan semantics. The // RtpTransceiver API is only available with kUnifiedPlan semantics. diff --git a/p2p/base/icetransportinternal.cc b/p2p/base/icetransportinternal.cc index e4e56fe39f..1edbf63a72 100644 --- a/p2p/base/icetransportinternal.cc +++ b/p2p/base/icetransportinternal.cc @@ -21,7 +21,8 @@ IceConfig::IceConfig(int receiving_timeout_ms, int stable_writable_connection_ping_interval_ms, bool presume_writable_when_fully_relayed, int regather_on_failed_networks_interval_ms, - int receiving_switching_delay_ms) + int receiving_switching_delay_ms, + rtc::Optional network_preference) : receiving_timeout(receiving_timeout_ms), backup_connection_ping_interval(backup_connection_ping_interval), continual_gathering_policy(gathering_policy), @@ -32,7 +33,8 @@ IceConfig::IceConfig(int receiving_timeout_ms, presume_writable_when_fully_relayed(presume_writable_when_fully_relayed), regather_on_failed_networks_interval( regather_on_failed_networks_interval_ms), - receiving_switching_delay(receiving_switching_delay_ms) {} + receiving_switching_delay(receiving_switching_delay_ms), + network_preference(network_preference) {} IceConfig::~IceConfig() = default; diff --git a/p2p/base/icetransportinternal.h b/p2p/base/icetransportinternal.h index 787f66e2a4..6888992618 100644 --- a/p2p/base/icetransportinternal.h +++ b/p2p/base/icetransportinternal.h @@ -115,6 +115,8 @@ struct IceConfig { // Measure in milliseconds. rtc::Optional ice_check_min_interval; + rtc::Optional network_preference; + IceConfig(); IceConfig(int receiving_timeout_ms, int backup_connection_ping_interval, @@ -123,7 +125,8 @@ struct IceConfig { int stable_writable_connection_ping_interval_ms, bool presume_writable_when_fully_relayed, int regather_on_failed_networks_interval_ms, - int receiving_switching_delay_ms); + int receiving_switching_delay_ms, + rtc::Optional network_preference); ~IceConfig(); }; diff --git a/p2p/base/p2ptransportchannel.cc b/p2p/base/p2ptransportchannel.cc index ddd240322e..02b943a401 100644 --- a/p2p/base/p2ptransportchannel.cc +++ b/p2p/base/p2ptransportchannel.cc @@ -61,6 +61,35 @@ cricket::PortInterface::CandidateOrigin GetOrigin(cricket::PortInterface* port, return cricket::PortInterface::ORIGIN_OTHER_PORT; } +// TODO(qingsi) Use an enum to replace the following constants for all +// comparision results. +static constexpr int a_is_better = 1; +static constexpr int b_is_better = -1; +static constexpr int a_and_b_equal = 0; + +bool LocalCandidateUsesPreferredNetwork( + const cricket::Connection* conn, + rtc::Optional network_preference) { + rtc::AdapterType network_type = conn->port()->Network()->type(); + return network_preference.has_value() && (network_type == network_preference); +} + +int CompareCandidatePairsByNetworkPreference( + const cricket::Connection* a, + const cricket::Connection* b, + rtc::Optional network_preference) { + bool a_uses_preferred_network = + LocalCandidateUsesPreferredNetwork(a, network_preference); + bool b_uses_preferred_network = + LocalCandidateUsesPreferredNetwork(b, network_preference); + if (a_uses_preferred_network && !b_uses_preferred_network) { + return a_is_better; + } else if (!a_uses_preferred_network && b_uses_preferred_network) { + return b_is_better; + } + return a_and_b_equal; +} + } // unnamed namespace namespace cricket { @@ -98,9 +127,6 @@ static const int DEFAULT_REGATHER_ON_FAILED_NETWORKS_INTERVAL = 5 * 60 * 1000; static constexpr int DEFAULT_BACKUP_CONNECTION_PING_INTERVAL = 25 * 1000; -static constexpr int a_is_better = 1; -static constexpr int b_is_better = -1; - bool IceCredentialsChanged(const std::string& old_ufrag, const std::string& old_pwd, const std::string& new_ufrag, @@ -135,7 +161,8 @@ P2PTransportChannel::P2PTransportChannel(const std::string& transport_name, STRONG_AND_STABLE_WRITABLE_CONNECTION_PING_INTERVAL, true /* presume_writable_when_fully_relayed */, DEFAULT_REGATHER_ON_FAILED_NETWORKS_INTERVAL, - RECEIVING_SWITCHING_DELAY) { + RECEIVING_SWITCHING_DELAY, + rtc::nullopt) { uint32_t weak_ping_interval = ::strtoul( webrtc::field_trial::FindFullName("WebRTC-StunInterPacketDelay").c_str(), nullptr, 10); @@ -218,11 +245,12 @@ bool P2PTransportChannel::ShouldSwitchSelectedConnection( return true; } - // Do not switch to a connection that is not receiving if it has higher cost - // because it may be just spuriously better. - if (new_connection->ComputeNetworkCost() > - selected_connection_->ComputeNetworkCost() && - !new_connection->receiving()) { + // Do not switch to a connection that is not receiving if it is not on a + // preferred network or it has higher cost because it may be just spuriously + // better. + int compare_a_b_by_networks = CompareCandidatePairNetworks( + new_connection, selected_connection_, config_.network_preference); + if (compare_a_b_by_networks == b_is_better && !new_connection->receiving()) { return false; } @@ -497,6 +525,14 @@ void P2PTransportChannel::SetIceConfig(const IceConfig& config) { RTC_LOG(LS_INFO) << "Set min ping interval to " << *config_.ice_check_min_interval; } + + if (config_.network_preference != config.network_preference) { + config_.network_preference = config.network_preference; + RTC_LOG(LS_INFO) << "Set network preference to " + << (config_.network_preference.has_value() + ? config_.network_preference.value() + : 0); + } } const IceConfig& P2PTransportChannel::config() const { @@ -1150,6 +1186,30 @@ void P2PTransportChannel::MaybeStartPinging() { } } +int P2PTransportChannel::CompareCandidatePairNetworks( + const Connection* a, + const Connection* b, + rtc::Optional network_preference) const { + int compare_a_b_by_network_preference = + CompareCandidatePairsByNetworkPreference(a, b, + config_.network_preference); + // The network preference has a higher precedence than the network cost. + if (compare_a_b_by_network_preference != a_and_b_equal) { + return compare_a_b_by_network_preference; + } + + uint32_t a_cost = a->ComputeNetworkCost(); + uint32_t b_cost = b->ComputeNetworkCost(); + // Prefer lower network cost. + if (a_cost < b_cost) { + return a_is_better; + } + if (a_cost > b_cost) { + return b_is_better; + } + return a_and_b_equal; +} + // Compare two connections based on their writing, receiving, and connected // states. int P2PTransportChannel::CompareConnectionStates( @@ -1230,15 +1290,10 @@ int P2PTransportChannel::CompareConnectionStates( int P2PTransportChannel::CompareConnectionCandidates( const Connection* a, const Connection* b) const { - // Prefer lower network cost. - uint32_t a_cost = a->ComputeNetworkCost(); - uint32_t b_cost = b->ComputeNetworkCost(); - // Smaller cost is better. - if (a_cost < b_cost) { - return a_is_better; - } - if (a_cost > b_cost) { - return b_is_better; + int compare_a_b_by_networks = + CompareCandidatePairNetworks(a, b, config_.network_preference); + if (compare_a_b_by_networks != a_and_b_equal) { + return compare_a_b_by_networks; } // Compare connection priority. Lower values get sorted last. diff --git a/p2p/base/p2ptransportchannel.h b/p2p/base/p2ptransportchannel.h index c2febbf1d3..ed0be2e111 100644 --- a/p2p/base/p2ptransportchannel.h +++ b/p2p/base/p2ptransportchannel.h @@ -195,6 +195,11 @@ class P2PTransportChannel : public IceTransportInternal, // that's pingable. void MaybeStartPinging(); + int CompareCandidatePairNetworks( + const Connection* a, + const Connection* b, + rtc::Optional network_preference) const; + // The methods below return a positive value if |a| is preferable to |b|, // a negative value if |b| is preferable, and 0 if they're equally preferable. // If |receiving_unchanged_threshold| is set, then when |b| is receiving and diff --git a/pc/peerconnection.cc b/pc/peerconnection.cc index a6297616b9..0596869eba 100644 --- a/pc/peerconnection.cc +++ b/pc/peerconnection.cc @@ -603,6 +603,7 @@ bool PeerConnectionInterface::RTCConfiguration::operator==( rtc::Optional ice_regather_interval_range; webrtc::TurnCustomizer* turn_customizer; SdpSemantics sdp_semantics; + rtc::Optional network_preference; }; static_assert(sizeof(stuff_being_tested_for_equality) == sizeof(*this), "Did you add something to RTCConfiguration and forget to " @@ -639,7 +640,8 @@ bool PeerConnectionInterface::RTCConfiguration::operator==( ice_check_min_interval == o.ice_check_min_interval && ice_regather_interval_range == o.ice_regather_interval_range && turn_customizer == o.turn_customizer && - sdp_semantics == o.sdp_semantics; + sdp_semantics == o.sdp_semantics && + network_preference == o.network_preference; } bool PeerConnectionInterface::RTCConfiguration::operator!=( @@ -2519,6 +2521,7 @@ bool PeerConnection::SetConfiguration(const RTCConfiguration& configuration, modified_config.prune_turn_ports = configuration.prune_turn_ports; modified_config.ice_check_min_interval = configuration.ice_check_min_interval; modified_config.turn_customizer = configuration.turn_customizer; + modified_config.network_preference = configuration.network_preference; if (configuration != modified_config) { RTC_LOG(LS_ERROR) << "Modifying the configuration in an unsupported way."; return SafeSetError(RTCErrorType::INVALID_MODIFICATION, error); @@ -4613,6 +4616,7 @@ cricket::IceConfig PeerConnection::ParseIceConfig( RTC_NOTREACHED(); gathering_policy = cricket::GATHER_ONCE; } + cricket::IceConfig ice_config; ice_config.receiving_timeout = config.ice_connection_receiving_timeout; ice_config.prioritize_most_likely_candidate_pairs = @@ -4625,6 +4629,7 @@ cricket::IceConfig PeerConnection::ParseIceConfig( ice_config.ice_check_min_interval = config.ice_check_min_interval; ice_config.regather_all_networks_interval_range = config.ice_regather_interval_range; + ice_config.network_preference = config.network_preference; return ice_config; } diff --git a/sdk/android/api/org/webrtc/PeerConnection.java b/sdk/android/api/org/webrtc/PeerConnection.java index 20b06d5c08..c6598b76d1 100644 --- a/sdk/android/api/org/webrtc/PeerConnection.java +++ b/sdk/android/api/org/webrtc/PeerConnection.java @@ -304,6 +304,16 @@ public class PeerConnection { /** Java version of PeerConnectionInterface.CandidateNetworkPolicy */ public enum CandidateNetworkPolicy { ALL, LOW_COST } + // Keep in sync with webrtc/rtc_base/network_constants.h. + public enum AdapterType { + UNKNOWN, + ETHERNET, + WIFI, + CELLULAR, + VPN, + LOOPBACK, + } + /** Java version of rtc::KeyType */ public enum KeyType { RSA, ECDSA } @@ -368,6 +378,9 @@ public class PeerConnection { public Integer screencastMinBitrate; public Boolean combinedAudioVideoBwe; public Boolean enableDtlsSrtp; + // Use "Unknown" to represent no preference of adapter types, not the + // preference of adapters of unknown types. + public AdapterType networkPreference; // This is an optional wrapper for the C++ webrtc::TurnCustomizer. public TurnCustomizer turnCustomizer; @@ -403,6 +416,7 @@ public class PeerConnection { screencastMinBitrate = null; combinedAudioVideoBwe = null; enableDtlsSrtp = null; + networkPreference = AdapterType.UNKNOWN; } @CalledByNative("RTCConfiguration") @@ -544,6 +558,11 @@ public class PeerConnection { Boolean getEnableDtlsSrtp() { return enableDtlsSrtp; } + + @CalledByNative("RTCConfiguration") + AdapterType getNetworkPreference() { + return networkPreference; + } }; private final List localStreams = new ArrayList<>(); diff --git a/sdk/android/api/org/webrtc/PeerConnectionFactory.java b/sdk/android/api/org/webrtc/PeerConnectionFactory.java index 90b4cb6f28..71e4b6c388 100644 --- a/sdk/android/api/org/webrtc/PeerConnectionFactory.java +++ b/sdk/android/api/org/webrtc/PeerConnectionFactory.java @@ -95,6 +95,8 @@ public class PeerConnectionFactory { public static class Options { // Keep in sync with webrtc/rtc_base/network.h! + // + // These bit fields are defined for |networkIgnoreMask| below. static final int ADAPTER_TYPE_UNKNOWN = 0; static final int ADAPTER_TYPE_ETHERNET = 1 << 0; static final int ADAPTER_TYPE_WIFI = 1 << 1; diff --git a/sdk/android/src/jni/pc/icecandidate.cc b/sdk/android/src/jni/pc/icecandidate.cc index e6fad74b8b..84e0f6e7c6 100644 --- a/sdk/android/src/jni/pc/icecandidate.cc +++ b/sdk/android/src/jni/pc/icecandidate.cc @@ -207,5 +207,32 @@ PeerConnectionInterface::TlsCertPolicy JavaToNativeTlsCertPolicy( return PeerConnectionInterface::kTlsCertPolicySecure; } +rtc::Optional JavaToNativeNetworkPreference( + JNIEnv* jni, + const JavaRef& j_network_preference) { + std::string enum_name = GetJavaEnumName(jni, j_network_preference); + + if (enum_name == "UNKNOWN") + return rtc::nullopt; + + if (enum_name == "ETHERNET") + return rtc::ADAPTER_TYPE_ETHERNET; + + if (enum_name == "WIFI") + return rtc::ADAPTER_TYPE_WIFI; + + if (enum_name == "CELLULAR") + return rtc::ADAPTER_TYPE_CELLULAR; + + if (enum_name == "VPN") + return rtc::ADAPTER_TYPE_VPN; + + if (enum_name == "LOOPBACK") + return rtc::ADAPTER_TYPE_LOOPBACK; + + RTC_CHECK(false) << "Unexpected NetworkPreference enum_name " << enum_name; + return rtc::nullopt; +} + } // namespace jni } // namespace webrtc diff --git a/sdk/android/src/jni/pc/icecandidate.h b/sdk/android/src/jni/pc/icecandidate.h index 324aaa6130..be4d27ca17 100644 --- a/sdk/android/src/jni/pc/icecandidate.h +++ b/sdk/android/src/jni/pc/icecandidate.h @@ -75,6 +75,10 @@ PeerConnectionInterface::TlsCertPolicy JavaToNativeTlsCertPolicy( JNIEnv* jni, const JavaRef& j_ice_server_tls_cert_policy); +rtc::Optional JavaToNativeNetworkPreference( + JNIEnv* jni, + const JavaRef& j_network_preference); + } // namespace jni } // namespace webrtc diff --git a/sdk/android/src/jni/pc/peerconnection.cc b/sdk/android/src/jni/pc/peerconnection.cc index 4055467b1f..f6f3932fa5 100644 --- a/sdk/android/src/jni/pc/peerconnection.cc +++ b/sdk/android/src/jni/pc/peerconnection.cc @@ -121,6 +121,8 @@ void JavaToNativeRTCConfiguration( Java_RTCConfiguration_getContinualGatheringPolicy(jni, j_rtc_config); ScopedJavaLocalRef j_turn_customizer = Java_RTCConfiguration_getTurnCustomizer(jni, j_rtc_config); + ScopedJavaLocalRef j_network_preference = + Java_RTCConfiguration_getNetworkPreference(jni, j_rtc_config); rtc_config->type = JavaToNativeIceTransportsType(jni, j_ice_transports_type); rtc_config->bundle_policy = JavaToNativeBundlePolicy(jni, j_bundle_policy); @@ -184,6 +186,8 @@ void JavaToNativeRTCConfiguration( jni, Java_RTCConfiguration_getCombinedAudioVideoBwe(jni, j_rtc_config)); rtc_config->enable_dtls_srtp = JavaToNativeOptionalBool( jni, Java_RTCConfiguration_getEnableDtlsSrtp(jni, j_rtc_config)); + rtc_config->network_preference = + JavaToNativeNetworkPreference(jni, j_network_preference); } rtc::KeyType GetRtcConfigKeyType(JNIEnv* env,