From 351d77b702d61eef28c4b6273e0c091ce177d1a0 Mon Sep 17 00:00:00 2001 From: Honghai Zhang Date: Fri, 20 May 2016 15:08:29 -0700 Subject: [PATCH] Update the type and cost of existing networks if the network monitor detects it after the native code does. Also set the network cost for ethernet, wifi, unknown, cellular network type to be 0, 10, 50, 900, so that unknown networks will have lower precedence than known networks with low cost (like Wifi) but higher precedence than known networks with high cost. And third, infer network type based on limited name matching in Android if there is no network monitor or network monitor did not find the type. BUG=webrtc:5890 R=pthatcher@chromium.org, pthatcher@webrtc.org Review URL: https://codereview.webrtc.org/1976683003 . Cr-Commit-Position: refs/heads/master@{#12833} --- .../api/java/jni/androidnetworkmonitor_jni.cc | 16 ++- webrtc/api/webrtcsdp.cc | 2 +- webrtc/base/network.cc | 63 +++++++--- webrtc/base/network.h | 30 ++++- webrtc/base/network_unittest.cc | 116 ++++++++++++++++++ webrtc/p2p/base/candidate.h | 6 +- webrtc/p2p/base/port.cc | 55 ++++++++- webrtc/p2p/base/port.h | 4 + webrtc/p2p/base/port_unittest.cc | 71 ++++++++++- webrtc/p2p/base/stunport.cc | 37 +++--- webrtc/p2p/base/stunport.h | 18 ++- webrtc/p2p/base/stunport_unittest.cc | 27 ++-- 12 files changed, 373 insertions(+), 72 deletions(-) diff --git a/webrtc/api/java/jni/androidnetworkmonitor_jni.cc b/webrtc/api/java/jni/androidnetworkmonitor_jni.cc index 6362764fcc..b7857e2ea9 100644 --- a/webrtc/api/java/jni/androidnetworkmonitor_jni.cc +++ b/webrtc/api/java/jni/androidnetworkmonitor_jni.cc @@ -268,13 +268,15 @@ int AndroidNetworkMonitor::BindSocketToNetwork(int socket_fd, void AndroidNetworkMonitor::OnNetworkConnected( const NetworkInformation& network_info) { - LOG(LS_INFO) << "Network connected: " << network_info.ToString(); worker_thread()->Invoke(rtc::Bind( &AndroidNetworkMonitor::OnNetworkConnected_w, this, network_info)); + // Fire SignalNetworksChanged to update the list of networks. + OnNetworksChanged(); } void AndroidNetworkMonitor::OnNetworkConnected_w( const NetworkInformation& network_info) { + LOG(LS_INFO) << "Network connected: " << network_info.ToString(); adapter_type_by_name_[network_info.interface_name] = AdapterTypeFromNetworkType(network_info.type); network_info_by_handle_[network_info.handle] = network_info; @@ -304,6 +306,8 @@ void AndroidNetworkMonitor::SetNetworkInfos( RTC_CHECK(thread_checker_.CalledOnValidThread()); network_handle_by_address_.clear(); network_info_by_handle_.clear(); + LOG(LS_INFO) << "Android network monitor found " << network_infos.size() + << " networks"; for (NetworkInformation network : network_infos) { OnNetworkConnected_w(network); } @@ -312,11 +316,13 @@ void AndroidNetworkMonitor::SetNetworkInfos( 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; + rtc::AdapterType type = (iter == adapter_type_by_name_.end()) + ? rtc::ADAPTER_TYPE_UNKNOWN + : iter->second; + if (type == rtc::ADAPTER_TYPE_UNKNOWN) { + LOG(LS_WARNING) << "Get an unknown type for the interface " << if_name; } - return iter->second; + return type; } rtc::NetworkMonitorInterface* diff --git a/webrtc/api/webrtcsdp.cc b/webrtc/api/webrtcsdp.cc index b86d9038dd..fa052419c0 100644 --- a/webrtc/api/webrtcsdp.cc +++ b/webrtc/api/webrtcsdp.cc @@ -1117,7 +1117,7 @@ bool ParseCandidate(const std::string& message, Candidate* candidate, if (!GetValueFromString(first_line, fields[++i], &network_cost, error)) { return false; } - network_cost = std::min(network_cost, cricket::kMaxNetworkCost); + network_cost = std::min(network_cost, rtc::kNetworkCostMax); } else { // Skip the unknown extension. ++i; diff --git a/webrtc/base/network.cc b/webrtc/base/network.cc index b6caaa8bbf..5031e967a5 100644 --- a/webrtc/base/network.cc +++ b/webrtc/base/network.cc @@ -292,6 +292,11 @@ void NetworkManagerBase::MergeNetworkList(const NetworkList& new_networks, Network* existing_net = existing->second; *changed = existing_net->SetIPs(kv.second.ips, *changed); merged_list.push_back(existing_net); + if (net->type() != ADAPTER_TYPE_UNKNOWN && + net->type() != existing_net->type()) { + existing_net->set_type(net->type()); + *changed = true; + } // If the existing network was not active, networks have changed. if (!existing_net->active()) { *changed = true; @@ -394,7 +399,7 @@ BasicNetworkManager::~BasicNetworkManager() { } void BasicNetworkManager::OnNetworksChanged() { - LOG(LS_VERBOSE) << "Network change was observed at the network manager"; + LOG(LS_INFO) << "Network change was observed"; UpdateNetworksOnce(); } @@ -452,24 +457,18 @@ void BasicNetworkManager::ConvertIfAddrs(struct ifaddrs* interfaces, reinterpret_cast(cursor->ifa_addr)->sin6_scope_id; } + AdapterType adapter_type = ADAPTER_TYPE_UNKNOWN; + if (cursor->ifa_flags & IFF_LOOPBACK) { + adapter_type = ADAPTER_TYPE_LOOPBACK; + } else { + adapter_type = GetAdapterTypeFromName(cursor->ifa_name); + } int prefix_length = CountIPMaskBits(mask); prefix = TruncateIP(ip, prefix_length); std::string key = MakeNetworkKey(std::string(cursor->ifa_name), prefix, prefix_length); - auto existing_network = current_networks.find(key); - if (existing_network == current_networks.end()) { - AdapterType adapter_type = ADAPTER_TYPE_UNKNOWN; - if (cursor->ifa_flags & IFF_LOOPBACK) { - adapter_type = ADAPTER_TYPE_LOOPBACK; - } else if (network_monitor_) { - adapter_type = network_monitor_->GetAdapterType(cursor->ifa_name); - } -#if defined(WEBRTC_IOS) - // Cell networks are pdp_ipN on iOS. - if (strncmp(cursor->ifa_name, "pdp_ip", 6) == 0) { - adapter_type = ADAPTER_TYPE_CELLULAR; - } -#endif + auto iter = current_networks.find(key); + if (iter == current_networks.end()) { // TODO(phoglund): Need to recognize other types as well. std::unique_ptr network( new Network(cursor->ifa_name, cursor->ifa_name, prefix, prefix_length, @@ -483,7 +482,11 @@ void BasicNetworkManager::ConvertIfAddrs(struct ifaddrs* interfaces, networks->push_back(network.release()); } } else { - (*existing_network).second->AddIP(ip); + Network* existing_network = iter->second; + existing_network->AddIP(ip); + if (adapter_type != ADAPTER_TYPE_UNKNOWN) { + existing_network->set_type(adapter_type); + } } } } @@ -787,6 +790,34 @@ void BasicNetworkManager::OnMessage(Message* msg) { } } +AdapterType BasicNetworkManager::GetAdapterTypeFromName( + const char* network_name) const { + // If there is a network_monitor, use it to get the adapter type. + // Otherwise, get the adapter type based on a few name matching rules. + if (network_monitor_) { + AdapterType type = network_monitor_->GetAdapterType(network_name); + if (type != ADAPTER_TYPE_UNKNOWN) { + return type; + } + } +#if defined(WEBRTC_IOS) + // Cell networks are pdp_ipN on iOS. + if (strncmp(network_name, "pdp_ip", 6) == 0) { + return ADAPTER_TYPE_CELLULAR; + } +#elif defined(WEBRTC_ANDROID) + if (strncmp(network_name, "rmnet", 5) == 0 || + strncmp(network_name, "v4-rmnet", 8) == 0) { + return ADAPTER_TYPE_CELLULAR; + } + if (strncmp(network_name, "wlan", 4) == 0) { + return ADAPTER_TYPE_WIFI; + } +#endif + + return ADAPTER_TYPE_UNKNOWN; +} + IPAddress BasicNetworkManager::QueryDefaultLocalAddress(int family) const { ASSERT(thread_ == Thread::Current()); ASSERT(thread_->socketserver() != nullptr); diff --git a/webrtc/base/network.h b/webrtc/base/network.h index a41da4a69a..05943fb912 100644 --- a/webrtc/base/network.h +++ b/webrtc/base/network.h @@ -37,6 +37,11 @@ class Network; class NetworkMonitorInterface; class Thread; +static const uint16_t kNetworkCostMax = 999; +static const uint16_t kNetworkCostHigh = 900; +static const uint16_t kNetworkCostUnknown = 50; +static const uint16_t kNetworkCostLow = 10; +static const uint16_t kNetworkCostMin = 0; // By default, ignore loopback interfaces on the host. const int kDefaultNetworkIgnoreMask = ADAPTER_TYPE_LOOPBACK; @@ -249,6 +254,8 @@ class BasicNetworkManager : public NetworkManagerBase, // Only updates the networks; does not reschedule the next update. void UpdateNetworksOnce(); + AdapterType GetAdapterTypeFromName(const char* network_name) const; + Thread* thread_; bool sent_first_update_; int start_count_; @@ -273,6 +280,7 @@ class Network { ~Network(); sigslot::signal1 SignalInactive; + sigslot::signal1 SignalTypeChanged; const DefaultLocalAddressProvider* default_local_address_provider() { return default_local_address_provider_; @@ -344,8 +352,28 @@ class Network { void set_ignored(bool ignored) { ignored_ = ignored; } AdapterType type() const { return type_; } - void set_type(AdapterType type) { type_ = type; } + void set_type(AdapterType type) { + if (type_ == type) { + return; + } + type_ = type; + SignalTypeChanged(this); + } + uint16_t GetCost() const { + switch (type_) { + case rtc::ADAPTER_TYPE_ETHERNET: + case rtc::ADAPTER_TYPE_LOOPBACK: + return kNetworkCostMin; + case rtc::ADAPTER_TYPE_WIFI: + case rtc::ADAPTER_TYPE_VPN: + return kNetworkCostLow; + case rtc::ADAPTER_TYPE_CELLULAR: + return kNetworkCostHigh; + default: + return kNetworkCostUnknown; + } + } // A unique id assigned by the network manager, which may be signaled // to the remote side in the candidate. uint16_t id() const { return id_; } diff --git a/webrtc/base/network_unittest.cc b/webrtc/base/network_unittest.cc index f3193e2b0f..9572c69746 100644 --- a/webrtc/base/network_unittest.cc +++ b/webrtc/base/network_unittest.cc @@ -34,6 +34,14 @@ class FakeNetworkMonitor : public NetworkMonitorBase { void Stop() override { started_ = false; } bool started() { return started_; } AdapterType GetAdapterType(const std::string& if_name) override { + // Note that the name matching rules are different from the + // GetAdapterTypeFromName in NetworkManager. + if (if_name.find("wifi") == 0) { + return ADAPTER_TYPE_WIFI; + } + if (if_name.find("cellular") == 0) { + return ADAPTER_TYPE_CELLULAR; + } return ADAPTER_TYPE_UNKNOWN; } @@ -102,6 +110,13 @@ class NetworkTest : public testing::Test, public sigslot::has_slots<> { network_manager.networks_map_.clear(); } + AdapterType GetAdapterType(BasicNetworkManager& network_manager) { + BasicNetworkManager::NetworkList list; + network_manager.GetNetworks(&list); + ASSERT(list.size() == 1u); + return list[0]->type(); + } + #if defined(WEBRTC_POSIX) // Separated from CreateNetworks for tests. static void CallConvertIfAddrs(const BasicNetworkManager& network_manager, @@ -144,6 +159,20 @@ class NetworkTest : public testing::Test, public sigslot::has_slots<> { return if_addr; } + struct ifaddrs* InstallIpv6Network(char* if_name, + const std::string& ipv6_address, + const std::string& ipv6_mask, + BasicNetworkManager& network_manager) { + ifaddrs* addr_list = nullptr; + addr_list = AddIpv6Address(addr_list, if_name, ipv6_address, ipv6_mask, 0); + NetworkManager::NetworkList result; + bool changed; + NetworkManager::Stats stats; + CallConvertIfAddrs(network_manager, addr_list, true, &result); + network_manager.MergeNetworkList(result, &changed, &stats); + return addr_list; + } + void ReleaseIfAddrs(struct ifaddrs* list) { struct ifaddrs* if_addr = list; while (if_addr != nullptr) { @@ -781,6 +810,93 @@ TEST_F(NetworkTest, TestConvertIfAddrsNotRunning) { CallConvertIfAddrs(manager, &list, true, &result); EXPECT_TRUE(result.empty()); } + +// Tests that the network type can be updated after the network monitor is +// started. +TEST_F(NetworkTest, TestGetAdapterTypeFromNetworkMonitor) { + char if_name1[20] = "wifi0"; + std::string ipv6_address1 = "1000:2000:3000:4000:0:0:0:1"; + std::string ipv6_address2 = "1000:2000:3000:8000:0:0:0:1"; + std::string ipv6_mask = "FFFF:FFFF:FFFF:FFFF::"; + BasicNetworkManager manager; + // A network created before the network monitor is started will get + // UNKNOWN type. + ifaddrs* addr_list = + InstallIpv6Network(if_name1, ipv6_address1, ipv6_mask, manager); + EXPECT_EQ(ADAPTER_TYPE_UNKNOWN, GetAdapterType(manager)); + ReleaseIfAddrs(addr_list); + // Note: Do not call ClearNetworks here in order to test that the type + // of an existing network can be changed after the network monitor starts + // and detects the network type correctly. + + // After the network monitor starts, the type will be updated. + FakeNetworkMonitorFactory* factory = new FakeNetworkMonitorFactory(); + NetworkMonitorFactory::SetFactory(factory); + // This brings up the hook with the network monitor. + manager.StartUpdating(); + // Add the same ipv6 address as before but it has the right network type + // detected by the network monitor now. + addr_list = InstallIpv6Network(if_name1, ipv6_address1, ipv6_mask, manager); + EXPECT_EQ(ADAPTER_TYPE_WIFI, GetAdapterType(manager)); + ReleaseIfAddrs(addr_list); + ClearNetworks(manager); + + // Add another network with the type inferred from the network monitor. + char if_name2[20] = "cellular0"; + addr_list = InstallIpv6Network(if_name2, ipv6_address2, ipv6_mask, manager); + EXPECT_EQ(ADAPTER_TYPE_CELLULAR, GetAdapterType(manager)); + ReleaseIfAddrs(addr_list); + ClearNetworks(manager); +} + +// Test that the network type can be determined based on name matching in +// a few cases. Note that UNKNOWN type for non-matching strings has been tested +// in the above test. +TEST_F(NetworkTest, TestGetAdapterTypeFromNameMatching) { + std::string ipv6_address1 = "1000:2000:3000:4000:0:0:0:1"; + std::string ipv6_address2 = "1000:2000:3000:8000:0:0:0:1"; + std::string ipv6_mask = "FFFF:FFFF:FFFF:FFFF::"; + BasicNetworkManager manager; + +#if defined(WEBRTC_IOS) + char if_name[20] = "pdp_ip0"; + ifaddrs* addr_list = + InstallIpv6Network(if_name, ipv6_address1, ipv6_mask, manager); + + EXPECT_EQ(ADAPTER_TYPE_CELLULAR, GetAdapterType(manager)); + ClearNetworks(manager); + ReleaseIfAddrs(addr_list); + +#elif defined(WEBRTC_ANDROID) + char if_name[20] = "rmnet0"; + ifaddrs* addr_list = + InstallIpv6Network(if_name, ipv6_address1, ipv6_mask, manager); + + EXPECT_EQ(ADAPTER_TYPE_CELLULAR, GetAdapterType(manager)); + ClearNetworks(manager); + ReleaseIfAddrs(addr_list); + + strcpy(if_name, "wlan1"); + addr_list = InstallIpv6Network(if_name, ipv6_address2, ipv6_mask, manager); + EXPECT_EQ(ADAPTER_TYPE_WIFI, GetAdapterType(manager)); + ClearNetworks(manager); + ReleaseIfAddrs(addr_list); + + strcpy(if_name, "v4-rmnet_data0"); + addr_list = InstallIpv6Network(if_name, ipv6_address2, ipv6_mask, manager); + EXPECT_EQ(ADAPTER_TYPE_CELLULAR, GetAdapterType(manager)); + ClearNetworks(manager); + ReleaseIfAddrs(addr_list); +#else + char if_name[20] = "wlan0"; + ifaddrs* addr_list = + InstallIpv6Network(if_name, ipv6_address1, ipv6_mask, manager); + + EXPECT_EQ(ADAPTER_TYPE_UNKNOWN, GetAdapterType(manager)); + ClearNetworks(manager); + ReleaseIfAddrs(addr_list); +#endif +} #endif // defined(WEBRTC_POSIX) #if defined(WEBRTC_LINUX) && !defined(WEBRTC_ANDROID) diff --git a/webrtc/p2p/base/candidate.h b/webrtc/p2p/base/candidate.h index b918344740..4eb05f0c63 100644 --- a/webrtc/p2p/base/candidate.h +++ b/webrtc/p2p/base/candidate.h @@ -27,8 +27,6 @@ namespace cricket { -const uint16_t kMaxNetworkCost = 999; - // Candidate for ICE based connection discovery. class Candidate { @@ -148,9 +146,9 @@ class Candidate { // |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. + // rtc::kNetworkCostMax indicates it should be used only as the last resort. void set_network_cost(uint16_t network_cost) { - ASSERT(network_cost <= kMaxNetworkCost); + ASSERT(network_cost <= rtc::kNetworkCostMax); network_cost_ = network_cost; } uint16_t network_cost() const { return network_cost_; } diff --git a/webrtc/p2p/base/port.cc b/webrtc/p2p/base/port.cc index fbc64f2e0a..bd7368bf3a 100644 --- a/webrtc/p2p/base/port.cc +++ b/webrtc/p2p/base/port.cc @@ -75,7 +75,7 @@ const int RTT_RATIO = 3; // 3 : 1 // The delay before we begin checking if this port is useless. const int kPortTimeoutDelay = 30 * 1000; // 30 seconds -} +} // namespace namespace cricket { @@ -196,11 +196,10 @@ void Port::Construct() { password_ = rtc::CreateRandomString(ICE_PWD_LENGTH); } network_->SignalInactive.connect(this, &Port::OnNetworkInactive); - // TODO(honghaiz): Make it configurable from user setting. - network_cost_ = - (network_->type() == rtc::ADAPTER_TYPE_CELLULAR) ? kMaxNetworkCost : 0; + network_->SignalTypeChanged.connect(this, &Port::OnNetworkTypeChanged); + network_cost_ = network_->GetCost(); - LOG_J(LS_INFO, this) << "Port created"; + LOG_J(LS_INFO, this) << "Port created with network cost " << network_cost_; } Port::~Port() { @@ -645,6 +644,12 @@ void Port::OnNetworkInactive(const rtc::Network* network) { SignalNetworkInactive(this); } +void Port::OnNetworkTypeChanged(const rtc::Network* network) { + ASSERT(network == network_); + + UpdateNetworkCost(); +} + std::string Port::ToString() const { std::stringstream ss; ss << "Port[" << std::hex << this << std::dec << ":" << content_name_ << ":" @@ -653,6 +658,29 @@ std::string Port::ToString() const { return ss.str(); } +// TODO(honghaiz): Make the network cost configurable from user setting. +void Port::UpdateNetworkCost() { + uint16_t new_cost = network_->GetCost(); + if (network_cost_ == new_cost) { + return; + } + LOG(LS_INFO) << "Network cost changed from " << network_cost_ + << " to " << new_cost + << ". Number of candidates created: " << candidates_.size() + << ". Number of connections created: " << connections_.size(); + network_cost_ = new_cost; + for (cricket::Candidate& candidate : candidates_) { + candidate.set_network_cost(network_cost_); + } + // Network cost change will affect the connection selection criteria. + // Signal the connection state change on each connection to force a + // re-sort in P2PTransportChannel. + for (auto kv : connections_) { + Connection* conn = kv.second; + conn->SignalStateChange(conn); + } +} + void Port::EnablePortPackets() { enable_port_packets_ = true; } @@ -1008,6 +1036,21 @@ void Connection::HandleBindingRequest(IceMessage* msg) { SignalNominated(this); } } + // Set the remote cost if the network_info attribute is available. + // Note: If packets are re-ordered, we may get incorrect network cost + // temporarily, but it should get the correct value shortly after that. + const StunUInt32Attribute* network_attr = + msg->GetUInt32(STUN_ATTR_NETWORK_INFO); + if (network_attr) { + uint32_t network_info = network_attr->value(); + uint16_t network_cost = static_cast(network_info); + if (network_cost != remote_candidate_.network_cost()) { + remote_candidate_.set_network_cost(network_cost); + // Network cost change will affect the connection ranking, so signal + // state change to force a re-sort in P2PTransportChannel. + SignalStateChange(this); + } + } } void Connection::OnReadyToSend() { @@ -1178,7 +1221,7 @@ std::string Connection::ToDebugId() const { 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(); + return port()->network_cost() + remote_candidate_.network_cost(); } std::string Connection::ToString() const { diff --git a/webrtc/p2p/base/port.h b/webrtc/p2p/base/port.h index 937f3cd028..e72e9dcc31 100644 --- a/webrtc/p2p/base/port.h +++ b/webrtc/p2p/base/port.h @@ -304,6 +304,8 @@ class Port : public PortInterface, public rtc::MessageHandler, MSG_FIRST_AVAILABLE }; + virtual void UpdateNetworkCost(); + void set_type(const std::string& type) { type_ = type; } void AddAddress(const rtc::SocketAddress& address, @@ -362,6 +364,8 @@ class Port : public PortInterface, public rtc::MessageHandler, void OnNetworkInactive(const rtc::Network* network); + void OnNetworkTypeChanged(const rtc::Network* network); + rtc::Thread* thread_; rtc::PacketSocketFactory* factory_; std::string type_; diff --git a/webrtc/p2p/base/port_unittest.cc b/webrtc/p2p/base/port_unittest.cc index efc2609401..a5cbd92a7b 100644 --- a/webrtc/p2p/base/port_unittest.cc +++ b/webrtc/p2p/base/port_unittest.cc @@ -759,7 +759,6 @@ class PortTest : public testing::Test, public sigslot::has_slots<> { return &nat_socket_factory1_; } - protected: rtc::VirtualSocketServer* vss() { return ss_.get(); } private: @@ -1762,11 +1761,69 @@ TEST_F(PortTest, TestUseCandidateAttribute) { ASSERT_TRUE(use_candidate_attr != NULL); } +// Tests that when the network type changes, the network cost of the port will +// change, the network cost of the local candidates will change. Also tests that +// the remote network costs are updated with the stun binding requests. +TEST_F(PortTest, TestNetworkCostChange) { + std::unique_ptr lport( + CreateTestPort(kLocalAddr1, "lfrag", "lpass")); + std::unique_ptr rport( + CreateTestPort(kLocalAddr2, "rfrag", "rpass")); + lport->SetIceRole(cricket::ICEROLE_CONTROLLING); + lport->SetIceTiebreaker(kTiebreaker1); + rport->SetIceRole(cricket::ICEROLE_CONTROLLED); + rport->SetIceTiebreaker(kTiebreaker2); + lport->PrepareAddress(); + rport->PrepareAddress(); + + // Default local port cost is rtc::kNetworkCostUnknown. + EXPECT_EQ(rtc::kNetworkCostUnknown, lport->network_cost()); + ASSERT_TRUE(!lport->Candidates().empty()); + for (const cricket::Candidate& candidate : lport->Candidates()) { + EXPECT_EQ(rtc::kNetworkCostUnknown, candidate.network_cost()); + } + + // Change the network type to wifi. + SetNetworkType(rtc::ADAPTER_TYPE_WIFI); + EXPECT_EQ(rtc::kNetworkCostLow, lport->network_cost()); + for (const cricket::Candidate& candidate : lport->Candidates()) { + EXPECT_EQ(rtc::kNetworkCostLow, candidate.network_cost()); + } + + // Add a connection and then change the network type. + Connection* lconn = + lport->CreateConnection(rport->Candidates()[0], Port::ORIGIN_MESSAGE); + // Change the network type to cellular. + SetNetworkType(rtc::ADAPTER_TYPE_CELLULAR); + EXPECT_EQ(rtc::kNetworkCostHigh, lport->network_cost()); + for (const cricket::Candidate& candidate : lport->Candidates()) { + EXPECT_EQ(rtc::kNetworkCostHigh, candidate.network_cost()); + } + + SetNetworkType(rtc::ADAPTER_TYPE_WIFI); + Connection* rconn = + rport->CreateConnection(lport->Candidates()[0], Port::ORIGIN_MESSAGE); + SetNetworkType(rtc::ADAPTER_TYPE_CELLULAR); + lconn->Ping(0); + // The rconn's remote candidate cost is rtc::kNetworkCostLow, but the ping + // contains an attribute of network cost of rtc::kNetworkCostHigh. Once the + // message is handled in rconn, The rconn's remote candidate will have cost + // rtc::kNetworkCostHigh; + EXPECT_EQ(rtc::kNetworkCostLow, rconn->remote_candidate().network_cost()); + ASSERT_TRUE_WAIT(lport->last_stun_msg() != NULL, 1000); + IceMessage* msg = lport->last_stun_msg(); + EXPECT_EQ(STUN_BINDING_REQUEST, msg->type()); + // Pass the binding request to rport. + rconn->OnReadPacket(lport->last_stun_buf()->data(), + lport->last_stun_buf()->size(), rtc::PacketTime()); + // Wait until rport sends the response and then check the remote network cost. + ASSERT_TRUE_WAIT(rport->last_stun_msg() != NULL, 1000); + EXPECT_EQ(rtc::kNetworkCostHigh, rconn->remote_candidate().network_cost()); +} + TEST_F(PortTest, TestNetworkInfoAttribute) { std::unique_ptr lport( CreateTestPort(kLocalAddr1, "lfrag", "lpass")); - // Set the network type for rport to be cellular so its cost will be 999. - SetNetworkType(rtc::ADAPTER_TYPE_CELLULAR); std::unique_ptr rport( CreateTestPort(kLocalAddr2, "rfrag", "rpass")); lport->SetIceRole(cricket::ICEROLE_CONTROLLING); @@ -1789,10 +1846,12 @@ TEST_F(PortTest, TestNetworkInfoAttribute) { ASSERT_TRUE(network_info_attr != NULL); uint32_t network_info = network_info_attr->value(); EXPECT_EQ(lnetwork_id, network_info >> 16); - // Default network cost is 0. - EXPECT_EQ(0U, network_info & 0xFFFF); + // Default network has unknown type and cost kNetworkCostUnknown. + EXPECT_EQ(rtc::kNetworkCostUnknown, network_info & 0xFFFF); + // Set the network type to be cellular so its cost will be kNetworkCostHigh. // Send a fake ping from rport to lport. + SetNetworkType(rtc::ADAPTER_TYPE_CELLULAR); uint16_t rnetwork_id = 8; rport->Network()->set_id(rnetwork_id); Connection* rconn = @@ -1804,7 +1863,7 @@ TEST_F(PortTest, TestNetworkInfoAttribute) { ASSERT_TRUE(network_info_attr != NULL); network_info = network_info_attr->value(); EXPECT_EQ(rnetwork_id, network_info >> 16); - EXPECT_EQ(cricket::kMaxNetworkCost, network_info & 0xFFFF); + EXPECT_EQ(rtc::kNetworkCostHigh, network_info & 0xFFFF); } // Test handling STUN messages. diff --git a/webrtc/p2p/base/stunport.cc b/webrtc/p2p/base/stunport.cc index 8ed8c44887..15c00957ef 100644 --- a/webrtc/p2p/base/stunport.cc +++ b/webrtc/p2p/base/stunport.cc @@ -25,22 +25,14 @@ namespace cricket { // TODO: Move these to a common place (used in relayport too) const int KEEPALIVE_DELAY = 10 * 1000; // 10 seconds - sort timeouts const int RETRY_TIMEOUT = 50 * 1000; // 50 seconds -// Lifetime chosen for STUN ports on low-cost networks. -const int INFINITE_LIFETIME = -1; -// Lifetime for STUN ports on high-cost networks: 2 minutes -const int HIGH_COST_PORT_KEEPALIVE_LIFETIME = 2 * 60 * 1000; // Handles a binding request sent to the STUN server. class StunBindingRequest : public StunRequest { public: StunBindingRequest(UDPPort* port, const rtc::SocketAddress& addr, - int64_t start_time, - int lifetime) - : port_(port), - server_addr_(addr), - start_time_(start_time), - lifetime_(lifetime) {} + int64_t start_time) + : port_(port), server_addr_(addr), start_time_(start_time) {} virtual ~StunBindingRequest() { } @@ -67,7 +59,7 @@ class StunBindingRequest : public StunRequest { // The keep-alive requests will be stopped after its lifetime has passed. if (WithinLifetime(rtc::TimeMillis())) { port_->requests_.SendDelayed( - new StunBindingRequest(port_, server_addr_, start_time_, lifetime_), + new StunBindingRequest(port_, server_addr_, start_time_), port_->stun_keepalive_delay()); } } @@ -89,7 +81,7 @@ class StunBindingRequest : public StunRequest { if (WithinLifetime(now) && rtc::TimeDiff(now, start_time_) < RETRY_TIMEOUT) { port_->requests_.SendDelayed( - new StunBindingRequest(port_, server_addr_, start_time_, lifetime_), + new StunBindingRequest(port_, server_addr_, start_time_), port_->stun_keepalive_delay()); } } @@ -105,14 +97,13 @@ class StunBindingRequest : public StunRequest { // Returns true if |now| is within the lifetime of the request (a negative // lifetime means infinite). bool WithinLifetime(int64_t now) const { - return lifetime_ < 0 || rtc::TimeDiff(now, start_time_) <= lifetime_; + int lifetime = port_->stun_keepalive_lifetime(); + return lifetime < 0 || rtc::TimeDiff(now, start_time_) <= lifetime; } UDPPort* port_; const rtc::SocketAddress server_addr_; int64_t start_time_; - // The time duration for which this request will be rescheduled. - int lifetime_; }; UDPPort::AddressResolver::AddressResolver( @@ -220,12 +211,7 @@ UDPPort::UDPPort(rtc::Thread* thread, } bool UDPPort::Init() { - // If this is a zero-cost network, it will keep on sending STUN binding - // requests indefinitely to keep the NAT binding alive. Otherwise, stop - // sending STUN binding requests after HIGH_COST_PORT_KEEPALIVE_LIFETIME. - stun_keepalive_lifetime_ = (network_cost() == 0) - ? INFINITE_LIFETIME - : HIGH_COST_PORT_KEEPALIVE_LIFETIME; + stun_keepalive_lifetime_ = GetStunKeepaliveLifetime(); if (!SharedSocket()) { ASSERT(socket_ == NULL); socket_ = socket_factory()->CreateUdpSocket( @@ -299,6 +285,11 @@ int UDPPort::SendTo(const void* data, size_t size, return sent; } +void UDPPort::UpdateNetworkCost() { + Port::UpdateNetworkCost(); + stun_keepalive_lifetime_ = GetStunKeepaliveLifetime(); +} + int UDPPort::SetOption(rtc::Socket::Option opt, int value) { return socket_->SetOption(opt, value); } @@ -411,8 +402,8 @@ void UDPPort::SendStunBindingRequest(const rtc::SocketAddress& stun_addr) { } else if (socket_->GetState() == rtc::AsyncPacketSocket::STATE_BOUND) { // Check if |server_addr_| is compatible with the port's ip. if (IsCompatibleAddress(stun_addr)) { - requests_.Send(new StunBindingRequest(this, stun_addr, rtc::TimeMillis(), - stun_keepalive_lifetime_)); + requests_.Send( + new StunBindingRequest(this, stun_addr, rtc::TimeMillis())); } else { // Since we can't send stun messages to the server, we should mark this // port ready. diff --git a/webrtc/p2p/base/stunport.h b/webrtc/p2p/base/stunport.h index cd844aa273..82be6fae2a 100644 --- a/webrtc/p2p/base/stunport.h +++ b/webrtc/p2p/base/stunport.h @@ -26,6 +26,11 @@ class SignalThread; namespace cricket { +// Lifetime chosen for STUN ports on low-cost networks. +static const int INFINITE_LIFETIME = -1; +// Lifetime for STUN ports on high-cost networks: 2 minutes +static const int HIGH_COST_PORT_KEEPALIVE_LIFETIME = 2 * 60 * 1000; + // Communicates using the address on the outside of a NAT. class UDPPort : public Port { public: @@ -145,6 +150,8 @@ class UDPPort : public Port { const rtc::PacketOptions& options, bool payload); + virtual void UpdateNetworkCost(); + void OnLocalAddressReady(rtc::AsyncPacketSocket* socket, const rtc::SocketAddress& address); void OnReadPacket(rtc::AsyncPacketSocket* socket, @@ -219,6 +226,15 @@ class UDPPort : public Port { bool HasCandidateWithAddress(const rtc::SocketAddress& addr) const; + // If this is a low-cost network, it will keep on sending STUN binding + // requests indefinitely to keep the NAT binding alive. Otherwise, stop + // sending STUN binding requests after HIGH_COST_PORT_KEEPALIVE_LIFETIME. + int GetStunKeepaliveLifetime() { + return (network_cost() >= rtc::kNetworkCostHigh) + ? HIGH_COST_PORT_KEEPALIVE_LIFETIME + : INFINITE_LIFETIME; + } + ServerAddresses server_addresses_; ServerAddresses bind_request_succeeded_servers_; ServerAddresses bind_request_failed_servers_; @@ -228,7 +244,7 @@ class UDPPort : public Port { std::unique_ptr resolver_; bool ready_; int stun_keepalive_delay_; - int stun_keepalive_lifetime_; + int stun_keepalive_lifetime_ = INFINITE_LIFETIME; // This is true by default and false when // PORTALLOCATOR_DISABLE_DEFAULT_LOCAL_CANDIDATE is specified. diff --git a/webrtc/p2p/base/stunport_unittest.cc b/webrtc/p2p/base/stunport_unittest.cc index 702ec2504e..ba4ca364b4 100644 --- a/webrtc/p2p/base/stunport_unittest.cc +++ b/webrtc/p2p/base/stunport_unittest.cc @@ -26,6 +26,7 @@ using rtc::SocketAddress; static const SocketAddress kLocalAddr("127.0.0.1", 0); static const SocketAddress kStunAddr1("127.0.0.1", 5000); static const SocketAddress kStunAddr2("127.0.0.1", 4000); +static const SocketAddress kStunAddr3("127.0.0.1", 3000); static const SocketAddress kBadAddr("0.0.0.1", 5000); static const SocketAddress kStunHostnameAddr("localhost", 5000); static const SocketAddress kBadHostnameAddr("not-a-real-hostname", 5000); @@ -306,31 +307,39 @@ TEST_F(StunPortTest, TestTwoCandidatesWithTwoStunServersAcrossNat) { } // Test that the stun_keepalive_lifetime is set correctly based on the network -// type on a STUN port. +// type on a STUN port. Also test that it will be updated if the network type +// changes. TEST_F(StunPortTest, TestStunPortGetStunKeepaliveLifetime) { - // Lifetime for the default network type is |kInfiniteLifetime|. + // Lifetime for the default (unknown) network type is |kInfiniteLifetime|. CreateStunPort(kStunAddr1); EXPECT_EQ(kInfiniteLifetime, port()->stun_keepalive_lifetime()); - // Lifetime for the cellular network is |kHighCostPortKeepaliveLifetimeMs| SetNetworkType(rtc::ADAPTER_TYPE_CELLULAR); - CreateStunPort(kStunAddr2); EXPECT_EQ(kHighCostPortKeepaliveLifetimeMs, port()->stun_keepalive_lifetime()); + + // Lifetime for the wifi network is |kInfiniteLifetime|. + SetNetworkType(rtc::ADAPTER_TYPE_WIFI); + CreateStunPort(kStunAddr2); + EXPECT_EQ(kInfiniteLifetime, port()->stun_keepalive_lifetime()); } // Test that the stun_keepalive_lifetime is set correctly based on the network -// type on a shared STUN port (UDPPort). +// type on a shared STUN port (UDPPort). Also test that it will be updated +// if the network type changes. TEST_F(StunPortTest, TestUdpPortGetStunKeepaliveLifetime) { - // Lifetime for the default network type is |kInfiniteLifetime|. + // Lifetime for the default (unknown) network type is |kInfiniteLifetime|. CreateSharedStunPort(kStunAddr1); EXPECT_EQ(kInfiniteLifetime, port()->stun_keepalive_lifetime()); - - // Lifetime for the cellular network is |kHighCostPortKeepaliveLifetimeMs| + // Lifetime for the cellular network is |kHighCostPortKeepaliveLifetimeMs|. SetNetworkType(rtc::ADAPTER_TYPE_CELLULAR); - CreateSharedStunPort(kStunAddr2); EXPECT_EQ(kHighCostPortKeepaliveLifetimeMs, port()->stun_keepalive_lifetime()); + + // Lifetime for the wifi network type is |kInfiniteLifetime|. + SetNetworkType(rtc::ADAPTER_TYPE_WIFI); + CreateSharedStunPort(kStunAddr2); + EXPECT_EQ(kInfiniteLifetime, port()->stun_keepalive_lifetime()); } // Test that STUN binding requests will be stopped shortly if the keep-alive