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 <qingsi@webrtc.org>
Reviewed-by: Honghai Zhang <honghaiz@webrtc.org>
Commit-Queue: Jonas Oreland <jonaso@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#29850}
diff --git a/p2p/BUILD.gn b/p2p/BUILD.gn
index 6ed14a8..945c679 100644
--- a/p2p/BUILD.gn
+++ b/p2p/BUILD.gn
@@ -36,6 +36,8 @@
"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 @@
"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 0000000..d348ae9
--- /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<rtc::AdapterType> 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<rtc::AdapterType> 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<IceTransportState()> ice_transport_state_func,
+ std::function<IceRole()> ice_role_func,
+ std::function<bool(const Connection*)> 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<Connection*, int> 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<Connection*>(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<const Connection*> 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<const Connection*> 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<rtc::Network*, const Connection*>
+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<rtc::Network*, const Connection*> 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<const Connection*>
+BasicIceController::GetBestWritableConnectionPerNetwork() const {
+ std::vector<const Connection*> 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<int64_t> 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<int> 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<int64_t> 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<int64_t> 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<rtc::AdapterType> 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<const Connection*> 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<const Connection*> 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 0000000..5335c00
--- /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 <algorithm>
+#include <map>
+#include <set>
+#include <utility>
+#include <vector>
+
+#include "p2p/base/ice_controller_interface.h"
+#include "p2p/base/p2p_transport_channel.h"
+
+namespace cricket {
+
+class BasicIceController : public IceControllerInterface {
+ public:
+ BasicIceController(
+ std::function<IceTransportState()> ice_transport_state_func,
+ std::function<IceRole()> ice_role_func,
+ std::function<bool(const Connection*)> 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<const Connection*> connections() const override {
+ return rtc::ArrayView<const Connection*>(
+ const_cast<const Connection**>(connections_.data()),
+ connections_.size());
+ }
+
+ bool HasPingableConnection() const override;
+
+ std::pair<Connection*, int> 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<const Connection*> 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<rtc::Network*, const Connection*> GetBestConnectionByNetwork() const;
+ std::vector<const Connection*> GetBestWritableConnectionPerNetwork() const;
+
+ bool ReadyToSend(const Connection* connection) const;
+ bool PresumedWritable(const Connection* conn) const;
+
+ int CompareCandidatePairNetworks(
+ const Connection* a,
+ const Connection* b,
+ absl::optional<rtc::AdapterType> 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<int64_t> 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<int64_t> receiving_unchanged_threshold,
+ bool* missed_receiving_unchanged_threshold) const;
+
+ SwitchResult HandleInitialSelectDampening(IceControllerEvent reason,
+ const Connection* new_connection);
+
+ std::function<IceTransportState()> ice_transport_state_func_;
+ std::function<IceRole()> ice_role_func_;
+ std::function<bool(const Connection*)> 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<const Connection*> connections_;
+ std::set<const Connection*> pinged_connections_;
+ std::set<const Connection*> 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 0000000..6c93012
--- /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 <string>
+
+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 0000000..4f8dc72
--- /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 <string>
+#include <utility>
+#include <vector>
+
+#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<const Connection*> connection;
+
+ // Delay in milliseconds when we should resort and try switching again.
+ absl::optional<int> 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<const Connection*> 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<Connection*, int> 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<const Connection*> 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 7f2b37a..b5f57dc 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 @@
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<rtc::AdapterType> 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<rtc::AdapterType> 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 @@
allocator_->SignalCandidateFilterChanged.connect(
this, &P2PTransportChannel::OnCandidateFilterChanged);
ice_event_log_.set_event_log(event_log);
+
+ ice_controller_ = std::make_unique<BasicIceController>(
+ [this] { return GetState(); }, [this] { return GetIceRole(); },
+ [this](const Connection* connection) {
+ return IsPortPruned(connection->port()) ||
+ IsRemoteCandidatePruned(connection->remote_candidate());
+ },
+ &field_trials_);
}
P2PTransportChannel::~P2PTransportChannel() {
+ std::vector<Connection*> 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::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 @@
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<int64_t> 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<void>(
- 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<Connection*>(*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<void>(
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 @@
}
std::vector<Connection*> active_connections;
- for (Connection* connection : connections_) {
+ for (Connection* connection : connections()) {
if (connection->active()) {
active_connections.push_back(connection);
}
@@ -510,7 +369,7 @@
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 @@
}
// 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<int>(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 @@
}
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
}
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 @@
// 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 @@
// 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 @@
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 @@
// 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 @@
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::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 @@
}
// 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 @@
return static_cast<rtc::DiffServCodePoint>(it->second);
}
+rtc::ArrayView<Connection*> P2PTransportChannel::connections() const {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ rtc::ArrayView<const Connection*> res = ice_controller_->connections();
+ return rtc::ArrayView<Connection*>(const_cast<Connection**>(res.data()),
+ res.size());
+}
+
// Monitor connection states.
void P2PTransportChannel::UpdateConnectionStates() {
RTC_DCHECK_RUN_ON(network_thread_);
@@ -1653,14 +1523,14 @@
// 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<void>(
@@ -1677,10 +1547,7 @@
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 @@
}
}
-int P2PTransportChannel::CompareCandidatePairNetworks(
- const Connection* a,
- const Connection* b,
- absl::optional<rtc::AdapterType> 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<int64_t> 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 @@
return !absl::c_linear_search(remote_candidates_, cand);
}
-int P2PTransportChannel::CompareConnections(
- const Connection* a,
- const Connection* b,
- absl::optional<int64_t> 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 @@
// 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 @@
// 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 @@
// 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 @@
MaybeStartPinging();
}
-std::map<rtc::Network*, Connection*>
-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<rtc::Network*, Connection*> 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<Connection*>
-P2PTransportChannel::GetBestWritableConnectionPerNetwork() const {
- std::vector<Connection*> 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<const Connection*> connections_to_prune =
+ ice_controller_->PruneConnections();
+ for (const Connection* conn : connections_to_prune) {
+ const_cast<Connection*>(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 @@
// 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 @@
}
++selected_candidate_pair_changes_;
+
+ ice_controller_->SetSelectedConnection(selected_connection_);
}
// Warning: UpdateState should eventually be called whenever a connection
@@ -2127,7 +1724,7 @@
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 @@
// 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 @@
// 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<void>(
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<Connection*> 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<Connection*> 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<Connection*>(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 @@
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 @@
}
// 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 @@
}
// 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 @@
// 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 @@
// 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 @@
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 @@
// 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 @@
}
}
-// 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 7ce0651..ee0cca2 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 @@
void MarkConnectionPinged(Connection* conn);
// Public for unit tests.
- const std::vector<Connection*>& connections() const { return connections_; }
+ rtc::ArrayView<Connection*> connections() const;
// Public for unit tests.
PortAllocatorSession* allocator_session() const {
@@ -226,63 +227,19 @@
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<rtc::AdapterType> 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<int64_t> 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<int64_t> 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 @@
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<PortAllocatorSession> session);
void AddConnection(Connection* connection);
@@ -360,33 +309,15 @@
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<rtc::Network*, Connection*> GetBestConnectionByNetwork() const;
- std::vector<Connection*> 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 @@
// 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 @@
// SignalUnknownAddress.
std::vector<PortInterface*> 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<Connection*> connections_ RTC_GUARDED_BY(network_thread_);
- std::set<Connection*> pinged_connections_ RTC_GUARDED_BY(network_thread_);
- std::set<Connection*> unpinged_connections_ RTC_GUARDED_BY(network_thread_);
-
Connection* selected_connection_ RTC_GUARDED_BY(network_thread_) = nullptr;
std::vector<RemoteCandidate> remote_candidates_
@@ -503,6 +422,9 @@
RTC_GUARDED_BY(network_thread_);
webrtc::IceEventLog ice_event_log_ RTC_GUARDED_BY(network_thread_);
+ std::unique_ptr<IceControllerInterface> ice_controller_
+ RTC_GUARDED_BY(network_thread_);
+
struct CandidateAndResolver final {
CandidateAndResolver(const Candidate& candidate,
rtc::AsyncResolverInterface* resolver);
@@ -521,9 +443,6 @@
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);
};