/* * Copyright 2019 The WebRTC Project Authors. All rights reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. An additional intellectual property rights grant can be found * in the file PATENTS. All contributing project authors may * be found in the AUTHORS file in the root of the source tree. */ #include "p2p/base/basic_ice_controller.h" namespace { // The minimum improvement in RTT that justifies a switch. const int kMinImprovement = 10; bool IsRelayRelay(const cricket::Connection* conn) { return conn->local_candidate().type() == cricket::RELAY_PORT_TYPE && conn->remote_candidate().type() == cricket::RELAY_PORT_TYPE; } bool IsUdp(const cricket::Connection* conn) { return conn->local_candidate().relay_protocol() == cricket::UDP_PROTOCOL_NAME; } // 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, absl::optional network_preference) { rtc::AdapterType network_type = conn->network()->type(); return network_preference.has_value() && (network_type == network_preference); } int CompareCandidatePairsByNetworkPreference( const cricket::Connection* a, const cricket::Connection* b, absl::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; } } // namespace namespace cricket { BasicIceController::BasicIceController(const IceControllerFactoryArgs& args) : ice_transport_state_func_(args.ice_transport_state_func), ice_role_func_(args.ice_role_func), is_connection_pruned_func_(args.is_connection_pruned_func), field_trials_(args.ice_field_trials) {} BasicIceController::~BasicIceController() {} void BasicIceController::SetIceConfig(const IceConfig& config) { config_ = config; } void BasicIceController::SetSelectedConnection( const Connection* selected_connection) { selected_connection_ = selected_connection; } void BasicIceController::AddConnection(const Connection* connection) { connections_.push_back(connection); unpinged_connections_.insert(connection); } void BasicIceController::OnConnectionDestroyed(const Connection* connection) { pinged_connections_.erase(connection); unpinged_connections_.erase(connection); connections_.erase(absl::c_find(connections_, connection)); } bool BasicIceController::HasPingableConnection() const { int64_t now = rtc::TimeMillis(); return absl::c_any_of(connections_, [this, now](const Connection* c) { return IsPingable(c, now); }); } IceControllerInterface::PingResult BasicIceController::SelectConnectionToPing( int64_t last_ping_sent_ms) { // When the selected connection is not receiving or not writable, or any // active connection has not been pinged enough times, use the weak ping // interval. bool need_more_pings_at_weak_interval = absl::c_any_of(connections_, [](const Connection* conn) { return conn->active() && conn->num_pings_sent() < MIN_PINGS_AT_WEAK_PING_INTERVAL; }); int ping_interval = (weak() || need_more_pings_at_weak_interval) ? weak_ping_interval() : strong_ping_interval(); const Connection* conn = nullptr; if (rtc::TimeMillis() >= last_ping_sent_ms + ping_interval) { conn = FindNextPingableConnection(); } PingResult res(conn, std::min(ping_interval, check_receiving_interval())); return res; } void BasicIceController::MarkConnectionPinged(const Connection* conn) { if (conn && pinged_connections_.insert(conn).second) { unpinged_connections_.erase(conn); } } // Returns the next pingable connection to ping. const Connection* BasicIceController::FindNextPingableConnection() { int64_t now = rtc::TimeMillis(); // Rule 1: Selected connection takes priority over non-selected ones. if (selected_connection_ && selected_connection_->connected() && selected_connection_->writable() && WritableConnectionPastPingInterval(selected_connection_, now)) { return selected_connection_; } // Rule 2: If the channel is weak, we need to find a new writable and // receiving connection, probably on a different network. If there are lots of // connections, it may take several seconds between two pings for every // non-selected connection. This will cause the receiving state of those // connections to be false, and thus they won't be selected. This is // problematic for network fail-over. We want to make sure at least one // connection per network is pinged frequently enough in order for it to be // selectable. So we prioritize one connection per network. // Rule 2.1: Among such connections, pick the one with the earliest // last-ping-sent time. if (weak()) { std::vector pingable_selectable_connections; absl::c_copy_if(GetBestWritableConnectionPerNetwork(), std::back_inserter(pingable_selectable_connections), [this, now](const Connection* conn) { return WritableConnectionPastPingInterval(conn, now); }); auto iter = absl::c_min_element( pingable_selectable_connections, [](const Connection* conn1, const Connection* conn2) { return conn1->last_ping_sent() < conn2->last_ping_sent(); }); if (iter != pingable_selectable_connections.end()) { return *iter; } } // Rule 3: Triggered checks have priority over non-triggered connections. // Rule 3.1: Among triggered checks, oldest takes precedence. const Connection* oldest_triggered_check = FindOldestConnectionNeedingTriggeredCheck(now); if (oldest_triggered_check) { return oldest_triggered_check; } // Rule 4: Unpinged connections have priority over pinged ones. RTC_CHECK(connections_.size() == pinged_connections_.size() + unpinged_connections_.size()); // If there are unpinged and pingable connections, only ping those. // Otherwise, treat everything as unpinged. // TODO(honghaiz): Instead of adding two separate vectors, we can add a state // "pinged" to filter out unpinged connections. if (absl::c_none_of(unpinged_connections_, [this, now](const Connection* conn) { return this->IsPingable(conn, now); })) { unpinged_connections_.insert(pinged_connections_.begin(), pinged_connections_.end()); pinged_connections_.clear(); } // Among un-pinged pingable connections, "more pingable" takes precedence. std::vector pingable_connections; absl::c_copy_if( unpinged_connections_, std::back_inserter(pingable_connections), [this, now](const Connection* conn) { return IsPingable(conn, now); }); auto iter = absl::c_max_element( pingable_connections, [this](const Connection* conn1, const Connection* conn2) { // Some implementations of max_element // compare an element with itself. if (conn1 == conn2) { return false; } return MorePingable(conn1, conn2) == conn2; }); if (iter != pingable_connections.end()) { return *iter; } return nullptr; } // Find "triggered checks". We ping first those connections that have // received a ping but have not sent a ping since receiving it // (last_ping_received > last_ping_sent). But we shouldn't do // triggered checks if the connection is already writable. const Connection* BasicIceController::FindOldestConnectionNeedingTriggeredCheck( int64_t now) { const Connection* oldest_needing_triggered_check = nullptr; for (auto* conn : connections_) { if (!IsPingable(conn, now)) { continue; } bool needs_triggered_check = (!conn->writable() && conn->last_ping_received() > conn->last_ping_sent()); if (needs_triggered_check && (!oldest_needing_triggered_check || (conn->last_ping_received() < oldest_needing_triggered_check->last_ping_received()))) { oldest_needing_triggered_check = conn; } } if (oldest_needing_triggered_check) { RTC_LOG(LS_INFO) << "Selecting connection for triggered check: " << oldest_needing_triggered_check->ToString(); } return oldest_needing_triggered_check; } bool BasicIceController::WritableConnectionPastPingInterval( const Connection* conn, int64_t now) const { int interval = CalculateActiveWritablePingInterval(conn, now); return conn->last_ping_sent() + interval <= now; } int BasicIceController::CalculateActiveWritablePingInterval( const Connection* conn, int64_t now) const { // Ping each connection at a higher rate at least // MIN_PINGS_AT_WEAK_PING_INTERVAL times. if (conn->num_pings_sent() < MIN_PINGS_AT_WEAK_PING_INTERVAL) { return weak_ping_interval(); } int stable_interval = config_.stable_writable_connection_ping_interval_or_default(); int weak_or_stablizing_interval = std::min( stable_interval, WEAK_OR_STABILIZING_WRITABLE_CONNECTION_PING_INTERVAL); // If the channel is weak or the connection is not stable yet, use the // weak_or_stablizing_interval. return (!weak() && conn->stable(now)) ? stable_interval : weak_or_stablizing_interval; } // Is the connection in a state for us to even consider pinging the other side? // We consider a connection pingable even if it's not connected because that's // how a TCP connection is kicked into reconnecting on the active side. bool BasicIceController::IsPingable(const Connection* conn, int64_t now) const { const Candidate& remote = conn->remote_candidate(); // We should never get this far with an empty remote ufrag. RTC_DCHECK(!remote.username().empty()); if (remote.username().empty() || remote.password().empty()) { // If we don't have an ICE ufrag and pwd, there's no way we can ping. return false; } // A failed connection will not be pinged. if (conn->state() == IceCandidatePairState::FAILED) { return false; } // An never connected connection cannot be written to at all, so pinging is // out of the question. However, if it has become WRITABLE, it is in the // reconnecting state so ping is needed. if (!conn->connected() && !conn->writable()) { return false; } // If we sent a number of pings wo/ reply, skip sending more // until we get one. if (conn->TooManyOutstandingPings(field_trials_->max_outstanding_pings)) { return false; } // If the channel is weakly connected, ping all connections. if (weak()) { return true; } // Always ping active connections regardless whether the channel is completed // or not, but backup connections are pinged at a slower rate. if (IsBackupConnection(conn)) { return conn->rtt_samples() == 0 || (now >= conn->last_ping_response_received() + config_.backup_connection_ping_interval_or_default()); } // Don't ping inactive non-backup connections. if (!conn->active()) { return false; } // Do ping unwritable, active connections. if (!conn->writable()) { return true; } // Ping writable, active connections if it's been long enough since the last // ping. return WritableConnectionPastPingInterval(conn, now); } // A connection is considered a backup connection if the channel state // is completed, the connection is not the selected connection and it is active. bool BasicIceController::IsBackupConnection(const Connection* conn) const { return ice_transport_state_func_() == IceTransportState::STATE_COMPLETED && conn != selected_connection_ && conn->active(); } const Connection* BasicIceController::MorePingable(const Connection* conn1, const Connection* conn2) { RTC_DCHECK(conn1 != conn2); if (config_.prioritize_most_likely_candidate_pairs) { const Connection* most_likely_to_work_conn = MostLikelyToWork(conn1, conn2); if (most_likely_to_work_conn) { return most_likely_to_work_conn; } } const Connection* least_recently_pinged_conn = LeastRecentlyPinged(conn1, conn2); if (least_recently_pinged_conn) { return least_recently_pinged_conn; } // During the initial state when nothing has been pinged yet, return the first // one in the ordered `connections_`. auto connections = connections_; return *(std::find_if(connections.begin(), connections.end(), [conn1, conn2](const Connection* conn) { return conn == conn1 || conn == conn2; })); } const Connection* BasicIceController::MostLikelyToWork( const Connection* conn1, const Connection* conn2) { bool rr1 = IsRelayRelay(conn1); bool rr2 = IsRelayRelay(conn2); if (rr1 && !rr2) { return conn1; } else if (rr2 && !rr1) { return conn2; } else if (rr1 && rr2) { bool udp1 = IsUdp(conn1); bool udp2 = IsUdp(conn2); if (udp1 && !udp2) { return conn1; } else if (udp2 && udp1) { return conn2; } } return nullptr; } const Connection* BasicIceController::LeastRecentlyPinged( const Connection* conn1, const Connection* conn2) { if (conn1->last_ping_sent() < conn2->last_ping_sent()) { return conn1; } if (conn1->last_ping_sent() > conn2->last_ping_sent()) { return conn2; } return nullptr; } std::map BasicIceController::GetBestConnectionByNetwork() const { // `connections_` has been sorted, so the first one in the list on a given // network is the best connection on the network, except that the selected // connection is always the best connection on the network. std::map best_connection_by_network; if (selected_connection_) { best_connection_by_network[selected_connection_->network()] = selected_connection_; } // TODO(honghaiz): Need to update this if `connections_` are not sorted. for (const Connection* conn : connections_) { const rtc::Network* network = conn->network(); // This only inserts when the network does not exist in the map. best_connection_by_network.insert(std::make_pair(network, conn)); } return best_connection_by_network; } std::vector BasicIceController::GetBestWritableConnectionPerNetwork() const { std::vector connections; for (auto kv : GetBestConnectionByNetwork()) { const Connection* conn = kv.second; if (conn->writable() && conn->connected()) { connections.push_back(conn); } } return connections; } IceControllerInterface::SwitchResult BasicIceController::HandleInitialSelectDampening( IceControllerEvent reason, const Connection* new_connection) { if (!field_trials_->initial_select_dampening.has_value() && !field_trials_->initial_select_dampening_ping_received.has_value()) { // experiment not enabled => select connection. return {new_connection, absl::nullopt}; } int64_t now = rtc::TimeMillis(); int64_t max_delay = 0; if (new_connection->last_ping_received() > 0 && field_trials_->initial_select_dampening_ping_received.has_value()) { max_delay = *field_trials_->initial_select_dampening_ping_received; } else if (field_trials_->initial_select_dampening.has_value()) { max_delay = *field_trials_->initial_select_dampening; } int64_t start_wait = initial_select_timestamp_ms_ == 0 ? now : initial_select_timestamp_ms_; int64_t max_wait_until = start_wait + max_delay; if (now >= max_wait_until) { RTC_LOG(LS_INFO) << "reset initial_select_timestamp_ = " << initial_select_timestamp_ms_ << " selection delayed by: " << (now - start_wait) << "ms"; initial_select_timestamp_ms_ = 0; return {new_connection, absl::nullopt}; } // We are not yet ready to select first connection... if (initial_select_timestamp_ms_ == 0) { // Set timestamp on first time... // but run the delayed invokation everytime to // avoid possibility that we miss it. initial_select_timestamp_ms_ = now; RTC_LOG(LS_INFO) << "set initial_select_timestamp_ms_ = " << initial_select_timestamp_ms_; } int min_delay = max_delay; if (field_trials_->initial_select_dampening.has_value()) { min_delay = std::min(min_delay, *field_trials_->initial_select_dampening); } if (field_trials_->initial_select_dampening_ping_received.has_value()) { min_delay = std::min( min_delay, *field_trials_->initial_select_dampening_ping_received); } RTC_LOG(LS_INFO) << "delay initial selection up to " << min_delay << "ms"; reason.type = IceControllerEvent::ICE_CONTROLLER_RECHECK; reason.recheck_delay_ms = min_delay; return {absl::nullopt, reason}; } IceControllerInterface::SwitchResult BasicIceController::ShouldSwitchConnection( IceControllerEvent reason, const Connection* new_connection) { if (!ReadyToSend(new_connection) || selected_connection_ == new_connection) { return {absl::nullopt, absl::nullopt}; } if (selected_connection_ == nullptr) { return HandleInitialSelectDampening(reason, new_connection); } // 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 {absl::nullopt, absl::nullopt}; } bool missed_receiving_unchanged_threshold = false; absl::optional receiving_unchanged_threshold( rtc::TimeMillis() - config_.receiving_switching_delay_or_default()); int cmp = CompareConnections(selected_connection_, new_connection, receiving_unchanged_threshold, &missed_receiving_unchanged_threshold); absl::optional recheck_event; if (missed_receiving_unchanged_threshold && config_.receiving_switching_delay_or_default()) { // If we do not switch to the connection because it missed the receiving // threshold, the new connection is in a better receiving state than the // currently selected connection. So we need to re-check whether it needs // to be switched at a later time. recheck_event = reason; recheck_event->recheck_delay_ms = config_.receiving_switching_delay_or_default(); } if (cmp < 0) { return {new_connection, absl::nullopt}; } else if (cmp > 0) { return {absl::nullopt, recheck_event}; } // If everything else is the same, switch only if rtt has improved by // a margin. if (new_connection->rtt() <= selected_connection_->rtt() - kMinImprovement) { return {new_connection, absl::nullopt}; } return {absl::nullopt, recheck_event}; } IceControllerInterface::SwitchResult BasicIceController::SortAndSwitchConnection(IceControllerEvent reason) { // Find the best alternative connection by sorting. It is important to note // that amongst equal preference, writable connections, this will choose the // one whose estimated latency is lowest. So it is the only one that we // need to consider switching to. // TODO(honghaiz): Don't sort; Just use std::max_element in the right places. absl::c_stable_sort( connections_, [this](const Connection* a, const Connection* b) { int cmp = CompareConnections(a, b, absl::nullopt, nullptr); if (cmp != 0) { return cmp > 0; } // Otherwise, sort based on latency estimate. return a->rtt() < b->rtt(); }); RTC_LOG(LS_VERBOSE) << "Sorting " << connections_.size() << " available connections"; for (size_t i = 0; i < connections_.size(); ++i) { RTC_LOG(LS_VERBOSE) << connections_[i]->ToString(); } const Connection* top_connection = (!connections_.empty()) ? connections_[0] : nullptr; return ShouldSwitchConnection(reason, top_connection); } bool BasicIceController::ReadyToSend(const Connection* connection) const { // Note that we allow sending on an unreliable connection, because it's // possible that it became unreliable simply due to bad chance. // So this shouldn't prevent attempting to send media. return connection != nullptr && (connection->writable() || connection->write_state() == Connection::STATE_WRITE_UNRELIABLE || PresumedWritable(connection)); } bool BasicIceController::PresumedWritable(const Connection* conn) const { return (conn->write_state() == Connection::STATE_WRITE_INIT && config_.presume_writable_when_fully_relayed && conn->local_candidate().type() == RELAY_PORT_TYPE && (conn->remote_candidate().type() == RELAY_PORT_TYPE || conn->remote_candidate().type() == PRFLX_PORT_TYPE)); } // Compare two connections based on their writing, receiving, and connected // states. int BasicIceController::CompareConnectionStates( const Connection* a, const Connection* b, absl::optional receiving_unchanged_threshold, bool* missed_receiving_unchanged_threshold) const { // First, prefer a connection that's writable or presumed writable over // one that's not writable. bool a_writable = a->writable() || PresumedWritable(a); bool b_writable = b->writable() || PresumedWritable(b); if (a_writable && !b_writable) { return a_is_better; } if (!a_writable && b_writable) { return b_is_better; } // Sort based on write-state. Better states have lower values. if (a->write_state() < b->write_state()) { return a_is_better; } if (b->write_state() < a->write_state()) { return b_is_better; } // We prefer a receiving connection to a non-receiving, higher-priority // connection when sorting connections and choosing which connection to // switch to. if (a->receiving() && !b->receiving()) { return a_is_better; } if (!a->receiving() && b->receiving()) { if (!receiving_unchanged_threshold || (a->receiving_unchanged_since() <= *receiving_unchanged_threshold && b->receiving_unchanged_since() <= *receiving_unchanged_threshold)) { return b_is_better; } *missed_receiving_unchanged_threshold = true; } // WARNING: Some complexity here about TCP reconnecting. // When a TCP connection fails because of a TCP socket disconnecting, the // active side of the connection will attempt to reconnect for 5 seconds while // pretending to be writable (the connection is not set to the unwritable // state). On the passive side, the connection also remains writable even // though it is disconnected, and a new connection is created when the active // side connects. At that point, there are two TCP connections on the passive // side: 1. the old, disconnected one that is pretending to be writable, and // 2. the new, connected one that is maybe not yet writable. For purposes of // pruning, pinging, and selecting the selected connection, we want to treat // the new connection as "better" than the old one. We could add a method // called something like Connection::ImReallyBadEvenThoughImWritable, but that // is equivalent to the existing Connection::connected(), which we already // have. So, in code throughout this file, we'll check whether the connection // is connected() or not, and if it is not, treat it as "worse" than a // connected one, even though it's writable. In the code below, we're doing // so to make sure we treat a new writable connection as better than an old // disconnected connection. // In the case where we reconnect TCP connections, the original best // connection is disconnected without changing to WRITE_TIMEOUT. In this case, // the new connection, when it becomes writable, should have higher priority. if (a->write_state() == Connection::STATE_WRITABLE && b->write_state() == Connection::STATE_WRITABLE) { if (a->connected() && !b->connected()) { return a_is_better; } if (!a->connected() && b->connected()) { return b_is_better; } } return 0; } // Compares two connections based only on the candidate and network information. // Returns positive if `a` is better than `b`. int BasicIceController::CompareConnectionCandidates(const Connection* a, const Connection* b) const { 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. if (a->priority() > b->priority()) { return a_is_better; } if (a->priority() < b->priority()) { return b_is_better; } // If we're still tied at this point, prefer a younger generation. // (Younger generation means a larger generation number). int cmp = (a->remote_candidate().generation() + a->generation()) - (b->remote_candidate().generation() + b->generation()); if (cmp != 0) { return cmp; } // A periodic regather (triggered by the regather_all_networks_interval_range) // will produce candidates that appear the same but would use a new port. We // want to use the new candidates and purge the old candidates as they come // in, so use the fact that the old ports get pruned immediately to rank the // candidates with an active port/remote candidate higher. bool a_pruned = is_connection_pruned_func_(a); bool b_pruned = is_connection_pruned_func_(b); if (!a_pruned && b_pruned) { return a_is_better; } if (a_pruned && !b_pruned) { return b_is_better; } // Otherwise, must be equal return 0; } int BasicIceController::CompareConnections( const Connection* a, const Connection* b, absl::optional receiving_unchanged_threshold, bool* missed_receiving_unchanged_threshold) const { RTC_CHECK(a != nullptr); RTC_CHECK(b != nullptr); // We prefer to switch to a writable and receiving connection over a // non-writable or non-receiving connection, even if the latter has // been nominated by the controlling side. int state_cmp = CompareConnectionStates(a, b, receiving_unchanged_threshold, missed_receiving_unchanged_threshold); if (state_cmp != 0) { return state_cmp; } if (ice_role_func_() == ICEROLE_CONTROLLED) { // Compare the connections based on the nomination states and the last data // received time if this is on the controlled side. if (a->remote_nomination() > b->remote_nomination()) { return a_is_better; } if (a->remote_nomination() < b->remote_nomination()) { return b_is_better; } if (a->last_data_received() > b->last_data_received()) { return a_is_better; } if (a->last_data_received() < b->last_data_received()) { return b_is_better; } } // Compare the network cost and priority. return CompareConnectionCandidates(a, b); } int BasicIceController::CompareCandidatePairNetworks( const Connection* a, const Connection* b, absl::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; } 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. if (a_cost < b_cost) { return a_is_better; } if (a_cost > b_cost) { return b_is_better; } return a_and_b_equal; } std::vector BasicIceController::PruneConnections() { // We can prune any connection for which there is a connected, writable // connection on the same network with better or equal priority. We leave // those with better priority just in case they become writable later (at // which point, we would prune out the current selected connection). We leave // connections on other networks because they may not be using the same // resources and they may represent very distinct paths over which we can // switch. If `best_conn_on_network` is not connected, we may be reconnecting // a TCP connection and should not prune connections in this network. // See the big comment in CompareConnectionStates. // // An exception is made for connections on an "any address" network, meaning // not bound to any specific network interface. We don't want to keep one of // these alive as a backup, since it could be using the same network // interface as the higher-priority, selected candidate pair. std::vector connections_to_prune; auto best_connection_by_network = GetBestConnectionByNetwork(); for (const Connection* conn : connections_) { const Connection* best_conn = selected_connection_; if (!rtc::IPIsAny(conn->network()->ip())) { // If the connection is bound to a specific network interface (not an // "any address" network), compare it against the best connection for // that network interface rather than the best connection overall. This // ensures that at least one connection per network will be left // unpruned. best_conn = best_connection_by_network[conn->network()]; } // Do not prune connections if the connection being compared against is // weak. Otherwise, it may delete connections prematurely. if (best_conn && conn != best_conn && !best_conn->weak() && CompareConnectionCandidates(best_conn, conn) >= 0) { connections_to_prune.push_back(conn); } } return connections_to_prune; } bool BasicIceController::GetUseCandidateAttr(const Connection* conn, NominationMode mode, IceMode remote_ice_mode) const { switch (mode) { case NominationMode::REGULAR: // TODO(honghaiz): Implement regular nomination. return false; case NominationMode::AGGRESSIVE: if (remote_ice_mode == ICEMODE_LITE) { return GetUseCandidateAttr(conn, NominationMode::REGULAR, remote_ice_mode); } return true; case NominationMode::SEMI_AGGRESSIVE: { // Nominate if // a) Remote is in FULL ICE AND // a.1) `conn` is the selected connection OR // a.2) there is no selected connection OR // a.3) the selected connection is unwritable OR // a.4) `conn` has higher priority than selected_connection. // b) Remote is in LITE ICE AND // b.1) `conn` is the selected_connection AND // b.2) `conn` is writable. bool selected = conn == selected_connection_; if (remote_ice_mode == ICEMODE_LITE) { return selected && conn->writable(); } bool better_than_selected = !selected_connection_ || !selected_connection_->writable() || CompareConnectionCandidates(selected_connection_, conn) < 0; return selected || better_than_selected; } default: RTC_DCHECK_NOTREACHED(); return false; } } } // namespace cricket