blob: f5b3eca3a428e63306ba59801aafce22d566630b [file] [log] [blame]
Jonas Oreland09c452e2019-11-20 08:01:021/*
2 * Copyright 2019 The WebRTC Project Authors. All rights reserved.
3 *
4 * Use of this source code is governed by a BSD-style license
5 * that can be found in the LICENSE file in the root of the source
6 * tree. An additional intellectual property rights grant can be found
7 * in the file PATENTS. All contributing project authors may
8 * be found in the AUTHORS file in the root of the source tree.
9 */
10
11#include "p2p/base/basic_ice_controller.h"
12
13namespace {
14
15// The minimum improvement in RTT that justifies a switch.
16const int kMinImprovement = 10;
17
18bool IsRelayRelay(const cricket::Connection* conn) {
Tommi698b4e72024-01-24 07:36:4519 return conn->local_candidate().is_relay() &&
20 conn->remote_candidate().is_relay();
Jonas Oreland09c452e2019-11-20 08:01:0221}
22
23bool IsUdp(const cricket::Connection* conn) {
24 return conn->local_candidate().relay_protocol() == cricket::UDP_PROTOCOL_NAME;
25}
26
27// TODO(qingsi) Use an enum to replace the following constants for all
28// comparision results.
29static constexpr int a_is_better = 1;
30static constexpr int b_is_better = -1;
31static constexpr int a_and_b_equal = 0;
32
33bool LocalCandidateUsesPreferredNetwork(
34 const cricket::Connection* conn,
35 absl::optional<rtc::AdapterType> network_preference) {
Jonas Oreland98e745b2019-11-27 10:02:4536 rtc::AdapterType network_type = conn->network()->type();
Jonas Oreland09c452e2019-11-20 08:01:0237 return network_preference.has_value() && (network_type == network_preference);
38}
39
40int CompareCandidatePairsByNetworkPreference(
41 const cricket::Connection* a,
42 const cricket::Connection* b,
43 absl::optional<rtc::AdapterType> network_preference) {
44 bool a_uses_preferred_network =
45 LocalCandidateUsesPreferredNetwork(a, network_preference);
46 bool b_uses_preferred_network =
47 LocalCandidateUsesPreferredNetwork(b, network_preference);
48 if (a_uses_preferred_network && !b_uses_preferred_network) {
49 return a_is_better;
50 } else if (!a_uses_preferred_network && b_uses_preferred_network) {
51 return b_is_better;
52 }
53 return a_and_b_equal;
54}
55
56} // namespace
57
58namespace cricket {
59
Jonas Oreland2f74d5f2019-11-22 06:53:2260BasicIceController::BasicIceController(const IceControllerFactoryArgs& args)
61 : ice_transport_state_func_(args.ice_transport_state_func),
62 ice_role_func_(args.ice_role_func),
63 is_connection_pruned_func_(args.is_connection_pruned_func),
64 field_trials_(args.ice_field_trials) {}
Jonas Oreland09c452e2019-11-20 08:01:0265
66BasicIceController::~BasicIceController() {}
67
68void BasicIceController::SetIceConfig(const IceConfig& config) {
69 config_ = config;
70}
71
72void BasicIceController::SetSelectedConnection(
73 const Connection* selected_connection) {
74 selected_connection_ = selected_connection;
75}
76
77void BasicIceController::AddConnection(const Connection* connection) {
78 connections_.push_back(connection);
79 unpinged_connections_.insert(connection);
80}
81
82void BasicIceController::OnConnectionDestroyed(const Connection* connection) {
83 pinged_connections_.erase(connection);
84 unpinged_connections_.erase(connection);
85 connections_.erase(absl::c_find(connections_, connection));
Tommiaf2930a2022-01-31 13:11:3986 if (selected_connection_ == connection)
87 selected_connection_ = nullptr;
Jonas Oreland09c452e2019-11-20 08:01:0288}
89
90bool BasicIceController::HasPingableConnection() const {
91 int64_t now = rtc::TimeMillis();
92 return absl::c_any_of(connections_, [this, now](const Connection* c) {
93 return IsPingable(c, now);
94 });
95}
96
Jonas Oreland2f3c0192020-03-26 11:59:4497IceControllerInterface::PingResult BasicIceController::SelectConnectionToPing(
Jonas Oreland09c452e2019-11-20 08:01:0298 int64_t last_ping_sent_ms) {
99 // When the selected connection is not receiving or not writable, or any
100 // active connection has not been pinged enough times, use the weak ping
101 // interval.
102 bool need_more_pings_at_weak_interval =
103 absl::c_any_of(connections_, [](const Connection* conn) {
104 return conn->active() &&
105 conn->num_pings_sent() < MIN_PINGS_AT_WEAK_PING_INTERVAL;
106 });
107 int ping_interval = (weak() || need_more_pings_at_weak_interval)
108 ? weak_ping_interval()
109 : strong_ping_interval();
110
111 const Connection* conn = nullptr;
112 if (rtc::TimeMillis() >= last_ping_sent_ms + ping_interval) {
113 conn = FindNextPingableConnection();
114 }
Jonas Oreland43336002020-03-26 19:59:03115 PingResult res(conn, std::min(ping_interval, check_receiving_interval()));
116 return res;
Jonas Oreland09c452e2019-11-20 08:01:02117}
118
119void BasicIceController::MarkConnectionPinged(const Connection* conn) {
120 if (conn && pinged_connections_.insert(conn).second) {
121 unpinged_connections_.erase(conn);
122 }
123}
124
125// Returns the next pingable connection to ping.
126const Connection* BasicIceController::FindNextPingableConnection() {
127 int64_t now = rtc::TimeMillis();
128
129 // Rule 1: Selected connection takes priority over non-selected ones.
130 if (selected_connection_ && selected_connection_->connected() &&
131 selected_connection_->writable() &&
132 WritableConnectionPastPingInterval(selected_connection_, now)) {
133 return selected_connection_;
134 }
135
136 // Rule 2: If the channel is weak, we need to find a new writable and
137 // receiving connection, probably on a different network. If there are lots of
138 // connections, it may take several seconds between two pings for every
139 // non-selected connection. This will cause the receiving state of those
140 // connections to be false, and thus they won't be selected. This is
141 // problematic for network fail-over. We want to make sure at least one
142 // connection per network is pinged frequently enough in order for it to be
143 // selectable. So we prioritize one connection per network.
144 // Rule 2.1: Among such connections, pick the one with the earliest
145 // last-ping-sent time.
146 if (weak()) {
147 std::vector<const Connection*> pingable_selectable_connections;
148 absl::c_copy_if(GetBestWritableConnectionPerNetwork(),
149 std::back_inserter(pingable_selectable_connections),
150 [this, now](const Connection* conn) {
151 return WritableConnectionPastPingInterval(conn, now);
152 });
153 auto iter = absl::c_min_element(
154 pingable_selectable_connections,
155 [](const Connection* conn1, const Connection* conn2) {
156 return conn1->last_ping_sent() < conn2->last_ping_sent();
157 });
158 if (iter != pingable_selectable_connections.end()) {
159 return *iter;
160 }
161 }
162
163 // Rule 3: Triggered checks have priority over non-triggered connections.
164 // Rule 3.1: Among triggered checks, oldest takes precedence.
165 const Connection* oldest_triggered_check =
166 FindOldestConnectionNeedingTriggeredCheck(now);
167 if (oldest_triggered_check) {
168 return oldest_triggered_check;
169 }
170
171 // Rule 4: Unpinged connections have priority over pinged ones.
172 RTC_CHECK(connections_.size() ==
173 pinged_connections_.size() + unpinged_connections_.size());
174 // If there are unpinged and pingable connections, only ping those.
175 // Otherwise, treat everything as unpinged.
176 // TODO(honghaiz): Instead of adding two separate vectors, we can add a state
177 // "pinged" to filter out unpinged connections.
178 if (absl::c_none_of(unpinged_connections_,
179 [this, now](const Connection* conn) {
180 return this->IsPingable(conn, now);
181 })) {
182 unpinged_connections_.insert(pinged_connections_.begin(),
183 pinged_connections_.end());
184 pinged_connections_.clear();
185 }
186
187 // Among un-pinged pingable connections, "more pingable" takes precedence.
188 std::vector<const Connection*> pingable_connections;
189 absl::c_copy_if(
190 unpinged_connections_, std::back_inserter(pingable_connections),
191 [this, now](const Connection* conn) { return IsPingable(conn, now); });
192 auto iter = absl::c_max_element(
193 pingable_connections,
194 [this](const Connection* conn1, const Connection* conn2) {
195 // Some implementations of max_element
196 // compare an element with itself.
197 if (conn1 == conn2) {
198 return false;
199 }
200 return MorePingable(conn1, conn2) == conn2;
201 });
202 if (iter != pingable_connections.end()) {
203 return *iter;
204 }
205 return nullptr;
206}
207
208// Find "triggered checks". We ping first those connections that have
209// received a ping but have not sent a ping since receiving it
210// (last_ping_received > last_ping_sent). But we shouldn't do
211// triggered checks if the connection is already writable.
212const Connection* BasicIceController::FindOldestConnectionNeedingTriggeredCheck(
213 int64_t now) {
214 const Connection* oldest_needing_triggered_check = nullptr;
215 for (auto* conn : connections_) {
216 if (!IsPingable(conn, now)) {
217 continue;
218 }
219 bool needs_triggered_check =
220 (!conn->writable() &&
221 conn->last_ping_received() > conn->last_ping_sent());
222 if (needs_triggered_check &&
223 (!oldest_needing_triggered_check ||
224 (conn->last_ping_received() <
225 oldest_needing_triggered_check->last_ping_received()))) {
226 oldest_needing_triggered_check = conn;
227 }
228 }
229
230 if (oldest_needing_triggered_check) {
231 RTC_LOG(LS_INFO) << "Selecting connection for triggered check: "
232 << oldest_needing_triggered_check->ToString();
233 }
234 return oldest_needing_triggered_check;
235}
236
237bool BasicIceController::WritableConnectionPastPingInterval(
238 const Connection* conn,
239 int64_t now) const {
240 int interval = CalculateActiveWritablePingInterval(conn, now);
241 return conn->last_ping_sent() + interval <= now;
242}
243
244int BasicIceController::CalculateActiveWritablePingInterval(
245 const Connection* conn,
246 int64_t now) const {
247 // Ping each connection at a higher rate at least
248 // MIN_PINGS_AT_WEAK_PING_INTERVAL times.
249 if (conn->num_pings_sent() < MIN_PINGS_AT_WEAK_PING_INTERVAL) {
250 return weak_ping_interval();
251 }
252
253 int stable_interval =
254 config_.stable_writable_connection_ping_interval_or_default();
255 int weak_or_stablizing_interval = std::min(
256 stable_interval, WEAK_OR_STABILIZING_WRITABLE_CONNECTION_PING_INTERVAL);
257 // If the channel is weak or the connection is not stable yet, use the
258 // weak_or_stablizing_interval.
259 return (!weak() && conn->stable(now)) ? stable_interval
260 : weak_or_stablizing_interval;
261}
262
263// Is the connection in a state for us to even consider pinging the other side?
264// We consider a connection pingable even if it's not connected because that's
265// how a TCP connection is kicked into reconnecting on the active side.
266bool BasicIceController::IsPingable(const Connection* conn, int64_t now) const {
267 const Candidate& remote = conn->remote_candidate();
268 // We should never get this far with an empty remote ufrag.
269 RTC_DCHECK(!remote.username().empty());
270 if (remote.username().empty() || remote.password().empty()) {
271 // If we don't have an ICE ufrag and pwd, there's no way we can ping.
272 return false;
273 }
274
275 // A failed connection will not be pinged.
276 if (conn->state() == IceCandidatePairState::FAILED) {
277 return false;
278 }
279
280 // An never connected connection cannot be written to at all, so pinging is
281 // out of the question. However, if it has become WRITABLE, it is in the
282 // reconnecting state so ping is needed.
283 if (!conn->connected() && !conn->writable()) {
284 return false;
285 }
286
287 // If we sent a number of pings wo/ reply, skip sending more
288 // until we get one.
289 if (conn->TooManyOutstandingPings(field_trials_->max_outstanding_pings)) {
290 return false;
291 }
292
293 // If the channel is weakly connected, ping all connections.
294 if (weak()) {
295 return true;
296 }
297
298 // Always ping active connections regardless whether the channel is completed
299 // or not, but backup connections are pinged at a slower rate.
300 if (IsBackupConnection(conn)) {
301 return conn->rtt_samples() == 0 ||
302 (now >= conn->last_ping_response_received() +
303 config_.backup_connection_ping_interval_or_default());
304 }
305 // Don't ping inactive non-backup connections.
306 if (!conn->active()) {
307 return false;
308 }
309
310 // Do ping unwritable, active connections.
311 if (!conn->writable()) {
312 return true;
313 }
314
315 // Ping writable, active connections if it's been long enough since the last
316 // ping.
317 return WritableConnectionPastPingInterval(conn, now);
318}
319
320// A connection is considered a backup connection if the channel state
321// is completed, the connection is not the selected connection and it is active.
322bool BasicIceController::IsBackupConnection(const Connection* conn) const {
323 return ice_transport_state_func_() == IceTransportState::STATE_COMPLETED &&
324 conn != selected_connection_ && conn->active();
325}
326
327const Connection* BasicIceController::MorePingable(const Connection* conn1,
328 const Connection* conn2) {
329 RTC_DCHECK(conn1 != conn2);
330 if (config_.prioritize_most_likely_candidate_pairs) {
331 const Connection* most_likely_to_work_conn = MostLikelyToWork(conn1, conn2);
332 if (most_likely_to_work_conn) {
333 return most_likely_to_work_conn;
334 }
335 }
336
337 const Connection* least_recently_pinged_conn =
338 LeastRecentlyPinged(conn1, conn2);
339 if (least_recently_pinged_conn) {
340 return least_recently_pinged_conn;
341 }
342
343 // During the initial state when nothing has been pinged yet, return the first
Artem Titov2dbb4c92021-07-26 13:12:41344 // one in the ordered `connections_`.
Jonas Oreland09c452e2019-11-20 08:01:02345 auto connections = connections_;
346 return *(std::find_if(connections.begin(), connections.end(),
347 [conn1, conn2](const Connection* conn) {
348 return conn == conn1 || conn == conn2;
349 }));
350}
351
352const Connection* BasicIceController::MostLikelyToWork(
353 const Connection* conn1,
354 const Connection* conn2) {
355 bool rr1 = IsRelayRelay(conn1);
356 bool rr2 = IsRelayRelay(conn2);
357 if (rr1 && !rr2) {
358 return conn1;
359 } else if (rr2 && !rr1) {
360 return conn2;
361 } else if (rr1 && rr2) {
362 bool udp1 = IsUdp(conn1);
363 bool udp2 = IsUdp(conn2);
364 if (udp1 && !udp2) {
365 return conn1;
366 } else if (udp2 && udp1) {
367 return conn2;
368 }
369 }
370 return nullptr;
371}
372
373const Connection* BasicIceController::LeastRecentlyPinged(
374 const Connection* conn1,
375 const Connection* conn2) {
376 if (conn1->last_ping_sent() < conn2->last_ping_sent()) {
377 return conn1;
378 }
379 if (conn1->last_ping_sent() > conn2->last_ping_sent()) {
380 return conn2;
381 }
382 return nullptr;
383}
384
Jonas Oreland98e745b2019-11-27 10:02:45385std::map<const rtc::Network*, const Connection*>
Jonas Oreland09c452e2019-11-20 08:01:02386BasicIceController::GetBestConnectionByNetwork() const {
Artem Titov2dbb4c92021-07-26 13:12:41387 // `connections_` has been sorted, so the first one in the list on a given
Jonas Oreland09c452e2019-11-20 08:01:02388 // network is the best connection on the network, except that the selected
389 // connection is always the best connection on the network.
Jonas Oreland98e745b2019-11-27 10:02:45390 std::map<const rtc::Network*, const Connection*> best_connection_by_network;
Jonas Oreland09c452e2019-11-20 08:01:02391 if (selected_connection_) {
Jonas Oreland98e745b2019-11-27 10:02:45392 best_connection_by_network[selected_connection_->network()] =
Jonas Oreland09c452e2019-11-20 08:01:02393 selected_connection_;
394 }
Artem Titov2dbb4c92021-07-26 13:12:41395 // TODO(honghaiz): Need to update this if `connections_` are not sorted.
Jonas Oreland09c452e2019-11-20 08:01:02396 for (const Connection* conn : connections_) {
Jonas Oreland98e745b2019-11-27 10:02:45397 const rtc::Network* network = conn->network();
Jonas Oreland09c452e2019-11-20 08:01:02398 // This only inserts when the network does not exist in the map.
399 best_connection_by_network.insert(std::make_pair(network, conn));
400 }
401 return best_connection_by_network;
402}
403
404std::vector<const Connection*>
405BasicIceController::GetBestWritableConnectionPerNetwork() const {
406 std::vector<const Connection*> connections;
407 for (auto kv : GetBestConnectionByNetwork()) {
408 const Connection* conn = kv.second;
409 if (conn->writable() && conn->connected()) {
410 connections.push_back(conn);
411 }
412 }
413 return connections;
414}
415
416IceControllerInterface::SwitchResult
417BasicIceController::HandleInitialSelectDampening(
Sameer Vijaykar781c12e2022-06-02 14:01:12418 IceSwitchReason reason,
Jonas Oreland09c452e2019-11-20 08:01:02419 const Connection* new_connection) {
420 if (!field_trials_->initial_select_dampening.has_value() &&
421 !field_trials_->initial_select_dampening_ping_received.has_value()) {
422 // experiment not enabled => select connection.
423 return {new_connection, absl::nullopt};
424 }
425
426 int64_t now = rtc::TimeMillis();
427 int64_t max_delay = 0;
428 if (new_connection->last_ping_received() > 0 &&
429 field_trials_->initial_select_dampening_ping_received.has_value()) {
430 max_delay = *field_trials_->initial_select_dampening_ping_received;
431 } else if (field_trials_->initial_select_dampening.has_value()) {
432 max_delay = *field_trials_->initial_select_dampening;
433 }
434
435 int64_t start_wait =
436 initial_select_timestamp_ms_ == 0 ? now : initial_select_timestamp_ms_;
437 int64_t max_wait_until = start_wait + max_delay;
438
439 if (now >= max_wait_until) {
440 RTC_LOG(LS_INFO) << "reset initial_select_timestamp_ = "
441 << initial_select_timestamp_ms_
442 << " selection delayed by: " << (now - start_wait) << "ms";
443 initial_select_timestamp_ms_ = 0;
444 return {new_connection, absl::nullopt};
445 }
446
447 // We are not yet ready to select first connection...
448 if (initial_select_timestamp_ms_ == 0) {
449 // Set timestamp on first time...
450 // but run the delayed invokation everytime to
451 // avoid possibility that we miss it.
452 initial_select_timestamp_ms_ = now;
453 RTC_LOG(LS_INFO) << "set initial_select_timestamp_ms_ = "
454 << initial_select_timestamp_ms_;
455 }
456
457 int min_delay = max_delay;
458 if (field_trials_->initial_select_dampening.has_value()) {
459 min_delay = std::min(min_delay, *field_trials_->initial_select_dampening);
460 }
461 if (field_trials_->initial_select_dampening_ping_received.has_value()) {
462 min_delay = std::min(
463 min_delay, *field_trials_->initial_select_dampening_ping_received);
464 }
465
466 RTC_LOG(LS_INFO) << "delay initial selection up to " << min_delay << "ms";
Sameer Vijaykar781c12e2022-06-02 14:01:12467 return {.connection = absl::nullopt,
Sameer Vijaykar2a1accd2022-06-03 09:39:39468 .recheck_event = IceRecheckEvent(
Sameer Vijaykar781c12e2022-06-02 14:01:12469 IceSwitchReason::ICE_CONTROLLER_RECHECK, min_delay)};
Jonas Oreland09c452e2019-11-20 08:01:02470}
471
472IceControllerInterface::SwitchResult BasicIceController::ShouldSwitchConnection(
Sameer Vijaykar781c12e2022-06-02 14:01:12473 IceSwitchReason reason,
Jonas Oreland09c452e2019-11-20 08:01:02474 const Connection* new_connection) {
475 if (!ReadyToSend(new_connection) || selected_connection_ == new_connection) {
476 return {absl::nullopt, absl::nullopt};
477 }
478
479 if (selected_connection_ == nullptr) {
480 return HandleInitialSelectDampening(reason, new_connection);
481 }
482
483 // Do not switch to a connection that is not receiving if it is not on a
484 // preferred network or it has higher cost because it may be just spuriously
485 // better.
486 int compare_a_b_by_networks = CompareCandidatePairNetworks(
487 new_connection, selected_connection_, config_.network_preference);
488 if (compare_a_b_by_networks == b_is_better && !new_connection->receiving()) {
489 return {absl::nullopt, absl::nullopt};
490 }
491
492 bool missed_receiving_unchanged_threshold = false;
493 absl::optional<int64_t> receiving_unchanged_threshold(
494 rtc::TimeMillis() - config_.receiving_switching_delay_or_default());
495 int cmp = CompareConnections(selected_connection_, new_connection,
496 receiving_unchanged_threshold,
497 &missed_receiving_unchanged_threshold);
498
Sameer Vijaykar2a1accd2022-06-03 09:39:39499 absl::optional<IceRecheckEvent> recheck_event;
Jonas Oreland09c452e2019-11-20 08:01:02500 if (missed_receiving_unchanged_threshold &&
501 config_.receiving_switching_delay_or_default()) {
502 // If we do not switch to the connection because it missed the receiving
503 // threshold, the new connection is in a better receiving state than the
504 // currently selected connection. So we need to re-check whether it needs
505 // to be switched at a later time.
Sameer Vijaykar781c12e2022-06-02 14:01:12506 recheck_event.emplace(reason,
507 config_.receiving_switching_delay_or_default());
Jonas Oreland09c452e2019-11-20 08:01:02508 }
509
510 if (cmp < 0) {
511 return {new_connection, absl::nullopt};
512 } else if (cmp > 0) {
Jonas Orelandb5aa0a82019-12-03 08:59:11513 return {absl::nullopt, recheck_event};
Jonas Oreland09c452e2019-11-20 08:01:02514 }
515
516 // If everything else is the same, switch only if rtt has improved by
517 // a margin.
518 if (new_connection->rtt() <= selected_connection_->rtt() - kMinImprovement) {
519 return {new_connection, absl::nullopt};
520 }
521
Jonas Orelandb5aa0a82019-12-03 08:59:11522 return {absl::nullopt, recheck_event};
Jonas Oreland09c452e2019-11-20 08:01:02523}
524
525IceControllerInterface::SwitchResult
Sameer Vijaykar781c12e2022-06-02 14:01:12526BasicIceController::SortAndSwitchConnection(IceSwitchReason reason) {
Jonas Oreland09c452e2019-11-20 08:01:02527 // Find the best alternative connection by sorting. It is important to note
528 // that amongst equal preference, writable connections, this will choose the
529 // one whose estimated latency is lowest. So it is the only one that we
530 // need to consider switching to.
531 // TODO(honghaiz): Don't sort; Just use std::max_element in the right places.
532 absl::c_stable_sort(
533 connections_, [this](const Connection* a, const Connection* b) {
534 int cmp = CompareConnections(a, b, absl::nullopt, nullptr);
535 if (cmp != 0) {
536 return cmp > 0;
537 }
538 // Otherwise, sort based on latency estimate.
539 return a->rtt() < b->rtt();
540 });
541
542 RTC_LOG(LS_VERBOSE) << "Sorting " << connections_.size()
Philipp Hancke4f51b342023-07-25 14:56:44543 << " available connections due to: "
544 << IceSwitchReasonToString(reason);
Jonas Oreland09c452e2019-11-20 08:01:02545 for (size_t i = 0; i < connections_.size(); ++i) {
546 RTC_LOG(LS_VERBOSE) << connections_[i]->ToString();
547 }
548
549 const Connection* top_connection =
550 (!connections_.empty()) ? connections_[0] : nullptr;
551
552 return ShouldSwitchConnection(reason, top_connection);
553}
554
555bool BasicIceController::ReadyToSend(const Connection* connection) const {
556 // Note that we allow sending on an unreliable connection, because it's
557 // possible that it became unreliable simply due to bad chance.
558 // So this shouldn't prevent attempting to send media.
559 return connection != nullptr &&
560 (connection->writable() ||
561 connection->write_state() == Connection::STATE_WRITE_UNRELIABLE ||
562 PresumedWritable(connection));
563}
564
565bool BasicIceController::PresumedWritable(const Connection* conn) const {
566 return (conn->write_state() == Connection::STATE_WRITE_INIT &&
567 config_.presume_writable_when_fully_relayed &&
Tommi0a7fc842024-01-19 12:11:37568 conn->local_candidate().is_relay() &&
569 (conn->remote_candidate().is_relay() ||
570 conn->remote_candidate().is_prflx()));
Jonas Oreland09c452e2019-11-20 08:01:02571}
572
573// Compare two connections based on their writing, receiving, and connected
574// states.
575int BasicIceController::CompareConnectionStates(
576 const Connection* a,
577 const Connection* b,
578 absl::optional<int64_t> receiving_unchanged_threshold,
579 bool* missed_receiving_unchanged_threshold) const {
580 // First, prefer a connection that's writable or presumed writable over
581 // one that's not writable.
582 bool a_writable = a->writable() || PresumedWritable(a);
583 bool b_writable = b->writable() || PresumedWritable(b);
584 if (a_writable && !b_writable) {
585 return a_is_better;
586 }
587 if (!a_writable && b_writable) {
588 return b_is_better;
589 }
590
591 // Sort based on write-state. Better states have lower values.
592 if (a->write_state() < b->write_state()) {
593 return a_is_better;
594 }
595 if (b->write_state() < a->write_state()) {
596 return b_is_better;
597 }
598
599 // We prefer a receiving connection to a non-receiving, higher-priority
600 // connection when sorting connections and choosing which connection to
601 // switch to.
602 if (a->receiving() && !b->receiving()) {
603 return a_is_better;
604 }
605 if (!a->receiving() && b->receiving()) {
606 if (!receiving_unchanged_threshold ||
607 (a->receiving_unchanged_since() <= *receiving_unchanged_threshold &&
608 b->receiving_unchanged_since() <= *receiving_unchanged_threshold)) {
609 return b_is_better;
610 }
611 *missed_receiving_unchanged_threshold = true;
612 }
613
614 // WARNING: Some complexity here about TCP reconnecting.
615 // When a TCP connection fails because of a TCP socket disconnecting, the
616 // active side of the connection will attempt to reconnect for 5 seconds while
617 // pretending to be writable (the connection is not set to the unwritable
618 // state). On the passive side, the connection also remains writable even
619 // though it is disconnected, and a new connection is created when the active
620 // side connects. At that point, there are two TCP connections on the passive
621 // side: 1. the old, disconnected one that is pretending to be writable, and
622 // 2. the new, connected one that is maybe not yet writable. For purposes of
623 // pruning, pinging, and selecting the selected connection, we want to treat
624 // the new connection as "better" than the old one. We could add a method
625 // called something like Connection::ImReallyBadEvenThoughImWritable, but that
626 // is equivalent to the existing Connection::connected(), which we already
627 // have. So, in code throughout this file, we'll check whether the connection
628 // is connected() or not, and if it is not, treat it as "worse" than a
629 // connected one, even though it's writable. In the code below, we're doing
630 // so to make sure we treat a new writable connection as better than an old
631 // disconnected connection.
632
633 // In the case where we reconnect TCP connections, the original best
634 // connection is disconnected without changing to WRITE_TIMEOUT. In this case,
635 // the new connection, when it becomes writable, should have higher priority.
636 if (a->write_state() == Connection::STATE_WRITABLE &&
637 b->write_state() == Connection::STATE_WRITABLE) {
638 if (a->connected() && !b->connected()) {
639 return a_is_better;
640 }
641 if (!a->connected() && b->connected()) {
642 return b_is_better;
643 }
644 }
645
646 return 0;
647}
648
649// Compares two connections based only on the candidate and network information.
Artem Titov2dbb4c92021-07-26 13:12:41650// Returns positive if `a` is better than `b`.
Jonas Oreland09c452e2019-11-20 08:01:02651int BasicIceController::CompareConnectionCandidates(const Connection* a,
652 const Connection* b) const {
653 int compare_a_b_by_networks =
654 CompareCandidatePairNetworks(a, b, config_.network_preference);
655 if (compare_a_b_by_networks != a_and_b_equal) {
656 return compare_a_b_by_networks;
657 }
658
659 // Compare connection priority. Lower values get sorted last.
660 if (a->priority() > b->priority()) {
661 return a_is_better;
662 }
663 if (a->priority() < b->priority()) {
664 return b_is_better;
665 }
666
667 // If we're still tied at this point, prefer a younger generation.
668 // (Younger generation means a larger generation number).
Jonas Oreland98e745b2019-11-27 10:02:45669 int cmp = (a->remote_candidate().generation() + a->generation()) -
670 (b->remote_candidate().generation() + b->generation());
Jonas Oreland09c452e2019-11-20 08:01:02671 if (cmp != 0) {
672 return cmp;
673 }
674
675 // A periodic regather (triggered by the regather_all_networks_interval_range)
676 // will produce candidates that appear the same but would use a new port. We
677 // want to use the new candidates and purge the old candidates as they come
678 // in, so use the fact that the old ports get pruned immediately to rank the
679 // candidates with an active port/remote candidate higher.
680 bool a_pruned = is_connection_pruned_func_(a);
681 bool b_pruned = is_connection_pruned_func_(b);
682 if (!a_pruned && b_pruned) {
683 return a_is_better;
684 }
685 if (a_pruned && !b_pruned) {
686 return b_is_better;
687 }
688
689 // Otherwise, must be equal
690 return 0;
691}
692
693int BasicIceController::CompareConnections(
694 const Connection* a,
695 const Connection* b,
696 absl::optional<int64_t> receiving_unchanged_threshold,
697 bool* missed_receiving_unchanged_threshold) const {
698 RTC_CHECK(a != nullptr);
699 RTC_CHECK(b != nullptr);
700
701 // We prefer to switch to a writable and receiving connection over a
702 // non-writable or non-receiving connection, even if the latter has
703 // been nominated by the controlling side.
704 int state_cmp = CompareConnectionStates(a, b, receiving_unchanged_threshold,
705 missed_receiving_unchanged_threshold);
706 if (state_cmp != 0) {
707 return state_cmp;
708 }
709
710 if (ice_role_func_() == ICEROLE_CONTROLLED) {
711 // Compare the connections based on the nomination states and the last data
712 // received time if this is on the controlled side.
713 if (a->remote_nomination() > b->remote_nomination()) {
714 return a_is_better;
715 }
716 if (a->remote_nomination() < b->remote_nomination()) {
717 return b_is_better;
718 }
719
720 if (a->last_data_received() > b->last_data_received()) {
721 return a_is_better;
722 }
723 if (a->last_data_received() < b->last_data_received()) {
724 return b_is_better;
725 }
726 }
727
728 // Compare the network cost and priority.
729 return CompareConnectionCandidates(a, b);
730}
731
732int BasicIceController::CompareCandidatePairNetworks(
733 const Connection* a,
734 const Connection* b,
735 absl::optional<rtc::AdapterType> network_preference) const {
736 int compare_a_b_by_network_preference =
737 CompareCandidatePairsByNetworkPreference(a, b,
738 config_.network_preference);
739 // The network preference has a higher precedence than the network cost.
740 if (compare_a_b_by_network_preference != a_and_b_equal) {
741 return compare_a_b_by_network_preference;
742 }
743
Jonas Orelandc8fa1ee2021-08-25 06:58:04744 bool a_vpn = a->network()->IsVpn();
745 bool b_vpn = b->network()->IsVpn();
746 switch (config_.vpn_preference) {
747 case webrtc::VpnPreference::kDefault:
748 break;
749 case webrtc::VpnPreference::kOnlyUseVpn:
750 case webrtc::VpnPreference::kPreferVpn:
751 if (a_vpn && !b_vpn) {
752 return a_is_better;
753 } else if (!a_vpn && b_vpn) {
754 return b_is_better;
755 }
756 break;
757 case webrtc::VpnPreference::kNeverUseVpn:
758 case webrtc::VpnPreference::kAvoidVpn:
759 if (a_vpn && !b_vpn) {
760 return b_is_better;
761 } else if (!a_vpn && b_vpn) {
762 return a_is_better;
763 }
764 break;
765 default:
766 break;
767 }
768
Jonas Oreland09c452e2019-11-20 08:01:02769 uint32_t a_cost = a->ComputeNetworkCost();
770 uint32_t b_cost = b->ComputeNetworkCost();
771 // Prefer lower network cost.
772 if (a_cost < b_cost) {
773 return a_is_better;
774 }
775 if (a_cost > b_cost) {
776 return b_is_better;
777 }
778 return a_and_b_equal;
779}
780
781std::vector<const Connection*> BasicIceController::PruneConnections() {
782 // We can prune any connection for which there is a connected, writable
783 // connection on the same network with better or equal priority. We leave
784 // those with better priority just in case they become writable later (at
785 // which point, we would prune out the current selected connection). We leave
786 // connections on other networks because they may not be using the same
787 // resources and they may represent very distinct paths over which we can
Artem Titov2dbb4c92021-07-26 13:12:41788 // switch. If `best_conn_on_network` is not connected, we may be reconnecting
Jonas Oreland09c452e2019-11-20 08:01:02789 // a TCP connection and should not prune connections in this network.
790 // See the big comment in CompareConnectionStates.
791 //
792 // An exception is made for connections on an "any address" network, meaning
793 // not bound to any specific network interface. We don't want to keep one of
794 // these alive as a backup, since it could be using the same network
795 // interface as the higher-priority, selected candidate pair.
796 std::vector<const Connection*> connections_to_prune;
797 auto best_connection_by_network = GetBestConnectionByNetwork();
798 for (const Connection* conn : connections_) {
799 const Connection* best_conn = selected_connection_;
Diep Bui1e589eb2022-08-02 07:37:30800 if (!rtc::IPIsAny(conn->network()->GetBestIP())) {
Jonas Oreland09c452e2019-11-20 08:01:02801 // If the connection is bound to a specific network interface (not an
802 // "any address" network), compare it against the best connection for
803 // that network interface rather than the best connection overall. This
804 // ensures that at least one connection per network will be left
805 // unpruned.
Jonas Oreland98e745b2019-11-27 10:02:45806 best_conn = best_connection_by_network[conn->network()];
Jonas Oreland09c452e2019-11-20 08:01:02807 }
808 // Do not prune connections if the connection being compared against is
809 // weak. Otherwise, it may delete connections prematurely.
810 if (best_conn && conn != best_conn && !best_conn->weak() &&
811 CompareConnectionCandidates(best_conn, conn) >= 0) {
812 connections_to_prune.push_back(conn);
813 }
814 }
815 return connections_to_prune;
816}
817
818bool BasicIceController::GetUseCandidateAttr(const Connection* conn,
819 NominationMode mode,
820 IceMode remote_ice_mode) const {
821 switch (mode) {
822 case NominationMode::REGULAR:
823 // TODO(honghaiz): Implement regular nomination.
824 return false;
825 case NominationMode::AGGRESSIVE:
826 if (remote_ice_mode == ICEMODE_LITE) {
827 return GetUseCandidateAttr(conn, NominationMode::REGULAR,
828 remote_ice_mode);
829 }
830 return true;
831 case NominationMode::SEMI_AGGRESSIVE: {
832 // Nominate if
833 // a) Remote is in FULL ICE AND
Artem Titov2dbb4c92021-07-26 13:12:41834 // a.1) `conn` is the selected connection OR
Jonas Oreland09c452e2019-11-20 08:01:02835 // a.2) there is no selected connection OR
836 // a.3) the selected connection is unwritable OR
Artem Titov2dbb4c92021-07-26 13:12:41837 // a.4) `conn` has higher priority than selected_connection.
Jonas Oreland09c452e2019-11-20 08:01:02838 // b) Remote is in LITE ICE AND
Artem Titov2dbb4c92021-07-26 13:12:41839 // b.1) `conn` is the selected_connection AND
840 // b.2) `conn` is writable.
Jonas Oreland09c452e2019-11-20 08:01:02841 bool selected = conn == selected_connection_;
842 if (remote_ice_mode == ICEMODE_LITE) {
843 return selected && conn->writable();
844 }
845 bool better_than_selected =
846 !selected_connection_ || !selected_connection_->writable() ||
847 CompareConnectionCandidates(selected_connection_, conn) < 0;
848 return selected || better_than_selected;
849 }
850 default:
Artem Titovd3251962021-11-15 15:57:07851 RTC_DCHECK_NOTREACHED();
Jonas Oreland09c452e2019-11-20 08:01:02852 return false;
853 }
854}
855
856} // namespace cricket