diff --git a/webrtc/api/java/jni/androidnetworkmonitor_jni.cc b/webrtc/api/java/jni/androidnetworkmonitor_jni.cc index a35da0c978..6362764fcc 100644 --- a/webrtc/api/java/jni/androidnetworkmonitor_jni.cc +++ b/webrtc/api/java/jni/androidnetworkmonitor_jni.cc @@ -313,6 +313,7 @@ rtc::AdapterType AndroidNetworkMonitor::GetAdapterType( const std::string& if_name) { auto iter = adapter_type_by_name_.find(if_name); if (iter == adapter_type_by_name_.end()) { + LOG(LS_WARNING) << "Get adapter type for an unknown interface: " << if_name; return rtc::ADAPTER_TYPE_UNKNOWN; } return iter->second; diff --git a/webrtc/api/webrtcsdp.cc b/webrtc/api/webrtcsdp.cc index 5fd200a576..a308a08414 100644 --- a/webrtc/api/webrtcsdp.cc +++ b/webrtc/api/webrtcsdp.cc @@ -126,6 +126,7 @@ static const char kAttributeCandidateRport[] = "rport"; static const char kAttributeCandidateUfrag[] = "ufrag"; static const char kAttributeCandidatePwd[] = "pwd"; static const char kAttributeCandidateGeneration[] = "generation"; +static const char kAttributeCandidateNetworkCost[] = "network-cost"; static const char kAttributeFingerprint[] = "fingerprint"; static const char kAttributeSetup[] = "setup"; static const char kAttributeFmtp[] = "fmtp"; @@ -1062,6 +1063,7 @@ bool ParseCandidate(const std::string& message, Candidate* candidate, std::string username; std::string password; uint32_t generation = 0; + uint32_t network_cost = 0; for (size_t i = current_position; i + 1 < fields.size(); ++i) { // RFC 5245 // *(SP extension-att-name SP extension-att-value) @@ -1073,6 +1075,10 @@ bool ParseCandidate(const std::string& message, Candidate* candidate, username = fields[++i]; } else if (fields[i] == kAttributeCandidatePwd) { password = fields[++i]; + } else if (fields[i] == kAttributeCandidateNetworkCost) { + if (!GetValueFromString(first_line, fields[++i], &network_cost, error)) { + return false; + } } else { // Skip the unknown extension. ++i; @@ -1084,6 +1090,7 @@ bool ParseCandidate(const std::string& message, Candidate* candidate, generation, foundation); candidate->set_related_address(related_address); candidate->set_tcptype(tcptype); + candidate->set_network_cost(std::min(network_cost, cricket::kMaxNetworkCost)); return true; } @@ -1758,6 +1765,9 @@ void BuildCandidate(const std::vector& candidates, if (include_ufrag && !it->username().empty()) { os << " " << kAttributeCandidateUfrag << " " << it->username(); } + if (it->network_cost() > 0) { + os << " " << kAttributeCandidateNetworkCost << " " << it->network_cost(); + } AddLine(os.str(), message); } diff --git a/webrtc/p2p/base/candidate.h b/webrtc/p2p/base/candidate.h index ac7acabf05..ddfeef0323 100644 --- a/webrtc/p2p/base/candidate.h +++ b/webrtc/p2p/base/candidate.h @@ -27,6 +27,8 @@ namespace cricket { +const uint32_t kMaxNetworkCost = 999; + // Candidate for ICE based connection discovery. class Candidate { @@ -138,6 +140,15 @@ class Candidate { ist >> generation_; } + // |network_cost| measures the cost/penalty of using this candidate. A network + // cost of 0 indicates this candidate can be used freely. A value of + // |kMaxNetworkCost| indicates it should be used only as the last resort. + void set_network_cost(uint32_t network_cost) { + ASSERT(network_cost <= kMaxNetworkCost); + network_cost_ = network_cost; + } + uint32_t network_cost() const { return network_cost_; } + const std::string& foundation() const { return foundation_; } @@ -211,10 +222,10 @@ class Candidate { std::ostringstream ost; std::string address = sensitive ? address_.ToSensitiveString() : address_.ToString(); - ost << "Cand[" << foundation_ << ":" << component_ << ":" - << protocol_ << ":" << priority_ << ":" - << address << ":" << type_ << ":" << related_address_ << ":" - << username_ << ":" << password_ << "]"; + ost << "Cand[" << foundation_ << ":" << component_ << ":" << protocol_ + << ":" << priority_ << ":" << address << ":" << type_ << ":" + << related_address_ << ":" << username_ << ":" << password_ << ":" + << network_cost_ << "]"; return ost.str(); } @@ -233,6 +244,7 @@ class Candidate { std::string foundation_; rtc::SocketAddress related_address_; std::string tcptype_; + uint32_t network_cost_ = 0; }; // Used during parsing and writing to map component to channel name diff --git a/webrtc/p2p/base/p2ptransportchannel.cc b/webrtc/p2p/base/p2ptransportchannel.cc index 952cfab747..87a50bc93a 100644 --- a/webrtc/p2p/base/p2ptransportchannel.cc +++ b/webrtc/p2p/base/p2ptransportchannel.cc @@ -39,9 +39,20 @@ cricket::PortInterface::CandidateOrigin GetOrigin(cricket::PortInterface* port, return cricket::PortInterface::ORIGIN_OTHER_PORT; } -// Compares two connections based only on static information about them. +// Compares two connections based only on the candidate and network information. +// Returns positive if |a| is better than |b|. int CompareConnectionCandidates(cricket::Connection* a, cricket::Connection* b) { + uint32_t a_cost = a->ComputeNetworkCost(); + uint32_t b_cost = b->ComputeNetworkCost(); + // Smaller cost is better. + if (a_cost < b_cost) { + return 1; + } + if (a_cost > b_cost) { + return -1; + } + // Compare connection priority. Lower values get sorted last. if (a->priority() > b->priority()) return 1; @@ -552,8 +563,6 @@ void P2PTransportChannel::OnUnknownAddress( } } else { // Create a new candidate with this address. - int remote_candidate_priority; - // The priority of the candidate is set to the PRIORITY attribute // from the request. const StunUInt32Attribute* priority_attr = @@ -566,7 +575,11 @@ void P2PTransportChannel::OnUnknownAddress( STUN_ERROR_REASON_BAD_REQUEST); return; } - remote_candidate_priority = priority_attr->value(); + int remote_candidate_priority = priority_attr->value(); + + const StunUInt32Attribute* cost_attr = + stun_msg->GetUInt32(STUN_ATTR_NETWORK_COST); + uint32_t network_cost = (cost_attr) ? cost_attr->value() : 0; // RFC 5245 // If the source transport address of the request does not match any @@ -581,8 +594,8 @@ void P2PTransportChannel::OnUnknownAddress( // from the foundation for all other remote candidates. remote_candidate.set_foundation( rtc::ToString(rtc::ComputeCrc32(remote_candidate.id()))); - remote_candidate.set_priority(remote_candidate_priority); + remote_candidate.set_network_cost(network_cost); } // RFC5245, the agent constructs a pair whose local candidate is equal to @@ -1311,7 +1324,7 @@ void P2PTransportChannel::PingConnection(Connection* conn) { if (remote_ice_mode_ == ICEMODE_FULL && ice_role_ == ICEROLE_CONTROLLING) { use_candidate = (conn == best_connection_) || (best_connection_ == NULL) || (!best_connection_->writable()) || - (conn->priority() > best_connection_->priority()); + (CompareConnectionCandidates(best_connection_, conn) < 0); } else if (remote_ice_mode_ == ICEMODE_LITE && conn == best_connection_) { use_candidate = best_connection_->writable(); } diff --git a/webrtc/p2p/base/p2ptransportchannel_unittest.cc b/webrtc/p2p/base/p2ptransportchannel_unittest.cc index 90ddd43714..ab17cfb55d 100644 --- a/webrtc/p2p/base/p2ptransportchannel_unittest.cc +++ b/webrtc/p2p/base/p2ptransportchannel_unittest.cc @@ -368,6 +368,13 @@ class P2PTransportChannelTestBase : public testing::Test, void AddAddress(int endpoint, const SocketAddress& addr) { GetEndpoint(endpoint)->network_manager_.AddInterface(addr); } + void AddAddress(int endpoint, + const SocketAddress& addr, + const std::string& ifname, + rtc::AdapterType adapter_type) { + GetEndpoint(endpoint)->network_manager_.AddInterface(addr, ifname, + adapter_type); + } void RemoveAddress(int endpoint, const SocketAddress& addr) { GetEndpoint(endpoint)->network_manager_.RemoveInterface(addr); } @@ -1650,6 +1657,68 @@ TEST_F(P2PTransportChannelMultihomedTest, TestFailoverControllingSide) { DestroyChannels(); } +// Tests that a Wifi-Wifi connection has the highest precedence. +TEST_F(P2PTransportChannelMultihomedTest, TestPreferWifiToWifiConnection) { + // The interface names are chosen so that |cellular| would have higher + // candidate priority if it is not for the network type. + auto& wifi = kAlternateAddrs; + auto& cellular = kPublicAddrs; + AddAddress(0, wifi[0], "test0", rtc::ADAPTER_TYPE_WIFI); + AddAddress(0, cellular[0], "test1", rtc::ADAPTER_TYPE_CELLULAR); + AddAddress(1, wifi[1], "test0", rtc::ADAPTER_TYPE_WIFI); + AddAddress(1, cellular[1], "test1", rtc::ADAPTER_TYPE_CELLULAR); + + // Use only local ports for simplicity. + SetAllocatorFlags(0, kOnlyLocalPorts); + SetAllocatorFlags(1, kOnlyLocalPorts); + + // Create channels and let them go writable, as usual. + CreateChannels(1); + + EXPECT_TRUE_WAIT_MARGIN(ep1_ch1()->receiving() && ep1_ch1()->writable() && + ep2_ch1()->receiving() && ep2_ch1()->writable(), + 1000, 1000); + // Need to wait to make sure the connections on both networks are writable. + EXPECT_TRUE_WAIT(ep1_ch1()->best_connection() && + LocalCandidate(ep1_ch1())->address().EqualIPs(wifi[0]) && + RemoteCandidate(ep1_ch1())->address().EqualIPs(wifi[1]), + 1000); + EXPECT_TRUE_WAIT(ep2_ch1()->best_connection() && + LocalCandidate(ep2_ch1())->address().EqualIPs(wifi[1]) && + RemoteCandidate(ep2_ch1())->address().EqualIPs(wifi[0]), + 1000); +} + +// Tests that a Wifi-Cellular connection has higher precedence than +// a Cellular-Cellular connection. +TEST_F(P2PTransportChannelMultihomedTest, TestPreferWifiOverCellularNetwork) { + // The interface names are chosen so that |cellular| would have higher + // candidate priority if it is not for the network type. + auto& wifi = kAlternateAddrs; + auto& cellular = kPublicAddrs; + AddAddress(0, cellular[0], "test1", rtc::ADAPTER_TYPE_CELLULAR); + AddAddress(1, wifi[1], "test0", rtc::ADAPTER_TYPE_WIFI); + AddAddress(1, cellular[1], "test1", rtc::ADAPTER_TYPE_CELLULAR); + + // Use only local ports for simplicity. + SetAllocatorFlags(0, kOnlyLocalPorts); + SetAllocatorFlags(1, kOnlyLocalPorts); + + // Create channels and let them go writable, as usual. + CreateChannels(1); + + EXPECT_TRUE_WAIT_MARGIN(ep1_ch1()->receiving() && ep1_ch1()->writable() && + ep2_ch1()->receiving() && ep2_ch1()->writable(), + 1000, 1000); + // Need to wait to make sure the connections on both networks are writable. + EXPECT_TRUE_WAIT(ep1_ch1()->best_connection() && + RemoteCandidate(ep1_ch1())->address().EqualIPs(wifi[1]), + 1000); + EXPECT_TRUE_WAIT(ep2_ch1()->best_connection() && + LocalCandidate(ep2_ch1())->address().EqualIPs(wifi[1]), + 1000); +} + // Test that the backup connection is pinged at a rate no faster than // what was configured. TEST_F(P2PTransportChannelMultihomedTest, TestPingBackupConnectionRate) { diff --git a/webrtc/p2p/base/port.cc b/webrtc/p2p/base/port.cc index 7993cc02bc..4d95279bba 100644 --- a/webrtc/p2p/base/port.cc +++ b/webrtc/p2p/base/port.cc @@ -195,6 +195,10 @@ void Port::Construct() { ice_username_fragment_ = rtc::CreateRandomString(ICE_UFRAG_LENGTH); password_ = rtc::CreateRandomString(ICE_PWD_LENGTH); } + // TODO(honghaiz): Make it configurable from user setting. + network_cost_ = + (network_->type() == rtc::ADAPTER_TYPE_CELLULAR) ? kMaxNetworkCost : 0; + LOG_J(LS_INFO, this) << "Port created"; } @@ -250,6 +254,7 @@ void Port::AddAddress(const rtc::SocketAddress& address, c.set_password(password_); c.set_network_name(network_->name()); c.set_network_type(network_->type()); + c.set_network_cost(network_cost_); c.set_generation(generation_); c.set_related_address(related_address); c.set_foundation( @@ -692,6 +697,11 @@ class ConnectionRequest : public StunRequest { static_cast(connection_->pings_since_last_response_.size() - 1))); } + uint32_t network_cost = connection_->port()->network_cost(); + if (network_cost > 0) { + request->AddAttribute( + new StunUInt32Attribute(STUN_ATTR_NETWORK_COST, network_cost)); + } // Adding ICE_CONTROLLED or ICE_CONTROLLING attribute based on the role. if (connection_->port()->GetIceRole() == ICEROLE_CONTROLLING) { @@ -1151,6 +1161,11 @@ std::string Connection::ToDebugId() const { return ss.str(); } +uint32_t Connection::ComputeNetworkCost() const { + // TODO(honghaiz): Will add rtt as part of the network cost. + return local_candidate().network_cost() + remote_candidate_.network_cost(); +} + std::string Connection::ToString() const { const char CONNECT_STATE_ABBREV[2] = { '-', // not connected (false) @@ -1389,6 +1404,7 @@ void Connection::MaybeAddPrflxCandidate(ConnectionRequest* request, new_local_candidate.set_password(local_candidate().password()); new_local_candidate.set_network_name(local_candidate().network_name()); new_local_candidate.set_network_type(local_candidate().network_type()); + new_local_candidate.set_network_cost(local_candidate().network_cost()); new_local_candidate.set_related_address(local_candidate().address()); new_local_candidate.set_foundation(ComputeFoundation( PRFLX_PORT_TYPE, local_candidate().protocol(), diff --git a/webrtc/p2p/base/port.h b/webrtc/p2p/base/port.h index 436b1e7faa..95abe86464 100644 --- a/webrtc/p2p/base/port.h +++ b/webrtc/p2p/base/port.h @@ -296,6 +296,7 @@ class Port : public PortInterface, public rtc::MessageHandler, void set_candidate_filter(uint32_t candidate_filter) { candidate_filter_ = candidate_filter; } + int32_t network_cost() const { return network_cost_; } protected: enum { @@ -395,6 +396,11 @@ class Port : public PortInterface, public rtc::MessageHandler, // TurnPort will hide raddr to avoid local address leakage. uint32_t candidate_filter_; + // A virtual cost perceived by the user, usually based on the network type + // (WiFi. vs. Cellular). It takes precedence over the priority when + // comparing two connections. + uint32_t network_cost_; + friend class Connection; }; @@ -560,6 +566,8 @@ class Connection : public rtc::MessageHandler, IceMode remote_ice_mode() const { return remote_ice_mode_; } + uint32_t ComputeNetworkCost() const; + // Update the ICE password of the remote candidate if |ice_ufrag| matches // the candidate's ufrag, and the candidate's passwrod has not been set. void MaybeSetRemoteIceCredentials(const std::string& ice_ufrag, diff --git a/webrtc/p2p/base/stun.h b/webrtc/p2p/base/stun.h index 75b89afb8a..9c8471304b 100644 --- a/webrtc/p2p/base/stun.h +++ b/webrtc/p2p/base/stun.h @@ -602,10 +602,11 @@ class TurnMessage : public StunMessage { // RFC 5245 ICE STUN attributes. enum IceAttributeType { - STUN_ATTR_PRIORITY = 0x0024, // UInt32 - STUN_ATTR_USE_CANDIDATE = 0x0025, // No content, Length = 0 - STUN_ATTR_ICE_CONTROLLED = 0x8029, // UInt64 - STUN_ATTR_ICE_CONTROLLING = 0x802A // UInt64 + STUN_ATTR_PRIORITY = 0x0024, // UInt32 + STUN_ATTR_USE_CANDIDATE = 0x0025, // No content, Length = 0 + STUN_ATTR_ICE_CONTROLLED = 0x8029, // UInt64 + STUN_ATTR_ICE_CONTROLLING = 0x802A, // UInt64 + STUN_ATTR_NETWORK_COST = 0xC057 // UInt32 }; // RFC 5245-defined errors. @@ -619,7 +620,9 @@ class IceMessage : public StunMessage { protected: virtual StunAttributeValueType GetAttributeValueType(int type) const { switch (type) { - case STUN_ATTR_PRIORITY: return STUN_VALUE_UINT32; + case STUN_ATTR_PRIORITY: + case STUN_ATTR_NETWORK_COST: + return STUN_VALUE_UINT32; case STUN_ATTR_USE_CANDIDATE: return STUN_VALUE_BYTE_STRING; case STUN_ATTR_ICE_CONTROLLED: return STUN_VALUE_UINT64; case STUN_ATTR_ICE_CONTROLLING: return STUN_VALUE_UINT64;