/* * Copyright 2022 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/wrapping_active_ice_controller.h" #include #include #include #include "api/sequence_checker.h" #include "api/task_queue/pending_task_safety_flag.h" #include "api/units/time_delta.h" #include "p2p/base/basic_ice_controller.h" #include "p2p/base/connection.h" #include "p2p/base/ice_agent_interface.h" #include "p2p/base/ice_controller_interface.h" #include "p2p/base/ice_switch_reason.h" #include "p2p/base/ice_transport_internal.h" #include "p2p/base/transport_description.h" #include "rtc_base/logging.h" #include "rtc_base/thread.h" #include "rtc_base/time_utils.h" namespace { using ::webrtc::SafeTask; using ::webrtc::TimeDelta; } // unnamed namespace namespace cricket { WrappingActiveIceController::WrappingActiveIceController( IceAgentInterface* ice_agent, std::unique_ptr wrapped) : network_thread_(rtc::Thread::Current()), wrapped_(std::move(wrapped)), agent_(*ice_agent) { RTC_DCHECK(ice_agent != nullptr); } WrappingActiveIceController::WrappingActiveIceController( IceAgentInterface* ice_agent, IceControllerFactoryInterface* wrapped_factory, const IceControllerFactoryArgs& wrapped_factory_args) : network_thread_(rtc::Thread::Current()), agent_(*ice_agent) { RTC_DCHECK(ice_agent != nullptr); if (wrapped_factory) { wrapped_ = wrapped_factory->Create(wrapped_factory_args); } else { wrapped_ = std::make_unique(wrapped_factory_args); } } WrappingActiveIceController::~WrappingActiveIceController() {} void WrappingActiveIceController::SetIceConfig(const IceConfig& config) { RTC_DCHECK_RUN_ON(network_thread_); wrapped_->SetIceConfig(config); } bool WrappingActiveIceController::GetUseCandidateAttribute( const Connection* connection, NominationMode mode, IceMode remote_ice_mode) const { RTC_DCHECK_RUN_ON(network_thread_); return wrapped_->GetUseCandidateAttr(connection, mode, remote_ice_mode); } void WrappingActiveIceController::OnConnectionAdded( const Connection* connection) { RTC_DCHECK_RUN_ON(network_thread_); wrapped_->AddConnection(connection); } void WrappingActiveIceController::OnConnectionPinged( const Connection* connection) { RTC_DCHECK_RUN_ON(network_thread_); wrapped_->MarkConnectionPinged(connection); } void WrappingActiveIceController::OnConnectionUpdated( const Connection* connection) { RTC_LOG(LS_VERBOSE) << "Connection report for " << connection->ToString(); // Do nothing. Native ICE controllers have direct access to Connection, so no // need to update connection state separately. } void WrappingActiveIceController::OnConnectionSwitched( const Connection* connection) { RTC_DCHECK_RUN_ON(network_thread_); selected_connection_ = connection; wrapped_->SetSelectedConnection(connection); } void WrappingActiveIceController::OnConnectionDestroyed( const Connection* connection) { RTC_DCHECK_RUN_ON(network_thread_); wrapped_->OnConnectionDestroyed(connection); } void WrappingActiveIceController::MaybeStartPinging() { RTC_DCHECK_RUN_ON(network_thread_); if (started_pinging_) { return; } if (wrapped_->HasPingableConnection()) { network_thread_->PostTask( SafeTask(task_safety_.flag(), [this]() { SelectAndPingConnection(); })); agent_.OnStartedPinging(); started_pinging_ = true; } } void WrappingActiveIceController::SelectAndPingConnection() { RTC_DCHECK_RUN_ON(network_thread_); agent_.UpdateConnectionStates(); IceControllerInterface::PingResult result = wrapped_->SelectConnectionToPing(agent_.GetLastPingSentMs()); HandlePingResult(result); } void WrappingActiveIceController::HandlePingResult( IceControllerInterface::PingResult result) { RTC_DCHECK_RUN_ON(network_thread_); if (result.connection.has_value()) { agent_.SendPingRequest(result.connection.value()); } network_thread_->PostDelayedTask( SafeTask(task_safety_.flag(), [this]() { SelectAndPingConnection(); }), TimeDelta::Millis(result.recheck_delay_ms)); } void WrappingActiveIceController::OnSortAndSwitchRequest( IceSwitchReason reason) { RTC_DCHECK_RUN_ON(network_thread_); if (!sort_pending_) { network_thread_->PostTask(SafeTask(task_safety_.flag(), [this, reason]() { SortAndSwitchToBestConnection(reason); })); sort_pending_ = true; } } void WrappingActiveIceController::OnImmediateSortAndSwitchRequest( IceSwitchReason reason) { RTC_DCHECK_RUN_ON(network_thread_); SortAndSwitchToBestConnection(reason); } void WrappingActiveIceController::SortAndSwitchToBestConnection( IceSwitchReason reason) { RTC_DCHECK_RUN_ON(network_thread_); // Make sure the connection states are up-to-date since this affects how they // will be sorted. agent_.UpdateConnectionStates(); // Any changes after this point will require a re-sort. sort_pending_ = false; IceControllerInterface::SwitchResult result = wrapped_->SortAndSwitchConnection(reason); HandleSwitchResult(reason, result); UpdateStateOnConnectionsResorted(); } bool WrappingActiveIceController::OnImmediateSwitchRequest( IceSwitchReason reason, const Connection* selected) { RTC_DCHECK_RUN_ON(network_thread_); IceControllerInterface::SwitchResult result = wrapped_->ShouldSwitchConnection(reason, selected); HandleSwitchResult(reason, result); return result.connection.has_value(); } void WrappingActiveIceController::HandleSwitchResult( IceSwitchReason reason_for_switch, IceControllerInterface::SwitchResult result) { RTC_DCHECK_RUN_ON(network_thread_); if (result.connection.has_value()) { RTC_LOG(LS_INFO) << "Switching selected connection due to: " << IceSwitchReasonToString(reason_for_switch); agent_.SwitchSelectedConnection(result.connection.value(), reason_for_switch); } if (result.recheck_event.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. network_thread_->PostDelayedTask( SafeTask(task_safety_.flag(), [this, recheck_reason = result.recheck_event->reason]() { SortAndSwitchToBestConnection(recheck_reason); }), TimeDelta::Millis(result.recheck_event->recheck_delay_ms)); } agent_.ForgetLearnedStateForConnections( result.connections_to_forget_state_on); } void WrappingActiveIceController::UpdateStateOnConnectionsResorted() { RTC_DCHECK_RUN_ON(network_thread_); PruneConnections(); // Update the internal state of the ICE agentl. agent_.UpdateState(); // Also possibly start pinging. // We could start pinging if: // * The first connection was created. // * ICE credentials were provided. // * A TCP connection became connected. MaybeStartPinging(); } void WrappingActiveIceController::PruneConnections() { RTC_DCHECK_RUN_ON(network_thread_); // The controlled side can prune only if the selected connection has been // nominated because otherwise it may prune the connection that will be // selected by the controlling side. // TODO(honghaiz): This is not enough to prevent a connection from being // pruned too early because with aggressive nomination, the controlling side // will nominate every connection until it becomes writable. if (agent_.GetIceRole() == ICEROLE_CONTROLLING || (selected_connection_ && selected_connection_->nominated())) { std::vector connections_to_prune = wrapped_->PruneConnections(); agent_.PruneConnections(connections_to_prune); } } // Only for unit tests const Connection* WrappingActiveIceController::FindNextPingableConnection() { RTC_DCHECK_RUN_ON(network_thread_); return wrapped_->FindNextPingableConnection(); } } // namespace cricket