From 09c452e7bae5c49024eba0098dd64805b2f9cd4c Mon Sep 17 00:00:00 2001 From: Jonas Oreland Date: Wed, 20 Nov 2019 09:01:02 +0100 Subject: [PATCH] Split P2PTransportChannel This patch moves the logic for - selection of connection to ping - selection of connection to use - selection of connection to prune into own file and puts it behind a new interface called 'IceControllerInterface'. BUG=webrtc:10647 Change-Id: I10228b3edd361d3200fa4a734d74a319560966c9 Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/158205 Reviewed-by: Qingsi Wang Reviewed-by: Honghai Zhang Commit-Queue: Jonas Oreland Cr-Commit-Position: refs/heads/master@{#29850} --- p2p/BUILD.gn | 4 + p2p/base/basic_ice_controller.cc | 829 ++++++++++++++++++++++++ p2p/base/basic_ice_controller.h | 167 +++++ p2p/base/ice_controller_interface.cc | 55 ++ p2p/base/ice_controller_interface.h | 117 ++++ p2p/base/p2p_transport_channel.cc | 932 ++++----------------------- p2p/base/p2p_transport_channel.h | 115 +--- 7 files changed, 1305 insertions(+), 914 deletions(-) create mode 100644 p2p/base/basic_ice_controller.cc create mode 100644 p2p/base/basic_ice_controller.h create mode 100644 p2p/base/ice_controller_interface.cc create mode 100644 p2p/base/ice_controller_interface.h diff --git a/p2p/BUILD.gn b/p2p/BUILD.gn index 6ed14a8da5..945c679459 100644 --- a/p2p/BUILD.gn +++ b/p2p/BUILD.gn @@ -36,6 +36,8 @@ rtc_library("rtc_p2p") { "base/async_stun_tcp_socket.h", "base/basic_async_resolver_factory.cc", "base/basic_async_resolver_factory.h", + "base/basic_ice_controller.cc", + "base/basic_ice_controller.h", "base/basic_packet_socket_factory.cc", "base/basic_packet_socket_factory.h", "base/candidate_pair_interface.h", @@ -50,6 +52,8 @@ rtc_library("rtc_p2p") { "base/dtls_transport_factory.h", "base/dtls_transport_internal.cc", "base/dtls_transport_internal.h", + "base/ice_controller_interface.cc", + "base/ice_controller_interface.h", "base/ice_credentials_iterator.cc", "base/ice_credentials_iterator.h", "base/ice_transport_internal.cc", diff --git a/p2p/base/basic_ice_controller.cc b/p2p/base/basic_ice_controller.cc new file mode 100644 index 0000000000..d348ae92d4 --- /dev/null +++ b/p2p/base/basic_ice_controller.cc @@ -0,0 +1,829 @@ +/* + * 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->port()->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( + std::function ice_transport_state_func, + std::function ice_role_func, + std::function is_connection_pruned_func, + const IceFieldTrials* field_trials) + : ice_transport_state_func_(ice_transport_state_func), + ice_role_func_(ice_role_func), + is_connection_pruned_func_(is_connection_pruned_func), + field_trials_(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); + }); +} + +std::pair 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(); + } + int delay = std::min(ping_interval, check_receiving_interval()); + return std::make_pair(const_cast(conn), delay); +} + +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_->port()->Network()] = + selected_connection_; + } + // TODO(honghaiz): Need to update this if |connections_| are not sorted. + for (const Connection* conn : connections_) { + rtc::Network* network = conn->port()->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"; + return {absl::nullopt, min_delay}; +} + +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_delay; + 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_delay = config_.receiving_switching_delay_or_default(); + } + + if (cmp < 0) { + return {new_connection, absl::nullopt}; + } else if (cmp > 0) { + return {absl::nullopt, recheck_delay}; + } + + // 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_delay}; +} + +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->port()->generation()) - + (b->remote_candidate().generation() + b->port()->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; + } + + 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->port()->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->port()->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_NOTREACHED(); + return false; + } +} + +} // namespace cricket diff --git a/p2p/base/basic_ice_controller.h b/p2p/base/basic_ice_controller.h new file mode 100644 index 0000000000..5335c0077c --- /dev/null +++ b/p2p/base/basic_ice_controller.h @@ -0,0 +1,167 @@ +/* + * 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. + */ + +#ifndef P2P_BASE_BASIC_ICE_CONTROLLER_H_ +#define P2P_BASE_BASIC_ICE_CONTROLLER_H_ + +#include +#include +#include +#include +#include + +#include "p2p/base/ice_controller_interface.h" +#include "p2p/base/p2p_transport_channel.h" + +namespace cricket { + +class BasicIceController : public IceControllerInterface { + public: + BasicIceController( + std::function ice_transport_state_func, + std::function ice_role_func, + std::function is_candidated_pruned_func, + const IceFieldTrials*); + virtual ~BasicIceController(); + + void SetIceConfig(const IceConfig& config) override; + void SetSelectedConnection(const Connection* selected_connection) override; + void AddConnection(const Connection* connection) override; + void OnConnectionDestroyed(const Connection* connection) override; + rtc::ArrayView connections() const override { + return rtc::ArrayView( + const_cast(connections_.data()), + connections_.size()); + } + + bool HasPingableConnection() const override; + + std::pair SelectConnectionToPing( + int64_t last_ping_sent_ms) override; + bool GetUseCandidateAttr(const Connection* conn, + NominationMode mode, + IceMode remote_ice_mode) const override; + + SwitchResult ShouldSwitchConnection(IceControllerEvent reason, + const Connection* connection) override; + SwitchResult SortAndSwitchConnection(IceControllerEvent reason) override; + + std::vector PruneConnections() override; + + // These methods are only for tests. + const Connection* FindNextPingableConnection() override; + void MarkConnectionPinged(const Connection* conn) override; + + private: + // A transport channel is weak if the current best connection is either + // not receiving or not writable, or if there is no best connection at all. + bool weak() const { + return !selected_connection_ || selected_connection_->weak(); + } + + int weak_ping_interval() const { + return std::max(config_.ice_check_interval_weak_connectivity_or_default(), + config_.ice_check_min_interval_or_default()); + } + + int strong_ping_interval() const { + return std::max(config_.ice_check_interval_strong_connectivity_or_default(), + config_.ice_check_min_interval_or_default()); + } + + int check_receiving_interval() const { + return std::max(MIN_CHECK_RECEIVING_INTERVAL, + config_.receiving_timeout_or_default() / 10); + } + + const Connection* FindOldestConnectionNeedingTriggeredCheck(int64_t now); + // Between |conn1| and |conn2|, this function returns the one which should + // be pinged first. + const Connection* MorePingable(const Connection* conn1, + const Connection* conn2); + // Select the connection which is Relay/Relay. If both of them are, + // UDP relay protocol takes precedence. + const Connection* MostLikelyToWork(const Connection* conn1, + const Connection* conn2); + // Compare the last_ping_sent time and return the one least recently pinged. + const Connection* LeastRecentlyPinged(const Connection* conn1, + const Connection* conn2); + + bool IsPingable(const Connection* conn, int64_t now) const; + bool IsBackupConnection(const Connection* conn) const; + // Whether a writable connection is past its ping interval and needs to be + // pinged again. + bool WritableConnectionPastPingInterval(const Connection* conn, + int64_t now) const; + int CalculateActiveWritablePingInterval(const Connection* conn, + int64_t now) const; + + std::map GetBestConnectionByNetwork() const; + std::vector GetBestWritableConnectionPerNetwork() const; + + bool ReadyToSend(const Connection* connection) const; + bool PresumedWritable(const Connection* conn) const; + + int CompareCandidatePairNetworks( + const Connection* a, + const Connection* b, + absl::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 + // |a| is not, returns a negative value only if |b| has been in receiving + // state and |a| has been in not receiving state since + // |receiving_unchanged_threshold| and sets + // |missed_receiving_unchanged_threshold| to true otherwise. + int CompareConnectionStates( + const Connection* a, + const Connection* b, + absl::optional receiving_unchanged_threshold, + bool* missed_receiving_unchanged_threshold) const; + int CompareConnectionCandidates(const Connection* a, + const Connection* b) const; + // Compares two connections based on the connection states + // (writable/receiving/connected), nomination states, last data received time, + // and static preferences. Does not include latency. Used by both sorting + // and ShouldSwitchSelectedConnection(). + // Returns a positive value if |a| is better than |b|. + int CompareConnections(const Connection* a, + const Connection* b, + absl::optional receiving_unchanged_threshold, + bool* missed_receiving_unchanged_threshold) const; + + SwitchResult HandleInitialSelectDampening(IceControllerEvent reason, + const Connection* new_connection); + + std::function ice_transport_state_func_; + std::function ice_role_func_; + std::function is_connection_pruned_func_; + + IceConfig config_; + const IceFieldTrials* field_trials_; + + // |connections_| is a sorted list with the first one always be the + // |selected_connection_| when it's not nullptr. The combination of + // |pinged_connections_| and |unpinged_connections_| has the same + // connections as |connections_|. These 2 sets maintain whether a + // connection should be pinged next or not. + const Connection* selected_connection_ = nullptr; + std::vector connections_; + std::set pinged_connections_; + std::set unpinged_connections_; + + // Timestamp for when we got the first selectable connection. + int64_t initial_select_timestamp_ms_ = 0; +}; + +} // namespace cricket + +#endif // P2P_BASE_BASIC_ICE_CONTROLLER_H_ diff --git a/p2p/base/ice_controller_interface.cc b/p2p/base/ice_controller_interface.cc new file mode 100644 index 0000000000..6c930124aa --- /dev/null +++ b/p2p/base/ice_controller_interface.cc @@ -0,0 +1,55 @@ +/* + * 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/ice_controller_interface.h" + +#include + +namespace cricket { + +std::string IceControllerEvent::ToString() const { + std::string reason; + switch (type) { + case REMOTE_CANDIDATE_GENERATION_CHANGE: + reason = "remote candidate generation maybe changed"; + break; + case NETWORK_PREFERENCE_CHANGE: + reason = "network preference changed"; + break; + case NEW_CONNECTION_FROM_LOCAL_CANDIDATE: + reason = "new candidate pairs created from a new local candidate"; + break; + case NEW_CONNECTION_FROM_REMOTE_CANDIDATE: + reason = "new candidate pairs created from a new remote candidate"; + break; + case NEW_CONNECTION_FROM_UNKNOWN_REMOTE_ADDRESS: + reason = "a new candidate pair created from an unknown remote address"; + break; + case NOMINATION_ON_CONTROLLED_SIDE: + reason = "nomination on the controlled side"; + break; + case DATA_RECEIVED: + reason = "data received"; + break; + case CONNECT_STATE_CHANGE: + reason = "candidate pair state changed"; + break; + case SELECTED_CONNECTION_DESTROYED: + reason = "selected candidate pair destroyed"; + break; + } + if (dampening_delay) { + reason += " (after switching dampening interval: " + + std::to_string(dampening_delay) + ")"; + } + return reason; +} + +} // namespace cricket diff --git a/p2p/base/ice_controller_interface.h b/p2p/base/ice_controller_interface.h new file mode 100644 index 0000000000..4f8dc7253a --- /dev/null +++ b/p2p/base/ice_controller_interface.h @@ -0,0 +1,117 @@ +/* + * 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. + */ + +#ifndef P2P_BASE_ICE_CONTROLLER_INTERFACE_H_ +#define P2P_BASE_ICE_CONTROLLER_INTERFACE_H_ + +#include +#include +#include + +#include "p2p/base/connection.h" +#include "p2p/base/ice_transport_internal.h" + +namespace cricket { + +struct IceFieldTrials; // Forward declaration to avoid circular dependency. + +struct IceControllerEvent { + enum Type { + REMOTE_CANDIDATE_GENERATION_CHANGE, + NETWORK_PREFERENCE_CHANGE, + NEW_CONNECTION_FROM_LOCAL_CANDIDATE, + NEW_CONNECTION_FROM_REMOTE_CANDIDATE, + NEW_CONNECTION_FROM_UNKNOWN_REMOTE_ADDRESS, + NOMINATION_ON_CONTROLLED_SIDE, + DATA_RECEIVED, + CONNECT_STATE_CHANGE, + SELECTED_CONNECTION_DESTROYED + }; + + IceControllerEvent(const Type& _type) // NOLINT: runtime/explicit + : type(_type) {} + std::string ToString() const; + + Type type; + int dampening_delay = 0; +}; + +// Defines the interface for a module that control +// - which connection to ping +// - which connection to use +// - which connection to prune +// +// P2PTransportChannel creates a |Connection| and adds a const pointer +// to the IceController using |AddConnection|, i.e the IceController +// should not call any non-const methods on a Connection. +// +// The IceController shall keeps track of all connections added +// (and not destroyed) and give them back using the connections()-function- +// +// When a Connection gets destroyed +// - signals on Connection::SignalDestroyed +// - P2PTransportChannel calls IceController::OnConnectionDestroyed +class IceControllerInterface { + public: + // This represents the result of a switch call. + struct SwitchResult { + // Connection that we should (optionally) switch to. + absl::optional connection; + + // Delay in milliseconds when we should resort and try switching again. + absl::optional recheck_delay_ms; + }; + + virtual ~IceControllerInterface() = default; + + // These setters are called when the state of P2PTransportChannel is mutated. + virtual void SetIceConfig(const IceConfig& config) = 0; + virtual void SetSelectedConnection(const Connection* selected_connection) = 0; + virtual void AddConnection(const Connection* connection) = 0; + virtual void OnConnectionDestroyed(const Connection* connection) = 0; + + // These are all connections that has been added and not destroyed. + virtual rtc::ArrayView connections() const = 0; + + // Is there a pingable connection ? + // This function is used to boot-strap pinging, after this returns true + // SelectConnectionToPing() will be called periodically. + virtual bool HasPingableConnection() const = 0; + + // Select a connection to Ping, or nullptr if none. + virtual std::pair SelectConnectionToPing( + int64_t last_ping_sent_ms) = 0; + + // Compute the "STUN_ATTR_USE_CANDIDATE" for |conn|. + virtual bool GetUseCandidateAttr(const Connection* conn, + NominationMode mode, + IceMode remote_ice_mode) const = 0; + + // These methods is only added to not have to change all unit tests + // that simulate pinging by marking a connection pinged. + virtual const Connection* FindNextPingableConnection() = 0; + virtual void MarkConnectionPinged(const Connection* con) = 0; + + // Check if we should switch to |connection|. + // This method is called for IceControllerEvent's that can switch directly + // i.e without resorting. + virtual SwitchResult ShouldSwitchConnection(IceControllerEvent reason, + const Connection* connection) = 0; + + // Sort connections and check if we should switch. + virtual SwitchResult SortAndSwitchConnection(IceControllerEvent reason) = 0; + + // Prune connections. + virtual std::vector PruneConnections() = 0; +}; + +} // namespace cricket + +#endif // P2P_BASE_ICE_CONTROLLER_INTERFACE_H_ diff --git a/p2p/base/p2p_transport_channel.cc b/p2p/base/p2p_transport_channel.cc index 7f2b37ac27..b5f57dc32f 100644 --- a/p2p/base/p2p_transport_channel.cc +++ b/p2p/base/p2p_transport_channel.cc @@ -19,6 +19,7 @@ #include "absl/strings/match.h" #include "api/candidate.h" #include "logging/rtc_event_log/ice_logger.h" +#include "p2p/base/basic_ice_controller.h" #include "p2p/base/candidate_pair_interface.h" #include "p2p/base/connection.h" #include "p2p/base/port.h" @@ -35,18 +36,6 @@ 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(cricket::Connection* conn) { - return conn->local_candidate().relay_protocol() == cricket::UDP_PROTOCOL_NAME; -} - cricket::PortInterface::CandidateOrigin GetOrigin( cricket::PortInterface* port, cricket::PortInterface* origin_port) { @@ -58,35 +47,6 @@ cricket::PortInterface::CandidateOrigin GetOrigin( 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, - absl::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, - 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; -} - uint32_t GetWeakPingIntervalInFieldTrial() { uint32_t weak_ping_interval = ::strtoul( webrtc::field_trial::FindFullName("WebRTC-StunInterPacketDelay").c_str(), @@ -162,9 +122,21 @@ P2PTransportChannel::P2PTransportChannel( allocator_->SignalCandidateFilterChanged.connect( this, &P2PTransportChannel::OnCandidateFilterChanged); ice_event_log_.set_event_log(event_log); + + ice_controller_ = std::make_unique( + [this] { return GetState(); }, [this] { return GetIceRole(); }, + [this](const Connection* connection) { + return IsPortPruned(connection->port()) || + IsRemoteCandidatePruned(connection->remote_candidate()); + }, + &field_trials_); } P2PTransportChannel::~P2PTransportChannel() { + std::vector copy(connections().begin(), connections().end()); + for (Connection* con : copy) { + con->Destroy(); + } for (auto& p : resolvers_) { p.resolver_->Destroy(false); } @@ -203,8 +175,6 @@ void P2PTransportChannel::AddAllocatorSession( void P2PTransportChannel::AddConnection(Connection* connection) { RTC_DCHECK_RUN_ON(network_thread_); - connections_.push_back(connection); - unpinged_connections_.insert(connection); connection->set_remote_ice_mode(remote_ice_mode_); connection->set_receiving_timeout(config_.receiving_timeout); connection->set_unwritable_timeout(config_.ice_unwritable_timeout); @@ -225,155 +195,44 @@ void P2PTransportChannel::AddConnection(Connection* connection) { connection->set_ice_event_log(&ice_event_log_); LogCandidatePairConfig(connection, webrtc::IceCandidatePairConfigType::kAdded); -} -// Determines whether we should switch the selected connection to -// |new_connection| based the writable/receiving state, the nomination state, -// and the last data received time. This prevents the controlled side from -// switching the selected connection too frequently when the controlling side -// is doing aggressive nominations. The precedence of the connection switching -// criteria is as follows: -// i) write/receiving/connected states -// ii) For controlled side, -// a) nomination state, -// b) last data received time. -// iii) Lower cost / higher priority. -// iv) rtt. -// To further prevent switching to high-cost networks, does not switch to -// a high-cost connection if it is not receiving. -// TODO(honghaiz): Stop the aggressive nomination on the controlling side and -// implement the ice-renomination option. -bool P2PTransportChannel::ShouldSwitchSelectedConnection( - Connection* new_connection, - bool* missed_receiving_unchanged_threshold) const { - RTC_DCHECK_RUN_ON(network_thread_); - if (!ReadyToSend(new_connection) || selected_connection_ == new_connection) { - return false; - } - - if (selected_connection_ == nullptr) { - return true; - } - - // 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; - } - - 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); - if (cmp != 0) { - return cmp < 0; - } - - // If everything else is the same, switch only if rtt has improved by - // a margin. - return new_connection->rtt() <= selected_connection_->rtt() - kMinImprovement; -} - -bool P2PTransportChannel::HandleInitialSelectDampening( - Connection* new_connection, - const std::string& reason) { - RTC_DCHECK_RUN_ON(network_thread_); - if (!field_trials_.initial_select_dampening.has_value() && - !field_trials_.initial_select_dampening_ping_received.has_value()) { - // experiment not enabled. - return true; - } - - 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 true; - } - - // 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); - } - - const std::string reason_to_sort = - reason + " (after initial select dampening interval: " + - std::to_string(max_delay) + ")"; - invoker_.AsyncInvokeDelayed( - RTC_FROM_HERE, thread(), - rtc::Bind(&P2PTransportChannel::SortConnectionsAndUpdateState, this, - reason_to_sort), - min_delay); - RTC_LOG(LS_INFO) << "delay initial selection up to " << min_delay << "ms"; - return false; + ice_controller_->AddConnection(connection); } bool P2PTransportChannel::MaybeSwitchSelectedConnection( Connection* new_connection, - const std::string& reason) { + IceControllerEvent reason) { RTC_DCHECK_RUN_ON(network_thread_); - if (selected_connection_ == nullptr && ReadyToSend(new_connection)) { - if (!HandleInitialSelectDampening(new_connection, reason)) { - // Delay the initial selection a while waiting for a better connection. - return false; - } + return MaybeSwitchSelectedConnection( + reason, ice_controller_->ShouldSwitchConnection(reason, new_connection)); +} + +bool P2PTransportChannel::MaybeSwitchSelectedConnection( + IceControllerEvent reason, + IceControllerInterface::SwitchResult result) { + RTC_DCHECK_RUN_ON(network_thread_); + if (result.connection.has_value()) { + RTC_LOG(LS_INFO) << "Switching selected connection due to: " + << reason.ToString(); + SwitchSelectedConnection(const_cast(*result.connection), + reason); } - bool missed_receiving_unchanged_threshold = false; - if (ShouldSwitchSelectedConnection(new_connection, - &missed_receiving_unchanged_threshold)) { - RTC_LOG(LS_INFO) << "Switching selected connection due to: " << reason; - SwitchSelectedConnection(new_connection, reason); - return true; - } - if (missed_receiving_unchanged_threshold && - config_.receiving_switching_delay_or_default()) { + if (result.recheck_delay_ms.has_value()) { // 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. - const std::string reason_to_sort = - reason + " (after switching dampening interval)"; + reason.dampening_delay = *result.recheck_delay_ms; invoker_.AsyncInvokeDelayed( RTC_FROM_HERE, thread(), rtc::Bind(&P2PTransportChannel::SortConnectionsAndUpdateState, this, - reason_to_sort), - config_.receiving_switching_delay_or_default()); + reason), + reason.dampening_delay); } - return false; + + return result.connection.has_value(); } void P2PTransportChannel::SetIceRole(IceRole ice_role) { @@ -475,7 +334,7 @@ IceTransportState P2PTransportChannel::ComputeState() const { } std::vector active_connections; - for (Connection* connection : connections_) { + for (Connection* connection : connections()) { if (connection->active()) { active_connections.push_back(connection); } @@ -510,7 +369,7 @@ webrtc::IceTransportState P2PTransportChannel::ComputeIceTransportState() const { RTC_DCHECK_RUN_ON(network_thread_); bool has_connection = false; - for (Connection* connection : connections_) { + for (Connection* connection : connections()) { if (connection->active()) { has_connection = true; break; @@ -570,12 +429,13 @@ void P2PTransportChannel::SetRemoteIceParameters( } // We need to update the credentials and generation for any peer reflexive // candidates. - for (Connection* conn : connections_) { + for (Connection* conn : connections()) { conn->MaybeSetRemoteIceParametersAndGeneration( ice_params, static_cast(remote_ice_parameters_.size() - 1)); } // Updating the remote ICE candidate generation could change the sort order. - RequestSortAndStateUpdate("remote candidate generation maybe changed"); + RequestSortAndStateUpdate( + IceControllerEvent::REMOTE_CANDIDATE_GENERATION_CHANGE); } void P2PTransportChannel::SetRemoteIceMode(IceMode mode) { @@ -611,7 +471,7 @@ void P2PTransportChannel::SetIceConfig(const IceConfig& config) { } if (config_.receiving_timeout != config.receiving_timeout) { config_.receiving_timeout = config.receiving_timeout; - for (Connection* connection : connections_) { + for (Connection* connection : connections()) { connection->set_receiving_timeout(config_.receiving_timeout); } RTC_LOG(LS_INFO) << "Set ICE receiving timeout to " @@ -635,7 +495,7 @@ void P2PTransportChannel::SetIceConfig(const IceConfig& config) { if (config_.presume_writable_when_fully_relayed != config.presume_writable_when_fully_relayed) { - if (!connections_.empty()) { + if (!connections().empty()) { RTC_LOG(LS_ERROR) << "Trying to change 'presume writable' " "while connections already exist!"; } else { @@ -714,7 +574,7 @@ void P2PTransportChannel::SetIceConfig(const IceConfig& config) { if (config_.ice_unwritable_timeout != config.ice_unwritable_timeout) { config_.ice_unwritable_timeout = config.ice_unwritable_timeout; - for (Connection* conn : connections_) { + for (Connection* conn : connections()) { conn->set_unwritable_timeout(config_.ice_unwritable_timeout); } RTC_LOG(LS_INFO) << "Set unwritable timeout to " @@ -723,7 +583,7 @@ void P2PTransportChannel::SetIceConfig(const IceConfig& config) { if (config_.ice_unwritable_min_checks != config.ice_unwritable_min_checks) { config_.ice_unwritable_min_checks = config.ice_unwritable_min_checks; - for (Connection* conn : connections_) { + for (Connection* conn : connections()) { conn->set_unwritable_min_checks(config_.ice_unwritable_min_checks); } RTC_LOG(LS_INFO) << "Set unwritable min checks to " @@ -732,7 +592,7 @@ void P2PTransportChannel::SetIceConfig(const IceConfig& config) { if (config_.ice_inactive_timeout != config.ice_inactive_timeout) { config_.ice_inactive_timeout = config.ice_inactive_timeout; - for (Connection* conn : connections_) { + for (Connection* conn : connections()) { conn->set_inactive_timeout(config_.ice_inactive_timeout); } RTC_LOG(LS_INFO) << "Set inactive timeout to " @@ -741,7 +601,7 @@ void P2PTransportChannel::SetIceConfig(const IceConfig& config) { if (config_.network_preference != config.network_preference) { config_.network_preference = config.network_preference; - RequestSortAndStateUpdate("network preference changed"); + RequestSortAndStateUpdate(IceControllerEvent::NETWORK_PREFERENCE_CHANGE); RTC_LOG(LS_INFO) << "Set network preference to " << (config_.network_preference.has_value() ? config_.network_preference.value() @@ -799,6 +659,8 @@ void P2PTransportChannel::SetIceConfig(const IceConfig& config) { config_.regather_on_failed_networks_interval_or_default()); regathering_controller_->SetConfig(regathering_config); + ice_controller_->SetIceConfig(config_); + RTC_DCHECK(ValidateIceConfig(config_).ok()); } @@ -980,7 +842,7 @@ void P2PTransportChannel::OnPortReady(PortAllocatorSession* session, } SortConnectionsAndUpdateState( - "new candidate pairs created from a new local candidate"); + IceControllerEvent::NEW_CONNECTION_FROM_LOCAL_CANDIDATE); } // A new candidate is available, let listeners know @@ -1157,7 +1019,7 @@ void P2PTransportChannel::OnUnknownAddress(PortInterface* port, // after sending the response since it could (in principle) delete the // connection in question. SortConnectionsAndUpdateState( - "a new candidate pair created from an unknown remote address"); + IceControllerEvent::NEW_CONNECTION_FROM_UNKNOWN_REMOTE_ADDRESS); } void P2PTransportChannel::OnCandidateFilterChanged(uint32_t prev_filter, @@ -1202,11 +1064,12 @@ void P2PTransportChannel::OnNominated(Connection* conn) { // TODO(qingsi): RequestSortAndStateUpdate will eventually call // MaybeSwitchSelectedConnection again. Rewrite this logic. - if (MaybeSwitchSelectedConnection(conn, - "nomination on the controlled side")) { + if (MaybeSwitchSelectedConnection( + conn, IceControllerEvent::NOMINATION_ON_CONTROLLED_SIDE)) { // Now that we have selected a connection, it is time to prune other // connections and update the read/write state of the channel. - RequestSortAndStateUpdate("nomination on the controlled side"); + RequestSortAndStateUpdate( + IceControllerEvent::NOMINATION_ON_CONTROLLED_SIDE); } else { RTC_LOG(LS_INFO) << "Not switching the selected connection on controlled side yet: " @@ -1337,7 +1200,7 @@ void P2PTransportChannel::FinishAddingRemoteCandidate( RTC_DCHECK_RUN_ON(network_thread_); // If this candidate matches what was thought to be a peer reflexive // candidate, we need to update the candidate priority/etc. - for (Connection* conn : connections_) { + for (Connection* conn : connections()) { conn->MaybeUpdatePeerReflexiveCandidate(new_remote_candidate); } @@ -1346,7 +1209,7 @@ void P2PTransportChannel::FinishAddingRemoteCandidate( // Resort the connections list, which may have new elements. SortConnectionsAndUpdateState( - "new candidate pairs created from a new remote candidate"); + IceControllerEvent::NEW_CONNECTION_FROM_REMOTE_CANDIDATE); } void P2PTransportChannel::RemoveRemoteCandidate( @@ -1453,7 +1316,7 @@ bool P2PTransportChannel::CreateConnection(PortInterface* port, AddConnection(connection); RTC_LOG(LS_INFO) << ToString() << ": Created connection with origin: " << origin - << ", total: " << connections_.size(); + << ", total: " << connections().size(); return true; } @@ -1472,7 +1335,7 @@ bool P2PTransportChannel::CreateConnection(PortInterface* port, bool P2PTransportChannel::FindConnection(Connection* connection) const { RTC_DCHECK_RUN_ON(network_thread_); - return absl::c_linear_search(connections_, connection); + return absl::c_linear_search(connections(), connection); } uint32_t P2PTransportChannel::GetRemoteCandidateGeneration( @@ -1618,7 +1481,7 @@ bool P2PTransportChannel::GetStats(IceTransportStats* ice_transport_stats) { } // TODO(qingsi): Remove naming inconsistency for candidate pair/connection. - for (Connection* connection : connections_) { + for (Connection* connection : connections()) { ConnectionInfo stats = connection->stats(); stats.local_candidate = SanitizeLocalCandidate(stats.local_candidate); stats.remote_candidate = SanitizeRemoteCandidate(stats.remote_candidate); @@ -1646,6 +1509,13 @@ rtc::DiffServCodePoint P2PTransportChannel::DefaultDscpValue() const { return static_cast(it->second); } +rtc::ArrayView P2PTransportChannel::connections() const { + RTC_DCHECK_RUN_ON(network_thread_); + rtc::ArrayView res = ice_controller_->connections(); + return rtc::ArrayView(const_cast(res.data()), + res.size()); +} + // Monitor connection states. void P2PTransportChannel::UpdateConnectionStates() { RTC_DCHECK_RUN_ON(network_thread_); @@ -1653,14 +1523,14 @@ void P2PTransportChannel::UpdateConnectionStates() { // We need to copy the list of connections since some may delete themselves // when we call UpdateState. - for (Connection* c : connections_) { + for (Connection* c : connections()) { c->UpdateState(now); } } // Prepare for best candidate sorting. void P2PTransportChannel::RequestSortAndStateUpdate( - const std::string& reason_to_sort) { + IceControllerEvent reason_to_sort) { RTC_DCHECK_RUN_ON(network_thread_); if (!sort_dirty_) { invoker_.AsyncInvoke( @@ -1677,10 +1547,7 @@ void P2PTransportChannel::MaybeStartPinging() { return; } - int64_t now = rtc::TimeMillis(); - if (absl::c_any_of(connections_, [this, now](const Connection* c) { - return IsPingable(c, now); - })) { + if (ice_controller_->HasPingableConnection()) { RTC_LOG(LS_INFO) << ToString() << ": Have a pingable connection for the first time; " "starting to ping."; @@ -1692,156 +1559,6 @@ void P2PTransportChannel::MaybeStartPinging() { } } -int P2PTransportChannel::CompareCandidatePairNetworks( - const Connection* a, - const Connection* b, - absl::optional network_preference) const { - RTC_DCHECK_RUN_ON(network_thread_); - 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( - const Connection* a, - const Connection* b, - absl::optional receiving_unchanged_threshold, - bool* missed_receiving_unchanged_threshold) const { - RTC_DCHECK_RUN_ON(network_thread_); - // 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 P2PTransportChannel::CompareConnectionCandidates( - const Connection* a, - const Connection* b) const { - RTC_DCHECK_RUN_ON(network_thread_); - 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->port()->generation()) - - (b->remote_candidate().generation() + b->port()->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 = - IsPortPruned(a->port()) || IsRemoteCandidatePruned(a->remote_candidate()); - bool b_pruned = - IsPortPruned(b->port()) || IsRemoteCandidatePruned(b->remote_candidate()); - if (!a_pruned && b_pruned) { - return a_is_better; - } - if (a_pruned && !b_pruned) { - return b_is_better; - } - - // Otherwise, must be equal - return 0; -} - bool P2PTransportChannel::IsPortPruned(const Port* port) const { RTC_DCHECK_RUN_ON(network_thread_); return !absl::c_linear_search(ports_, port); @@ -1852,46 +1569,6 @@ bool P2PTransportChannel::IsRemoteCandidatePruned(const Candidate& cand) const { return !absl::c_linear_search(remote_candidates_, cand); } -int P2PTransportChannel::CompareConnections( - const Connection* a, - const Connection* b, - absl::optional receiving_unchanged_threshold, - bool* missed_receiving_unchanged_threshold) const { - RTC_DCHECK_RUN_ON(network_thread_); - 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_ == 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); -} - bool P2PTransportChannel::PresumedWritable(const Connection* conn) const { RTC_DCHECK_RUN_ON(network_thread_); return (conn->write_state() == Connection::STATE_WRITE_INIT && @@ -1904,7 +1581,7 @@ bool P2PTransportChannel::PresumedWritable(const Connection* conn) const { // Sort the available connections to find the best one. We also monitor // the number of available connections and the current state. void P2PTransportChannel::SortConnectionsAndUpdateState( - const std::string& reason_to_sort) { + IceControllerEvent reason_to_sort) { RTC_DCHECK_RUN_ON(network_thread_); // Make sure the connection states are up-to-date since this affects how they @@ -1914,34 +1591,11 @@ void P2PTransportChannel::SortConnectionsAndUpdateState( // Any changes after this point will require a re-sort. sort_dirty_ = false; - // 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(); - } - - Connection* top_connection = - (connections_.size() > 0) ? connections_[0] : nullptr; - // If necessary, switch to the new choice. Note that |top_connection| doesn't // have to be writable to become the selected connection although it will // have higher priority if it is writable. - MaybeSwitchSelectedConnection(top_connection, reason_to_sort); + MaybeSwitchSelectedConnection( + reason_to_sort, ice_controller_->SortAndSwitchConnection(reason_to_sort)); // The controlled side can prune only if the selected connection has been // nominated because otherwise it may prune the connection that will be @@ -1956,8 +1610,8 @@ void P2PTransportChannel::SortConnectionsAndUpdateState( // Check if all connections are timedout. bool all_connections_timedout = true; - for (size_t i = 0; i < connections_.size(); ++i) { - if (connections_[i]->write_state() != Connection::STATE_WRITE_TIMEOUT) { + for (const Connection* conn : connections()) { + if (conn->write_state() != Connection::STATE_WRITE_TIMEOUT) { all_connections_timedout = false; break; } @@ -1980,77 +1634,18 @@ void P2PTransportChannel::SortConnectionsAndUpdateState( MaybeStartPinging(); } -std::map -P2PTransportChannel::GetBestConnectionByNetwork() const { - RTC_DCHECK_RUN_ON(network_thread_); - // |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_->port()->Network()] = - selected_connection_; - } - // TODO(honghaiz): Need to update this if |connections_| are not sorted. - for (Connection* conn : connections_) { - rtc::Network* network = conn->port()->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 -P2PTransportChannel::GetBestWritableConnectionPerNetwork() const { - std::vector connections; - for (auto kv : GetBestConnectionByNetwork()) { - Connection* conn = kv.second; - if (conn->writable() && conn->connected()) { - connections.push_back(conn); - } - } - return connections; -} - void P2PTransportChannel::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. RTC_DCHECK_RUN_ON(network_thread_); - auto best_connection_by_network = GetBestConnectionByNetwork(); - for (Connection* conn : connections_) { - Connection* best_conn = selected_connection_; - if (!rtc::IPIsAny(conn->port()->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->port()->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) { - conn->Prune(); - } + std::vector connections_to_prune = + ice_controller_->PruneConnections(); + for (const Connection* conn : connections_to_prune) { + const_cast(conn)->Prune(); } } // Change the selected connection, and let listeners know. void P2PTransportChannel::SwitchSelectedConnection(Connection* conn, - const std::string& reason) { + IceControllerEvent reason) { RTC_DCHECK_RUN_ON(network_thread_); // Note: if conn is NULL, the previous |selected_connection_| has been // destroyed, so don't use it. @@ -2100,7 +1695,7 @@ void P2PTransportChannel::SwitchSelectedConnection(Connection* conn, // Create event for candidate pair change. if (selected_connection_) { CandidatePairChangeEvent pair_change; - pair_change.reason = reason; + pair_change.reason = reason.ToString(); pair_change.selected_candidate_pair = *GetSelectedCandidatePair(); pair_change.last_data_received_ms = selected_connection_->last_data_received(); @@ -2108,6 +1703,8 @@ void P2PTransportChannel::SwitchSelectedConnection(Connection* conn, } ++selected_candidate_pair_changes_; + + ice_controller_->SetSelectedConnection(selected_connection_); } // Warning: UpdateState should eventually be called whenever a connection @@ -2127,7 +1724,7 @@ void P2PTransportChannel::UpdateState() { SetWritable(writable); bool receiving = false; - for (const Connection* connection : connections_) { + for (const Connection* connection : connections()) { if (connection->receiving()) { receiving = true; break; @@ -2208,16 +1805,11 @@ void P2PTransportChannel::MaybeStopPortAllocatorSessions() { // If all connections timed out, delete them all. void P2PTransportChannel::HandleAllTimedOut() { RTC_DCHECK_RUN_ON(network_thread_); - for (Connection* connection : connections_) { + for (Connection* connection : connections()) { connection->Destroy(); } } -bool P2PTransportChannel::weak() const { - RTC_DCHECK_RUN_ON(network_thread_); - return !selected_connection_ || selected_connection_->weak(); -} - bool P2PTransportChannel::ReadyToSend(Connection* connection) const { RTC_DCHECK_RUN_ON(network_thread_); // Note that we allow sending on an unreliable connection, because it's @@ -2235,212 +1827,32 @@ void P2PTransportChannel::CheckAndPing() { // Make sure the states of the connections are up-to-date (since this affects // which ones are pingable). UpdateConnectionStates(); - // 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_, [](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(); - if (rtc::TimeMillis() >= last_ping_sent_ms_ + ping_interval) { - Connection* conn = FindNextPingableConnection(); - if (conn) { - PingConnection(conn); - MarkConnectionPinged(conn); - } + + auto result = ice_controller_->SelectConnectionToPing(last_ping_sent_ms_); + Connection* conn = result.first; + int delay = result.second; + + if (conn) { + PingConnection(conn); + MarkConnectionPinged(conn); } - int delay = std::min(ping_interval, check_receiving_interval()); + invoker_.AsyncInvokeDelayed( RTC_FROM_HERE, thread(), rtc::Bind(&P2PTransportChannel::CheckAndPing, this), delay); } -// 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 P2PTransportChannel::IsBackupConnection(const Connection* conn) const { - RTC_DCHECK_RUN_ON(network_thread_); - return state_ == IceTransportState::STATE_COMPLETED && - conn != selected_connection_ && conn->active(); -} - -// 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 P2PTransportChannel::IsPingable(const Connection* conn, - int64_t now) const { - RTC_DCHECK_RUN_ON(network_thread_); - 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); -} - -bool P2PTransportChannel::WritableConnectionPastPingInterval( - const Connection* conn, - int64_t now) const { - RTC_DCHECK_RUN_ON(network_thread_); - int interval = CalculateActiveWritablePingInterval(conn, now); - return conn->last_ping_sent() + interval <= now; -} - -int P2PTransportChannel::CalculateActiveWritablePingInterval( - const Connection* conn, - int64_t now) const { - RTC_DCHECK_RUN_ON(network_thread_); - // 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; -} - -// Returns the next pingable connection to ping. +// This method is only for unit testing. Connection* P2PTransportChannel::FindNextPingableConnection() { RTC_DCHECK_RUN_ON(network_thread_); - 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](Connection* conn) { - return WritableConnectionPastPingInterval(conn, now); - }); - auto iter = absl::c_min_element(pingable_selectable_connections, - [](Connection* conn1, 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. - 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](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](Connection* conn) { return IsPingable(conn, now); }); - auto iter = absl::c_max_element(pingable_connections, - [this](Connection* conn1, 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; + return const_cast(ice_controller_->FindNextPingableConnection()); } +// A connection is considered a backup connection if the channel state +// is completed, the connection is not the selected connection and it is active. void P2PTransportChannel::MarkConnectionPinged(Connection* conn) { RTC_DCHECK_RUN_ON(network_thread_); - if (conn && pinged_connections_.insert(conn).second) { - unpinged_connections_.erase(conn); - } + ice_controller_->MarkConnectionPinged(conn); } // Apart from sending ping from |conn| this method also updates @@ -2457,8 +1869,7 @@ void P2PTransportChannel::PingConnection(Connection* conn) { if (renomination_supported) { nomination = GetNominationAttr(conn); } else { - use_candidate_attr = - GetUseCandidateAttr(conn, config_.default_nomination_mode); + use_candidate_attr = GetUseCandidateAttr(conn); } } conn->set_nomination(nomination); @@ -2473,41 +1884,10 @@ uint32_t P2PTransportChannel::GetNominationAttr(Connection* conn) const { } // Nominate a connection based on the NominationMode. -bool P2PTransportChannel::GetUseCandidateAttr(Connection* conn, - NominationMode mode) const { +bool P2PTransportChannel::GetUseCandidateAttr(Connection* conn) const { RTC_DCHECK_RUN_ON(network_thread_); - 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); - } - 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_NOTREACHED(); - return false; - } + return ice_controller_->GetUseCandidateAttr( + conn, config_.default_nomination_mode, remote_ice_mode_); } // When a connection's state changes, we need to figure out who to use as @@ -2529,7 +1909,9 @@ void P2PTransportChannel::OnConnectionStateChange(Connection* connection) { } // We have to unroll the stack before doing this because we may be changing // the state of connections while sorting. - RequestSortAndStateUpdate("candidate pair state changed"); + RequestSortAndStateUpdate( + IceControllerEvent::CONNECT_STATE_CHANGE); // "candidate pair state + // changed"); } // When a connection is removed, edit it out, and then update our best @@ -2541,14 +1923,10 @@ void P2PTransportChannel::OnConnectionDestroyed(Connection* connection) { // use it. // Remove this connection from the list. - auto iter = absl::c_find(connections_, connection); - RTC_DCHECK(iter != connections_.end()); - pinged_connections_.erase(connection); - unpinged_connections_.erase(connection); - connections_.erase(iter); + ice_controller_->OnConnectionDestroyed(connection); RTC_LOG(LS_INFO) << ToString() << ": Removed connection " << connection - << " (" << connections_.size() << " remaining)"; + << " (" << connections().size() << " remaining)"; // If this is currently the selected connection, then we need to pick a new // one. The call to SortConnectionsAndUpdateState will pick a new one. It @@ -2558,7 +1936,8 @@ void P2PTransportChannel::OnConnectionDestroyed(Connection* connection) { // there was no selected connection. if (selected_connection_ == connection) { RTC_LOG(LS_INFO) << "Selected connection destroyed. Will choose a new one."; - const std::string reason = "selected candidate pair destroyed"; + IceControllerEvent reason = + IceControllerEvent::SELECTED_CONNECTION_DESTROYED; SwitchSelectedConnection(nullptr, reason); RequestSortAndStateUpdate(reason); } else { @@ -2638,6 +2017,12 @@ void P2PTransportChannel::OnReadPacket(Connection* connection, int64_t packet_time_us) { RTC_DCHECK_RUN_ON(network_thread_); + if (connection == selected_connection_) { + // Let the client know of an incoming packet + SignalReadPacket(this, data, len, packet_time_us, 0); + return; + } + // Do not deliver, if packet doesn't belong to the correct transport channel. if (!FindConnection(connection)) return; @@ -2648,7 +2033,8 @@ void P2PTransportChannel::OnReadPacket(Connection* connection, // May need to switch the sending connection based on the receiving media path // if this is the controlled side. if (ice_role_ == ICEROLE_CONTROLLED) { - MaybeSwitchSelectedConnection(connection, "data received"); + MaybeSwitchSelectedConnection(connection, + IceControllerEvent::DATA_RECEIVED); } } @@ -2665,92 +2051,6 @@ void P2PTransportChannel::OnReadyToSend(Connection* connection) { } } -// 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. -Connection* P2PTransportChannel::FindOldestConnectionNeedingTriggeredCheck( - int64_t now) { - RTC_DCHECK_RUN_ON(network_thread_); - 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; -} - -Connection* P2PTransportChannel::MostLikelyToWork(Connection* conn1, - Connection* conn2) { - RTC_DCHECK_RUN_ON(network_thread_); - 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; -} - -Connection* P2PTransportChannel::LeastRecentlyPinged(Connection* conn1, - Connection* conn2) { - RTC_DCHECK_RUN_ON(network_thread_); - if (conn1->last_ping_sent() < conn2->last_ping_sent()) { - return conn1; - } - if (conn1->last_ping_sent() > conn2->last_ping_sent()) { - return conn2; - } - return nullptr; -} - -Connection* P2PTransportChannel::MorePingable(Connection* conn1, - Connection* conn2) { - RTC_DCHECK_RUN_ON(network_thread_); - RTC_DCHECK(conn1 != conn2); - if (config_.prioritize_most_likely_candidate_pairs) { - Connection* most_likely_to_work_conn = MostLikelyToWork(conn1, conn2); - if (most_likely_to_work_conn) { - return most_likely_to_work_conn; - } - } - - 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_|. - return *(absl::c_find_if(connections_, [conn1, conn2](Connection* conn) { - return conn == conn1 || conn == conn2; - })); -} - void P2PTransportChannel::SetWritable(bool writable) { RTC_DCHECK_RUN_ON(network_thread_); if (writable_ == writable) { diff --git a/p2p/base/p2p_transport_channel.h b/p2p/base/p2p_transport_channel.h index 7ce0651c9a..ee0cca2402 100644 --- a/p2p/base/p2p_transport_channel.h +++ b/p2p/base/p2p_transport_channel.h @@ -33,6 +33,7 @@ #include "logging/rtc_event_log/events/rtc_event_ice_candidate_pair_config.h" #include "logging/rtc_event_log/ice_logger.h" #include "p2p/base/candidate_pair_interface.h" +#include "p2p/base/ice_controller_interface.h" #include "p2p/base/ice_transport_internal.h" #include "p2p/base/p2p_constants.h" #include "p2p/base/port_allocator.h" @@ -191,7 +192,7 @@ class RTC_EXPORT P2PTransportChannel : public IceTransportInternal { void MarkConnectionPinged(Connection* conn); // Public for unit tests. - const std::vector& connections() const { return connections_; } + rtc::ArrayView connections() const; // Public for unit tests. PortAllocatorSession* allocator_session() const { @@ -226,63 +227,19 @@ class RTC_EXPORT P2PTransportChannel : public IceTransportInternal { return allocator_session()->IsGettingPorts(); } - // A transport channel is weak if the current best connection is either - // not receiving or not writable, or if there is no best connection at all. - bool weak() const; - - int weak_ping_interval() const { - RTC_DCHECK_RUN_ON(network_thread_); - return std::max(config_.ice_check_interval_weak_connectivity_or_default(), - config_.ice_check_min_interval_or_default()); - } - - int strong_ping_interval() const { - RTC_DCHECK_RUN_ON(network_thread_); - return std::max(config_.ice_check_interval_strong_connectivity_or_default(), - config_.ice_check_min_interval_or_default()); - } - // Returns true if it's possible to send packets on |connection|. bool ReadyToSend(Connection* connection) const; + bool PresumedWritable(const Connection* conn) const; void UpdateConnectionStates(); - void RequestSortAndStateUpdate(const std::string& reason_to_sort); + void RequestSortAndStateUpdate(IceControllerEvent reason_to_sort); // Start pinging if we haven't already started, and we now have a connection // that's pingable. void MaybeStartPinging(); - int CompareCandidatePairNetworks( - const Connection* a, - const Connection* b, - absl::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 - // |a| is not, returns a negative value only if |b| has been in receiving - // state and |a| has been in not receiving state since - // |receiving_unchanged_threshold| and sets - // |missed_receiving_unchanged_threshold| to true otherwise. - int CompareConnectionStates( - const cricket::Connection* a, - const cricket::Connection* b, - absl::optional receiving_unchanged_threshold, - bool* missed_receiving_unchanged_threshold) const; - int CompareConnectionCandidates(const cricket::Connection* a, - const cricket::Connection* b) const; - // Compares two connections based on the connection states - // (writable/receiving/connected), nomination states, last data received time, - // and static preferences. Does not include latency. Used by both sorting - // and ShouldSwitchSelectedConnection(). - // Returns a positive value if |a| is better than |b|. - int CompareConnections(const cricket::Connection* a, - const cricket::Connection* b, - absl::optional receiving_unchanged_threshold, - bool* missed_receiving_unchanged_threshold) const; - - bool PresumedWritable(const cricket::Connection* conn) const; - - void SortConnectionsAndUpdateState(const std::string& reason_to_sort); - void SwitchSelectedConnection(Connection* conn, const std::string& reason); + void SortConnectionsAndUpdateState(IceControllerEvent reason_to_sort); + void SortConnections(); + void SortConnectionsIfNeeded(); + void SwitchSelectedConnection(Connection* conn, IceControllerEvent reason); void UpdateState(); void HandleAllTimedOut(); void MaybeStopPortAllocatorSessions(); @@ -294,25 +251,17 @@ class RTC_EXPORT P2PTransportChannel : public IceTransportInternal { IceTransportState ComputeState() const; webrtc::IceTransportState ComputeIceTransportState() const; - Connection* GetBestConnectionOnNetwork(rtc::Network* network) const; bool CreateConnections(const Candidate& remote_candidate, PortInterface* origin_port); bool CreateConnection(PortInterface* port, const Candidate& remote_candidate, PortInterface* origin_port); - bool FindConnection(cricket::Connection* connection) const; + bool FindConnection(Connection* connection) const; uint32_t GetRemoteCandidateGeneration(const Candidate& candidate); bool IsDuplicateRemoteCandidate(const Candidate& candidate); void RememberRemoteCandidate(const Candidate& remote_candidate, PortInterface* origin_port); - bool IsPingable(const Connection* conn, int64_t now) const; - // Whether a writable connection is past its ping interval and needs to be - // pinged again. - bool WritableConnectionPastPingInterval(const Connection* conn, - int64_t now) const; - int CalculateActiveWritablePingInterval(const Connection* conn, - int64_t now) const; void PingConnection(Connection* conn); void AddAllocatorSession(std::unique_ptr session); void AddConnection(Connection* connection); @@ -360,33 +309,15 @@ class RTC_EXPORT P2PTransportChannel : public IceTransportInternal { webrtc::IceCandidatePairConfigType type); uint32_t GetNominationAttr(Connection* conn) const; - bool GetUseCandidateAttr(Connection* conn, NominationMode mode) const; + bool GetUseCandidateAttr(Connection* conn) const; - // Returns true if we should switch to the new connection. - // sets |missed_receiving_unchanged_threshold| to true if either - // the selected connection or the new connection missed its - // receiving-unchanged-threshold. - bool ShouldSwitchSelectedConnection( - Connection* new_connection, - bool* missed_receiving_unchanged_threshold) const; // Returns true if the new_connection is selected for transmission. bool MaybeSwitchSelectedConnection(Connection* new_connection, - const std::string& reason); - // Gets the best connection for each network. - std::map GetBestConnectionByNetwork() const; - std::vector GetBestWritableConnectionPerNetwork() const; + IceControllerEvent reason); + bool MaybeSwitchSelectedConnection( + IceControllerEvent reason, + IceControllerInterface::SwitchResult result); void PruneConnections(); - bool IsBackupConnection(const Connection* conn) const; - - Connection* FindOldestConnectionNeedingTriggeredCheck(int64_t now); - // Between |conn1| and |conn2|, this function returns the one which should - // be pinged first. - Connection* MorePingable(Connection* conn1, Connection* conn2); - // Select the connection which is Relay/Relay. If both of them are, - // UDP relay protocol takes precedence. - Connection* MostLikelyToWork(Connection* conn1, Connection* conn2); - // Compare the last_ping_sent time and return the one least recently pinged. - Connection* LeastRecentlyPinged(Connection* conn1, Connection* conn2); // Returns the latest remote ICE parameters or nullptr if there are no remote // ICE parameters yet. @@ -428,9 +359,6 @@ class RTC_EXPORT P2PTransportChannel : public IceTransportInternal { // 2. Peer-reflexive remote candidates. Candidate SanitizeRemoteCandidate(const Candidate& c) const; - bool HandleInitialSelectDampening(Connection* new_connection, - const std::string& reason); - std::string transport_name_ RTC_GUARDED_BY(network_thread_); int component_ RTC_GUARDED_BY(network_thread_); PortAllocator* allocator_ RTC_GUARDED_BY(network_thread_); @@ -450,15 +378,6 @@ class RTC_EXPORT P2PTransportChannel : public IceTransportInternal { // SignalUnknownAddress. std::vector pruned_ports_ RTC_GUARDED_BY(network_thread_); - // |connections_| is a sorted list with the first one always be the - // |selected_connection_| when it's not nullptr. The combination of - // |pinged_connections_| and |unpinged_connections_| has the same - // connections as |connections_|. These 2 sets maintain whether a - // connection should be pinged next or not. - std::vector connections_ RTC_GUARDED_BY(network_thread_); - std::set pinged_connections_ RTC_GUARDED_BY(network_thread_); - std::set unpinged_connections_ RTC_GUARDED_BY(network_thread_); - Connection* selected_connection_ RTC_GUARDED_BY(network_thread_) = nullptr; std::vector remote_candidates_ @@ -503,6 +422,9 @@ class RTC_EXPORT P2PTransportChannel : public IceTransportInternal { RTC_GUARDED_BY(network_thread_); webrtc::IceEventLog ice_event_log_ RTC_GUARDED_BY(network_thread_); + std::unique_ptr ice_controller_ + RTC_GUARDED_BY(network_thread_); + struct CandidateAndResolver final { CandidateAndResolver(const Candidate& candidate, rtc::AsyncResolverInterface* resolver); @@ -521,9 +443,6 @@ class RTC_EXPORT P2PTransportChannel : public IceTransportInternal { IceFieldTrials field_trials_; - // Timestamp for when we got the first selectable connection. - int64_t initial_select_timestamp_ms_ = 0; - RTC_DISALLOW_COPY_AND_ASSIGN(P2PTransportChannel); };