Jonas Oreland | 09c452e | 2019-11-20 08:01:02 | [diff] [blame] | 1 | /* |
| 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 | |
| 13 | namespace { |
| 14 | |
| 15 | // The minimum improvement in RTT that justifies a switch. |
| 16 | const int kMinImprovement = 10; |
| 17 | |
| 18 | bool IsRelayRelay(const cricket::Connection* conn) { |
Tommi | 698b4e7 | 2024-01-24 07:36:45 | [diff] [blame] | 19 | return conn->local_candidate().is_relay() && |
| 20 | conn->remote_candidate().is_relay(); |
Jonas Oreland | 09c452e | 2019-11-20 08:01:02 | [diff] [blame] | 21 | } |
| 22 | |
| 23 | bool 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. |
| 29 | static constexpr int a_is_better = 1; |
| 30 | static constexpr int b_is_better = -1; |
| 31 | static constexpr int a_and_b_equal = 0; |
| 32 | |
| 33 | bool LocalCandidateUsesPreferredNetwork( |
| 34 | const cricket::Connection* conn, |
| 35 | absl::optional<rtc::AdapterType> network_preference) { |
Jonas Oreland | 98e745b | 2019-11-27 10:02:45 | [diff] [blame] | 36 | rtc::AdapterType network_type = conn->network()->type(); |
Jonas Oreland | 09c452e | 2019-11-20 08:01:02 | [diff] [blame] | 37 | return network_preference.has_value() && (network_type == network_preference); |
| 38 | } |
| 39 | |
| 40 | int 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 | |
| 58 | namespace cricket { |
| 59 | |
Jonas Oreland | 2f74d5f | 2019-11-22 06:53:22 | [diff] [blame] | 60 | BasicIceController::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 Oreland | 09c452e | 2019-11-20 08:01:02 | [diff] [blame] | 65 | |
| 66 | BasicIceController::~BasicIceController() {} |
| 67 | |
| 68 | void BasicIceController::SetIceConfig(const IceConfig& config) { |
| 69 | config_ = config; |
| 70 | } |
| 71 | |
| 72 | void BasicIceController::SetSelectedConnection( |
| 73 | const Connection* selected_connection) { |
| 74 | selected_connection_ = selected_connection; |
| 75 | } |
| 76 | |
| 77 | void BasicIceController::AddConnection(const Connection* connection) { |
| 78 | connections_.push_back(connection); |
| 79 | unpinged_connections_.insert(connection); |
| 80 | } |
| 81 | |
| 82 | void BasicIceController::OnConnectionDestroyed(const Connection* connection) { |
| 83 | pinged_connections_.erase(connection); |
| 84 | unpinged_connections_.erase(connection); |
| 85 | connections_.erase(absl::c_find(connections_, connection)); |
Tommi | af2930a | 2022-01-31 13:11:39 | [diff] [blame] | 86 | if (selected_connection_ == connection) |
| 87 | selected_connection_ = nullptr; |
Jonas Oreland | 09c452e | 2019-11-20 08:01:02 | [diff] [blame] | 88 | } |
| 89 | |
| 90 | bool 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 Oreland | 2f3c019 | 2020-03-26 11:59:44 | [diff] [blame] | 97 | IceControllerInterface::PingResult BasicIceController::SelectConnectionToPing( |
Jonas Oreland | 09c452e | 2019-11-20 08:01:02 | [diff] [blame] | 98 | 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 Oreland | 4333600 | 2020-03-26 19:59:03 | [diff] [blame] | 115 | PingResult res(conn, std::min(ping_interval, check_receiving_interval())); |
| 116 | return res; |
Jonas Oreland | 09c452e | 2019-11-20 08:01:02 | [diff] [blame] | 117 | } |
| 118 | |
| 119 | void 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. |
| 126 | const 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. |
| 212 | const 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 | |
| 237 | bool 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 | |
| 244 | int 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. |
| 266 | bool 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. |
| 322 | bool BasicIceController::IsBackupConnection(const Connection* conn) const { |
| 323 | return ice_transport_state_func_() == IceTransportState::STATE_COMPLETED && |
| 324 | conn != selected_connection_ && conn->active(); |
| 325 | } |
| 326 | |
| 327 | const 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 Titov | 2dbb4c9 | 2021-07-26 13:12:41 | [diff] [blame] | 344 | // one in the ordered `connections_`. |
Jonas Oreland | 09c452e | 2019-11-20 08:01:02 | [diff] [blame] | 345 | 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 | |
| 352 | const 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 | |
| 373 | const 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 Oreland | 98e745b | 2019-11-27 10:02:45 | [diff] [blame] | 385 | std::map<const rtc::Network*, const Connection*> |
Jonas Oreland | 09c452e | 2019-11-20 08:01:02 | [diff] [blame] | 386 | BasicIceController::GetBestConnectionByNetwork() const { |
Artem Titov | 2dbb4c9 | 2021-07-26 13:12:41 | [diff] [blame] | 387 | // `connections_` has been sorted, so the first one in the list on a given |
Jonas Oreland | 09c452e | 2019-11-20 08:01:02 | [diff] [blame] | 388 | // network is the best connection on the network, except that the selected |
| 389 | // connection is always the best connection on the network. |
Jonas Oreland | 98e745b | 2019-11-27 10:02:45 | [diff] [blame] | 390 | std::map<const rtc::Network*, const Connection*> best_connection_by_network; |
Jonas Oreland | 09c452e | 2019-11-20 08:01:02 | [diff] [blame] | 391 | if (selected_connection_) { |
Jonas Oreland | 98e745b | 2019-11-27 10:02:45 | [diff] [blame] | 392 | best_connection_by_network[selected_connection_->network()] = |
Jonas Oreland | 09c452e | 2019-11-20 08:01:02 | [diff] [blame] | 393 | selected_connection_; |
| 394 | } |
Artem Titov | 2dbb4c9 | 2021-07-26 13:12:41 | [diff] [blame] | 395 | // TODO(honghaiz): Need to update this if `connections_` are not sorted. |
Jonas Oreland | 09c452e | 2019-11-20 08:01:02 | [diff] [blame] | 396 | for (const Connection* conn : connections_) { |
Jonas Oreland | 98e745b | 2019-11-27 10:02:45 | [diff] [blame] | 397 | const rtc::Network* network = conn->network(); |
Jonas Oreland | 09c452e | 2019-11-20 08:01:02 | [diff] [blame] | 398 | // 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 | |
| 404 | std::vector<const Connection*> |
| 405 | BasicIceController::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 | |
| 416 | IceControllerInterface::SwitchResult |
| 417 | BasicIceController::HandleInitialSelectDampening( |
Sameer Vijaykar | 781c12e | 2022-06-02 14:01:12 | [diff] [blame] | 418 | IceSwitchReason reason, |
Jonas Oreland | 09c452e | 2019-11-20 08:01:02 | [diff] [blame] | 419 | 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 Vijaykar | 781c12e | 2022-06-02 14:01:12 | [diff] [blame] | 467 | return {.connection = absl::nullopt, |
Sameer Vijaykar | 2a1accd | 2022-06-03 09:39:39 | [diff] [blame] | 468 | .recheck_event = IceRecheckEvent( |
Sameer Vijaykar | 781c12e | 2022-06-02 14:01:12 | [diff] [blame] | 469 | IceSwitchReason::ICE_CONTROLLER_RECHECK, min_delay)}; |
Jonas Oreland | 09c452e | 2019-11-20 08:01:02 | [diff] [blame] | 470 | } |
| 471 | |
| 472 | IceControllerInterface::SwitchResult BasicIceController::ShouldSwitchConnection( |
Sameer Vijaykar | 781c12e | 2022-06-02 14:01:12 | [diff] [blame] | 473 | IceSwitchReason reason, |
Jonas Oreland | 09c452e | 2019-11-20 08:01:02 | [diff] [blame] | 474 | 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 Vijaykar | 2a1accd | 2022-06-03 09:39:39 | [diff] [blame] | 499 | absl::optional<IceRecheckEvent> recheck_event; |
Jonas Oreland | 09c452e | 2019-11-20 08:01:02 | [diff] [blame] | 500 | 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 Vijaykar | 781c12e | 2022-06-02 14:01:12 | [diff] [blame] | 506 | recheck_event.emplace(reason, |
| 507 | config_.receiving_switching_delay_or_default()); |
Jonas Oreland | 09c452e | 2019-11-20 08:01:02 | [diff] [blame] | 508 | } |
| 509 | |
| 510 | if (cmp < 0) { |
| 511 | return {new_connection, absl::nullopt}; |
| 512 | } else if (cmp > 0) { |
Jonas Oreland | b5aa0a8 | 2019-12-03 08:59:11 | [diff] [blame] | 513 | return {absl::nullopt, recheck_event}; |
Jonas Oreland | 09c452e | 2019-11-20 08:01:02 | [diff] [blame] | 514 | } |
| 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 Oreland | b5aa0a8 | 2019-12-03 08:59:11 | [diff] [blame] | 522 | return {absl::nullopt, recheck_event}; |
Jonas Oreland | 09c452e | 2019-11-20 08:01:02 | [diff] [blame] | 523 | } |
| 524 | |
| 525 | IceControllerInterface::SwitchResult |
Sameer Vijaykar | 781c12e | 2022-06-02 14:01:12 | [diff] [blame] | 526 | BasicIceController::SortAndSwitchConnection(IceSwitchReason reason) { |
Jonas Oreland | 09c452e | 2019-11-20 08:01:02 | [diff] [blame] | 527 | // 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 Hancke | 4f51b34 | 2023-07-25 14:56:44 | [diff] [blame] | 543 | << " available connections due to: " |
| 544 | << IceSwitchReasonToString(reason); |
Jonas Oreland | 09c452e | 2019-11-20 08:01:02 | [diff] [blame] | 545 | 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 | |
| 555 | bool 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 | |
| 565 | bool BasicIceController::PresumedWritable(const Connection* conn) const { |
| 566 | return (conn->write_state() == Connection::STATE_WRITE_INIT && |
| 567 | config_.presume_writable_when_fully_relayed && |
Tommi | 0a7fc84 | 2024-01-19 12:11:37 | [diff] [blame] | 568 | conn->local_candidate().is_relay() && |
| 569 | (conn->remote_candidate().is_relay() || |
| 570 | conn->remote_candidate().is_prflx())); |
Jonas Oreland | 09c452e | 2019-11-20 08:01:02 | [diff] [blame] | 571 | } |
| 572 | |
| 573 | // Compare two connections based on their writing, receiving, and connected |
| 574 | // states. |
| 575 | int 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 Titov | 2dbb4c9 | 2021-07-26 13:12:41 | [diff] [blame] | 650 | // Returns positive if `a` is better than `b`. |
Jonas Oreland | 09c452e | 2019-11-20 08:01:02 | [diff] [blame] | 651 | int 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 Oreland | 98e745b | 2019-11-27 10:02:45 | [diff] [blame] | 669 | int cmp = (a->remote_candidate().generation() + a->generation()) - |
| 670 | (b->remote_candidate().generation() + b->generation()); |
Jonas Oreland | 09c452e | 2019-11-20 08:01:02 | [diff] [blame] | 671 | 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 | |
| 693 | int 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 | |
| 732 | int 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 Oreland | c8fa1ee | 2021-08-25 06:58:04 | [diff] [blame] | 744 | 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 Oreland | 09c452e | 2019-11-20 08:01:02 | [diff] [blame] | 769 | 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 | |
| 781 | std::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 Titov | 2dbb4c9 | 2021-07-26 13:12:41 | [diff] [blame] | 788 | // switch. If `best_conn_on_network` is not connected, we may be reconnecting |
Jonas Oreland | 09c452e | 2019-11-20 08:01:02 | [diff] [blame] | 789 | // 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 Bui | 1e589eb | 2022-08-02 07:37:30 | [diff] [blame] | 800 | if (!rtc::IPIsAny(conn->network()->GetBestIP())) { |
Jonas Oreland | 09c452e | 2019-11-20 08:01:02 | [diff] [blame] | 801 | // 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 Oreland | 98e745b | 2019-11-27 10:02:45 | [diff] [blame] | 806 | best_conn = best_connection_by_network[conn->network()]; |
Jonas Oreland | 09c452e | 2019-11-20 08:01:02 | [diff] [blame] | 807 | } |
| 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 | |
| 818 | bool 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 Titov | 2dbb4c9 | 2021-07-26 13:12:41 | [diff] [blame] | 834 | // a.1) `conn` is the selected connection OR |
Jonas Oreland | 09c452e | 2019-11-20 08:01:02 | [diff] [blame] | 835 | // a.2) there is no selected connection OR |
| 836 | // a.3) the selected connection is unwritable OR |
Artem Titov | 2dbb4c9 | 2021-07-26 13:12:41 | [diff] [blame] | 837 | // a.4) `conn` has higher priority than selected_connection. |
Jonas Oreland | 09c452e | 2019-11-20 08:01:02 | [diff] [blame] | 838 | // b) Remote is in LITE ICE AND |
Artem Titov | 2dbb4c9 | 2021-07-26 13:12:41 | [diff] [blame] | 839 | // b.1) `conn` is the selected_connection AND |
| 840 | // b.2) `conn` is writable. |
Jonas Oreland | 09c452e | 2019-11-20 08:01:02 | [diff] [blame] | 841 | 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 Titov | d325196 | 2021-11-15 15:57:07 | [diff] [blame] | 851 | RTC_DCHECK_NOTREACHED(); |
Jonas Oreland | 09c452e | 2019-11-20 08:01:02 | [diff] [blame] | 852 | return false; |
| 853 | } |
| 854 | } |
| 855 | |
| 856 | } // namespace cricket |