From 36d22d82aa202bb199967e9512281e9a53db42c9 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 21:33:14 +0200 Subject: Adding upstream version 115.7.0esr. Signed-off-by: Daniel Baumann --- .../libwebrtc/p2p/base/p2p_transport_channel.cc | 2636 ++++++++++++++++++++ 1 file changed, 2636 insertions(+) create mode 100644 third_party/libwebrtc/p2p/base/p2p_transport_channel.cc (limited to 'third_party/libwebrtc/p2p/base/p2p_transport_channel.cc') diff --git a/third_party/libwebrtc/p2p/base/p2p_transport_channel.cc b/third_party/libwebrtc/p2p/base/p2p_transport_channel.cc new file mode 100644 index 0000000000..15b1cf6f87 --- /dev/null +++ b/third_party/libwebrtc/p2p/base/p2p_transport_channel.cc @@ -0,0 +1,2636 @@ +/* + * Copyright 2004 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/p2p_transport_channel.h" + +#include +#include + +#include +#include +#include +#include +#include + +#include "absl/algorithm/container.h" +#include "absl/memory/memory.h" +#include "absl/strings/match.h" +#include "absl/strings/string_view.h" +#include "api/async_dns_resolver.h" +#include "api/candidate.h" +#include "api/field_trials_view.h" +#include "api/units/time_delta.h" +#include "logging/rtc_event_log/ice_logger.h" +#include "p2p/base/basic_async_resolver_factory.h" +#include "p2p/base/basic_ice_controller.h" +#include "p2p/base/connection.h" +#include "p2p/base/connection_info.h" +#include "p2p/base/port.h" +#include "p2p/base/wrapping_active_ice_controller.h" +#include "rtc_base/checks.h" +#include "rtc_base/crc32.h" +#include "rtc_base/experiments/struct_parameters_parser.h" +#include "rtc_base/ip_address.h" +#include "rtc_base/logging.h" +#include "rtc_base/net_helper.h" +#include "rtc_base/network.h" +#include "rtc_base/network_constants.h" +#include "rtc_base/string_encode.h" +#include "rtc_base/third_party/sigslot/sigslot.h" +#include "rtc_base/time_utils.h" +#include "rtc_base/trace_event.h" +#include "system_wrappers/include/metrics.h" + +namespace { + +cricket::PortInterface::CandidateOrigin GetOrigin( + cricket::PortInterface* port, + cricket::PortInterface* origin_port) { + if (!origin_port) + return cricket::PortInterface::ORIGIN_MESSAGE; + else if (port == origin_port) + return cricket::PortInterface::ORIGIN_THIS_PORT; + else + return cricket::PortInterface::ORIGIN_OTHER_PORT; +} + +uint32_t GetWeakPingIntervalInFieldTrial( + const webrtc::FieldTrialsView* field_trials) { + if (field_trials != nullptr) { + uint32_t weak_ping_interval = + ::strtoul(field_trials->Lookup("WebRTC-StunInterPacketDelay").c_str(), + nullptr, 10); + if (weak_ping_interval) { + return static_cast(weak_ping_interval); + } + } + return cricket::WEAK_PING_INTERVAL; +} + +rtc::RouteEndpoint CreateRouteEndpointFromCandidate( + bool local, + const cricket::Candidate& candidate, + bool uses_turn) { + auto adapter_type = candidate.network_type(); + if (!local && adapter_type == rtc::ADAPTER_TYPE_UNKNOWN) { + bool vpn; + std::tie(adapter_type, vpn) = + rtc::Network::GuessAdapterFromNetworkCost(candidate.network_cost()); + } + + // TODO(bugs.webrtc.org/9446) : Rewrite if information about remote network + // adapter becomes available. The implication of this implementation is that + // we will only ever report 1 adapter per type. In practice this is probably + // fine, since the endpoint also contains network-id. + uint16_t adapter_id = static_cast(adapter_type); + return rtc::RouteEndpoint(adapter_type, adapter_id, candidate.network_id(), + uses_turn); +} + +bool UseActiveIceControllerFieldTrialEnabled( + const webrtc::FieldTrialsView* field_trials) { + // Feature to refactor ICE controller and enable active ICE controllers. + // Field trial key reserved in bugs.webrtc.org/14367 + return field_trials && + field_trials->IsEnabled("WebRTC-UseActiveIceController"); +} + +using ::webrtc::RTCError; +using ::webrtc::RTCErrorType; +using ::webrtc::SafeTask; +using ::webrtc::TimeDelta; + +} // unnamed namespace + +namespace cricket { + +bool IceCredentialsChanged(absl::string_view old_ufrag, + absl::string_view old_pwd, + absl::string_view new_ufrag, + absl::string_view new_pwd) { + // The standard (RFC 5245 Section 9.1.1.1) says that ICE restarts MUST change + // both the ufrag and password. However, section 9.2.1.1 says changing the + // ufrag OR password indicates an ICE restart. So, to keep compatibility with + // endpoints that only change one, we'll treat this as an ICE restart. + return (old_ufrag != new_ufrag) || (old_pwd != new_pwd); +} + +std::unique_ptr P2PTransportChannel::Create( + absl::string_view transport_name, + int component, + webrtc::IceTransportInit init) { + if (init.async_resolver_factory()) { + return absl::WrapUnique(new P2PTransportChannel( + transport_name, component, init.port_allocator(), nullptr, + std::make_unique( + init.async_resolver_factory()), + init.event_log(), init.ice_controller_factory(), + init.active_ice_controller_factory(), init.field_trials())); + } else { + return absl::WrapUnique(new P2PTransportChannel( + transport_name, component, init.port_allocator(), + init.async_dns_resolver_factory(), nullptr, init.event_log(), + init.ice_controller_factory(), init.active_ice_controller_factory(), + init.field_trials())); + } +} + +P2PTransportChannel::P2PTransportChannel( + absl::string_view transport_name, + int component, + PortAllocator* allocator, + const webrtc::FieldTrialsView* field_trials) + : P2PTransportChannel(transport_name, + component, + allocator, + /* async_dns_resolver_factory= */ nullptr, + /* owned_dns_resolver_factory= */ nullptr, + /* event_log= */ nullptr, + /* ice_controller_factory= */ nullptr, + /* active_ice_controller_factory= */ nullptr, + field_trials) {} + +// Private constructor, called from Create() +P2PTransportChannel::P2PTransportChannel( + absl::string_view transport_name, + int component, + PortAllocator* allocator, + webrtc::AsyncDnsResolverFactoryInterface* async_dns_resolver_factory, + std::unique_ptr + owned_dns_resolver_factory, + webrtc::RtcEventLog* event_log, + IceControllerFactoryInterface* ice_controller_factory, + ActiveIceControllerFactoryInterface* active_ice_controller_factory, + const webrtc::FieldTrialsView* field_trials) + : transport_name_(transport_name), + component_(component), + allocator_(allocator), + // If owned_dns_resolver_factory is given, async_dns_resolver_factory is + // ignored. + async_dns_resolver_factory_(owned_dns_resolver_factory + ? owned_dns_resolver_factory.get() + : async_dns_resolver_factory), + owned_dns_resolver_factory_(std::move(owned_dns_resolver_factory)), + network_thread_(rtc::Thread::Current()), + incoming_only_(false), + error_(0), + sort_dirty_(false), + remote_ice_mode_(ICEMODE_FULL), + ice_role_(ICEROLE_UNKNOWN), + tiebreaker_(0), + gathering_state_(kIceGatheringNew), + weak_ping_interval_(GetWeakPingIntervalInFieldTrial(field_trials)), + config_(RECEIVING_TIMEOUT, + BACKUP_CONNECTION_PING_INTERVAL, + GATHER_ONCE /* continual_gathering_policy */, + false /* prioritize_most_likely_candidate_pairs */, + STRONG_AND_STABLE_WRITABLE_CONNECTION_PING_INTERVAL, + true /* presume_writable_when_fully_relayed */, + REGATHER_ON_FAILED_NETWORKS_INTERVAL, + RECEIVING_SWITCHING_DELAY) { + TRACE_EVENT0("webrtc", "P2PTransportChannel::P2PTransportChannel"); + RTC_DCHECK(allocator_ != nullptr); + // Validate IceConfig even for mostly built-in constant default values in case + // we change them. + RTC_DCHECK(ValidateIceConfig(config_).ok()); + webrtc::BasicRegatheringController::Config regathering_config; + regathering_config.regather_on_failed_networks_interval = + config_.regather_on_failed_networks_interval_or_default(); + regathering_controller_ = + std::make_unique( + regathering_config, this, network_thread_); + // We populate the change in the candidate filter to the session taken by + // the transport. + allocator_->SignalCandidateFilterChanged.connect( + this, &P2PTransportChannel::OnCandidateFilterChanged); + ice_event_log_.set_event_log(event_log); + + ParseFieldTrials(field_trials); + + IceControllerFactoryArgs args{ + [this] { return GetState(); }, [this] { return GetIceRole(); }, + [this](const Connection* connection) { + return IsPortPruned(connection->port()) || + IsRemoteCandidatePruned(connection->remote_candidate()); + }, + &ice_field_trials_, + field_trials ? field_trials->Lookup("WebRTC-IceControllerFieldTrials") + : ""}; + ice_adapter_ = std::make_unique( + args, ice_controller_factory, active_ice_controller_factory, field_trials, + /* transport= */ this); +} + +P2PTransportChannel::~P2PTransportChannel() { + TRACE_EVENT0("webrtc", "P2PTransportChannel::~P2PTransportChannel"); + RTC_DCHECK_RUN_ON(network_thread_); + std::vector copy(connections().begin(), connections().end()); + for (Connection* connection : copy) { + connection->SignalDestroyed.disconnect(this); + RemoveConnection(connection); + connection->Destroy(); + } + resolvers_.clear(); +} + +// Add the allocator session to our list so that we know which sessions +// are still active. +void P2PTransportChannel::AddAllocatorSession( + std::unique_ptr session) { + RTC_DCHECK_RUN_ON(network_thread_); + + session->set_generation(static_cast(allocator_sessions_.size())); + session->SignalPortReady.connect(this, &P2PTransportChannel::OnPortReady); + session->SignalPortsPruned.connect(this, &P2PTransportChannel::OnPortsPruned); + session->SignalCandidatesReady.connect( + this, &P2PTransportChannel::OnCandidatesReady); + session->SignalCandidateError.connect(this, + &P2PTransportChannel::OnCandidateError); + session->SignalCandidatesRemoved.connect( + this, &P2PTransportChannel::OnCandidatesRemoved); + session->SignalCandidatesAllocationDone.connect( + this, &P2PTransportChannel::OnCandidatesAllocationDone); + if (!allocator_sessions_.empty()) { + allocator_session()->PruneAllPorts(); + } + allocator_sessions_.push_back(std::move(session)); + regathering_controller_->set_allocator_session(allocator_session()); + + // We now only want to apply new candidates that we receive to the ports + // created by this new session because these are replacing those of the + // previous sessions. + PruneAllPorts(); +} + +void P2PTransportChannel::AddConnection(Connection* connection) { + RTC_DCHECK_RUN_ON(network_thread_); + connection->set_receiving_timeout(config_.receiving_timeout); + connection->set_unwritable_timeout(config_.ice_unwritable_timeout); + connection->set_unwritable_min_checks(config_.ice_unwritable_min_checks); + connection->set_inactive_timeout(config_.ice_inactive_timeout); + connection->SignalReadPacket.connect(this, + &P2PTransportChannel::OnReadPacket); + connection->SignalReadyToSend.connect(this, + &P2PTransportChannel::OnReadyToSend); + connection->SignalStateChange.connect( + this, &P2PTransportChannel::OnConnectionStateChange); + connection->SignalDestroyed.connect( + this, &P2PTransportChannel::OnConnectionDestroyed); + connection->SignalNominated.connect(this, &P2PTransportChannel::OnNominated); + + had_connection_ = true; + + connection->set_ice_event_log(&ice_event_log_); + connection->SetIceFieldTrials(&ice_field_trials_); + LogCandidatePairConfig(connection, + webrtc::IceCandidatePairConfigType::kAdded); + + connections_.push_back(connection); + ice_adapter_->OnConnectionAdded(connection); +} + +// TODO(bugs.webrtc.org/14367) remove once refactor lands. +bool P2PTransportChannel::MaybeSwitchSelectedConnection( + const Connection* new_connection, + IceSwitchReason reason) { + RTC_DCHECK_RUN_ON(network_thread_); + + return MaybeSwitchSelectedConnection( + reason, + ice_adapter_->LegacyShouldSwitchConnection(reason, new_connection)); +} + +// TODO(bugs.webrtc.org/14367) remove once refactor lands. +bool P2PTransportChannel::MaybeSwitchSelectedConnection( + IceSwitchReason reason, + 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); + SwitchSelectedConnection(FromIceController(*result.connection), reason); + } + + 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, reason = result.recheck_event->reason]() { + SortConnectionsAndUpdateState(reason); + }), + TimeDelta::Millis(result.recheck_event->recheck_delay_ms)); + } + + for (const auto* con : result.connections_to_forget_state_on) { + FromIceController(con)->ForgetLearnedState(); + } + + return result.connection.has_value(); +} + +void P2PTransportChannel::ForgetLearnedStateForConnections( + rtc::ArrayView connections) { + for (const Connection* con : connections) { + FromIceController(con)->ForgetLearnedState(); + } +} + +void P2PTransportChannel::SetIceRole(IceRole ice_role) { + RTC_DCHECK_RUN_ON(network_thread_); + if (ice_role_ != ice_role) { + ice_role_ = ice_role; + for (PortInterface* port : ports_) { + port->SetIceRole(ice_role); + } + // Update role on pruned ports as well, because they may still have + // connections alive that should be using the correct role. + for (PortInterface* port : pruned_ports_) { + port->SetIceRole(ice_role); + } + } +} + +IceRole P2PTransportChannel::GetIceRole() const { + RTC_DCHECK_RUN_ON(network_thread_); + return ice_role_; +} + +void P2PTransportChannel::SetIceTiebreaker(uint64_t tiebreaker) { + RTC_DCHECK_RUN_ON(network_thread_); + if (!ports_.empty() || !pruned_ports_.empty()) { + RTC_LOG(LS_ERROR) + << "Attempt to change tiebreaker after Port has been allocated."; + return; + } + + tiebreaker_ = tiebreaker; +} + +IceTransportState P2PTransportChannel::GetState() const { + RTC_DCHECK_RUN_ON(network_thread_); + return state_; +} + +webrtc::IceTransportState P2PTransportChannel::GetIceTransportState() const { + RTC_DCHECK_RUN_ON(network_thread_); + return standardized_state_; +} + +const std::string& P2PTransportChannel::transport_name() const { + RTC_DCHECK_RUN_ON(network_thread_); + return transport_name_; +} + +int P2PTransportChannel::component() const { + RTC_DCHECK_RUN_ON(network_thread_); + return component_; +} + +bool P2PTransportChannel::writable() const { + RTC_DCHECK_RUN_ON(network_thread_); + return writable_; +} + +bool P2PTransportChannel::receiving() const { + RTC_DCHECK_RUN_ON(network_thread_); + return receiving_; +} + +IceGatheringState P2PTransportChannel::gathering_state() const { + RTC_DCHECK_RUN_ON(network_thread_); + return gathering_state_; +} + +absl::optional P2PTransportChannel::GetRttEstimate() { + RTC_DCHECK_RUN_ON(network_thread_); + if (selected_connection_ != nullptr && + selected_connection_->rtt_samples() > 0) { + return selected_connection_->rtt(); + } else { + return absl::nullopt; + } +} + +absl::optional +P2PTransportChannel::GetSelectedCandidatePair() const { + RTC_DCHECK_RUN_ON(network_thread_); + if (selected_connection_ == nullptr) { + return absl::nullopt; + } + + CandidatePair pair; + pair.local = SanitizeLocalCandidate(selected_connection_->local_candidate()); + pair.remote = + SanitizeRemoteCandidate(selected_connection_->remote_candidate()); + return pair; +} + +// A channel is considered ICE completed once there is at most one active +// connection per network and at least one active connection. +IceTransportState P2PTransportChannel::ComputeState() const { + RTC_DCHECK_RUN_ON(network_thread_); + if (!had_connection_) { + return IceTransportState::STATE_INIT; + } + + std::vector active_connections; + for (Connection* connection : connections()) { + if (connection->active()) { + active_connections.push_back(connection); + } + } + if (active_connections.empty()) { + return IceTransportState::STATE_FAILED; + } + + std::set networks; + for (Connection* connection : active_connections) { + const rtc::Network* network = connection->network(); + if (networks.find(network) == networks.end()) { + networks.insert(network); + } else { + RTC_LOG(LS_VERBOSE) << ToString() + << ": Ice not completed yet for this channel as " + << network->ToString() + << " has more than 1 connection."; + return IceTransportState::STATE_CONNECTING; + } + } + + ice_event_log_.DumpCandidatePairDescriptionToMemoryAsConfigEvents(); + return IceTransportState::STATE_COMPLETED; +} + +// Compute the current RTCIceTransportState as described in +// https://www.w3.org/TR/webrtc/#dom-rtcicetransportstate +// TODO(bugs.webrtc.org/9218): Start signaling kCompleted once we have +// implemented end-of-candidates signalling. +webrtc::IceTransportState P2PTransportChannel::ComputeIceTransportState() + const { + RTC_DCHECK_RUN_ON(network_thread_); + bool has_connection = false; + for (Connection* connection : connections()) { + if (connection->active()) { + has_connection = true; + break; + } + } + + if (had_connection_ && !has_connection) { + return webrtc::IceTransportState::kFailed; + } + + if (!writable() && has_been_writable_) { + return webrtc::IceTransportState::kDisconnected; + } + + if (!had_connection_ && !has_connection) { + return webrtc::IceTransportState::kNew; + } + + if (has_connection && !writable()) { + // A candidate pair has been formed by adding a remote candidate + // and gathering a local candidate. + return webrtc::IceTransportState::kChecking; + } + + return webrtc::IceTransportState::kConnected; +} + +void P2PTransportChannel::SetIceParameters(const IceParameters& ice_params) { + RTC_DCHECK_RUN_ON(network_thread_); + RTC_LOG(LS_INFO) << "Set ICE ufrag: " << ice_params.ufrag + << " pwd: " << ice_params.pwd << " on transport " + << transport_name(); + ice_parameters_ = ice_params; + // Note: Candidate gathering will restart when MaybeStartGathering is next + // called. +} + +void P2PTransportChannel::SetRemoteIceParameters( + const IceParameters& ice_params) { + RTC_DCHECK_RUN_ON(network_thread_); + RTC_LOG(LS_INFO) << "Received remote ICE parameters: ufrag=" + << ice_params.ufrag << ", renomination " + << (ice_params.renomination ? "enabled" : "disabled"); + IceParameters* current_ice = remote_ice(); + if (!current_ice || *current_ice != ice_params) { + // Keep the ICE credentials so that newer connections + // are prioritized over the older ones. + remote_ice_parameters_.push_back(ice_params); + } + + // Update the pwd of remote candidate if needed. + for (RemoteCandidate& candidate : remote_candidates_) { + if (candidate.username() == ice_params.ufrag && + candidate.password().empty()) { + candidate.set_password(ice_params.pwd); + } + } + // We need to update the credentials and generation for any peer reflexive + // candidates. + for (Connection* conn : connections()) { + conn->MaybeSetRemoteIceParametersAndGeneration( + ice_params, static_cast(remote_ice_parameters_.size() - 1)); + } + // Updating the remote ICE candidate generation could change the sort order. + ice_adapter_->OnSortAndSwitchRequest( + IceSwitchReason::REMOTE_CANDIDATE_GENERATION_CHANGE); +} + +void P2PTransportChannel::SetRemoteIceMode(IceMode mode) { + RTC_DCHECK_RUN_ON(network_thread_); + remote_ice_mode_ = mode; +} + +// TODO(qingsi): We apply the convention that setting a absl::optional parameter +// to null restores its default value in the implementation. However, some +// absl::optional parameters are only processed below if non-null, e.g., +// regather_on_failed_networks_interval, and thus there is no way to restore the +// defaults. Fix this issue later for consistency. +void P2PTransportChannel::SetIceConfig(const IceConfig& config) { + RTC_DCHECK_RUN_ON(network_thread_); + if (config_.continual_gathering_policy != config.continual_gathering_policy) { + if (!allocator_sessions_.empty()) { + RTC_LOG(LS_ERROR) << "Trying to change continual gathering policy " + "when gathering has already started!"; + } else { + config_.continual_gathering_policy = config.continual_gathering_policy; + RTC_LOG(LS_INFO) << "Set continual_gathering_policy to " + << config_.continual_gathering_policy; + } + } + + if (config_.backup_connection_ping_interval != + config.backup_connection_ping_interval) { + config_.backup_connection_ping_interval = + config.backup_connection_ping_interval; + RTC_LOG(LS_INFO) << "Set backup connection ping interval to " + << config_.backup_connection_ping_interval_or_default() + << " milliseconds."; + } + if (config_.receiving_timeout != config.receiving_timeout) { + config_.receiving_timeout = config.receiving_timeout; + for (Connection* connection : connections()) { + connection->set_receiving_timeout(config_.receiving_timeout); + } + RTC_LOG(LS_INFO) << "Set ICE receiving timeout to " + << config_.receiving_timeout_or_default() + << " milliseconds"; + } + + config_.prioritize_most_likely_candidate_pairs = + config.prioritize_most_likely_candidate_pairs; + RTC_LOG(LS_INFO) << "Set ping most likely connection to " + << config_.prioritize_most_likely_candidate_pairs; + + if (config_.stable_writable_connection_ping_interval != + config.stable_writable_connection_ping_interval) { + config_.stable_writable_connection_ping_interval = + config.stable_writable_connection_ping_interval; + RTC_LOG(LS_INFO) + << "Set stable_writable_connection_ping_interval to " + << config_.stable_writable_connection_ping_interval_or_default(); + } + + if (config_.presume_writable_when_fully_relayed != + config.presume_writable_when_fully_relayed) { + if (!connections().empty()) { + RTC_LOG(LS_ERROR) << "Trying to change 'presume writable' " + "while connections already exist!"; + } else { + config_.presume_writable_when_fully_relayed = + config.presume_writable_when_fully_relayed; + RTC_LOG(LS_INFO) << "Set presume writable when fully relayed to " + << config_.presume_writable_when_fully_relayed; + } + } + + config_.surface_ice_candidates_on_ice_transport_type_changed = + config.surface_ice_candidates_on_ice_transport_type_changed; + if (config_.surface_ice_candidates_on_ice_transport_type_changed && + config_.continual_gathering_policy != GATHER_CONTINUALLY) { + RTC_LOG(LS_WARNING) + << "surface_ice_candidates_on_ice_transport_type_changed is " + "ineffective since we do not gather continually."; + } + + if (config_.regather_on_failed_networks_interval != + config.regather_on_failed_networks_interval) { + config_.regather_on_failed_networks_interval = + config.regather_on_failed_networks_interval; + RTC_LOG(LS_INFO) + << "Set regather_on_failed_networks_interval to " + << config_.regather_on_failed_networks_interval_or_default(); + } + + if (config_.receiving_switching_delay != config.receiving_switching_delay) { + config_.receiving_switching_delay = config.receiving_switching_delay; + RTC_LOG(LS_INFO) << "Set receiving_switching_delay to " + << config_.receiving_switching_delay_or_default(); + } + + if (config_.default_nomination_mode != config.default_nomination_mode) { + config_.default_nomination_mode = config.default_nomination_mode; + RTC_LOG(LS_INFO) << "Set default nomination mode to " + << static_cast(config_.default_nomination_mode); + } + + if (config_.ice_check_interval_strong_connectivity != + config.ice_check_interval_strong_connectivity) { + config_.ice_check_interval_strong_connectivity = + config.ice_check_interval_strong_connectivity; + RTC_LOG(LS_INFO) + << "Set strong ping interval to " + << config_.ice_check_interval_strong_connectivity_or_default(); + } + + if (config_.ice_check_interval_weak_connectivity != + config.ice_check_interval_weak_connectivity) { + config_.ice_check_interval_weak_connectivity = + config.ice_check_interval_weak_connectivity; + RTC_LOG(LS_INFO) + << "Set weak ping interval to " + << config_.ice_check_interval_weak_connectivity_or_default(); + } + + if (config_.ice_check_min_interval != config.ice_check_min_interval) { + config_.ice_check_min_interval = config.ice_check_min_interval; + RTC_LOG(LS_INFO) << "Set min ping interval to " + << config_.ice_check_min_interval_or_default(); + } + + if (config_.ice_unwritable_timeout != config.ice_unwritable_timeout) { + config_.ice_unwritable_timeout = config.ice_unwritable_timeout; + for (Connection* conn : connections()) { + conn->set_unwritable_timeout(config_.ice_unwritable_timeout); + } + RTC_LOG(LS_INFO) << "Set unwritable timeout to " + << config_.ice_unwritable_timeout_or_default(); + } + + 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()) { + conn->set_unwritable_min_checks(config_.ice_unwritable_min_checks); + } + RTC_LOG(LS_INFO) << "Set unwritable min checks to " + << config_.ice_unwritable_min_checks_or_default(); + } + + if (config_.ice_inactive_timeout != config.ice_inactive_timeout) { + config_.ice_inactive_timeout = config.ice_inactive_timeout; + for (Connection* conn : connections()) { + conn->set_inactive_timeout(config_.ice_inactive_timeout); + } + RTC_LOG(LS_INFO) << "Set inactive timeout to " + << config_.ice_inactive_timeout_or_default(); + } + + if (config_.network_preference != config.network_preference) { + config_.network_preference = config.network_preference; + ice_adapter_->OnSortAndSwitchRequest( + IceSwitchReason::NETWORK_PREFERENCE_CHANGE); + RTC_LOG(LS_INFO) << "Set network preference to " + << (config_.network_preference.has_value() + ? config_.network_preference.value() + : -1); // network_preference cannot be bound to + // int with value_or. + } + + // TODO(qingsi): Resolve the naming conflict of stun_keepalive_delay in + // UDPPort and stun_keepalive_interval. + if (config_.stun_keepalive_interval != config.stun_keepalive_interval) { + config_.stun_keepalive_interval = config.stun_keepalive_interval; + allocator_session()->SetStunKeepaliveIntervalForReadyPorts( + config_.stun_keepalive_interval); + RTC_LOG(LS_INFO) << "Set STUN keepalive interval to " + << config.stun_keepalive_interval_or_default(); + } + + webrtc::BasicRegatheringController::Config regathering_config; + regathering_config.regather_on_failed_networks_interval = + config_.regather_on_failed_networks_interval_or_default(); + regathering_controller_->SetConfig(regathering_config); + + config_.vpn_preference = config.vpn_preference; + allocator_->SetVpnPreference(config_.vpn_preference); + + ice_adapter_->SetIceConfig(config_); + + RTC_DCHECK(ValidateIceConfig(config_).ok()); +} + +void P2PTransportChannel::ParseFieldTrials( + const webrtc::FieldTrialsView* field_trials) { + if (field_trials == nullptr) { + return; + } + + if (field_trials->IsEnabled("WebRTC-ExtraICEPing")) { + RTC_LOG(LS_INFO) << "Set WebRTC-ExtraICEPing: Enabled"; + } + + webrtc::StructParametersParser::Create( + // go/skylift-light + "skip_relay_to_non_relay_connections", + &ice_field_trials_.skip_relay_to_non_relay_connections, + // Limiting pings sent. + "max_outstanding_pings", &ice_field_trials_.max_outstanding_pings, + // Delay initial selection of connection. + "initial_select_dampening", &ice_field_trials_.initial_select_dampening, + // Delay initial selection of connections, that are receiving. + "initial_select_dampening_ping_received", + &ice_field_trials_.initial_select_dampening_ping_received, + // Reply that we support goog ping. + "announce_goog_ping", &ice_field_trials_.announce_goog_ping, + // Use goog ping if remote support it. + "enable_goog_ping", &ice_field_trials_.enable_goog_ping, + // How fast does a RTT sample decay. + "rtt_estimate_halftime_ms", &ice_field_trials_.rtt_estimate_halftime_ms, + // Make sure that nomination reaching ICE controlled asap. + "send_ping_on_switch_ice_controlling", + &ice_field_trials_.send_ping_on_switch_ice_controlling, + // Make sure that nomination reaching ICE controlled asap. + "send_ping_on_selected_ice_controlling", + &ice_field_trials_.send_ping_on_selected_ice_controlling, + // Reply to nomination ASAP. + "send_ping_on_nomination_ice_controlled", + &ice_field_trials_.send_ping_on_nomination_ice_controlled, + // Allow connections to live untouched longer that 30s. + "dead_connection_timeout_ms", + &ice_field_trials_.dead_connection_timeout_ms, + // Stop gathering on strongly connected. + "stop_gather_on_strongly_connected", + &ice_field_trials_.stop_gather_on_strongly_connected) + ->Parse(field_trials->Lookup("WebRTC-IceFieldTrials")); + + if (ice_field_trials_.dead_connection_timeout_ms < 30000) { + RTC_LOG(LS_WARNING) << "dead_connection_timeout_ms set to " + << ice_field_trials_.dead_connection_timeout_ms + << " increasing it to 30000"; + ice_field_trials_.dead_connection_timeout_ms = 30000; + } + + if (ice_field_trials_.skip_relay_to_non_relay_connections) { + RTC_LOG(LS_INFO) << "Set skip_relay_to_non_relay_connections"; + } + + if (ice_field_trials_.max_outstanding_pings.has_value()) { + RTC_LOG(LS_INFO) << "Set max_outstanding_pings: " + << *ice_field_trials_.max_outstanding_pings; + } + + if (ice_field_trials_.initial_select_dampening.has_value()) { + RTC_LOG(LS_INFO) << "Set initial_select_dampening: " + << *ice_field_trials_.initial_select_dampening; + } + + if (ice_field_trials_.initial_select_dampening_ping_received.has_value()) { + RTC_LOG(LS_INFO) + << "Set initial_select_dampening_ping_received: " + << *ice_field_trials_.initial_select_dampening_ping_received; + } + + // DSCP override, allow user to specify (any) int value + // that will be used for tagging all packets. + webrtc::StructParametersParser::Create("override_dscp", + &ice_field_trials_.override_dscp) + ->Parse(field_trials->Lookup("WebRTC-DscpFieldTrial")); + + if (ice_field_trials_.override_dscp) { + SetOption(rtc::Socket::OPT_DSCP, *ice_field_trials_.override_dscp); + } + + std::string field_trial_string = + field_trials->Lookup("WebRTC-SetSocketReceiveBuffer"); + int receive_buffer_size_kb = 0; + sscanf(field_trial_string.c_str(), "Enabled-%d", &receive_buffer_size_kb); + if (receive_buffer_size_kb > 0) { + RTC_LOG(LS_INFO) << "Set WebRTC-SetSocketReceiveBuffer: Enabled and set to " + << receive_buffer_size_kb << "kb"; + SetOption(rtc::Socket::OPT_RCVBUF, receive_buffer_size_kb * 1024); + } + + ice_field_trials_.piggyback_ice_check_acknowledgement = + field_trials->IsEnabled("WebRTC-PiggybackIceCheckAcknowledgement"); + + ice_field_trials_.extra_ice_ping = + field_trials->IsEnabled("WebRTC-ExtraICEPing"); +} + +const IceConfig& P2PTransportChannel::config() const { + RTC_DCHECK_RUN_ON(network_thread_); + return config_; +} + +// TODO(qingsi): Add tests for the config validation starting from +// PeerConnection::SetConfiguration. +// Static +RTCError P2PTransportChannel::ValidateIceConfig(const IceConfig& config) { + if (config.ice_check_interval_strong_connectivity_or_default() < + config.ice_check_interval_weak_connectivity.value_or( + GetWeakPingIntervalInFieldTrial(nullptr))) { + return RTCError(RTCErrorType::INVALID_PARAMETER, + "Ping interval of candidate pairs is shorter when ICE is " + "strongly connected than that when ICE is weakly " + "connected"); + } + + if (config.receiving_timeout_or_default() < + std::max(config.ice_check_interval_strong_connectivity_or_default(), + config.ice_check_min_interval_or_default())) { + return RTCError( + RTCErrorType::INVALID_PARAMETER, + "Receiving timeout is shorter than the minimal ping interval."); + } + + if (config.backup_connection_ping_interval_or_default() < + config.ice_check_interval_strong_connectivity_or_default()) { + return RTCError(RTCErrorType::INVALID_PARAMETER, + "Ping interval of backup candidate pairs is shorter than " + "that of general candidate pairs when ICE is strongly " + "connected"); + } + + if (config.stable_writable_connection_ping_interval_or_default() < + config.ice_check_interval_strong_connectivity_or_default()) { + return RTCError(RTCErrorType::INVALID_PARAMETER, + "Ping interval of stable and writable candidate pairs is " + "shorter than that of general candidate pairs when ICE is " + "strongly connected"); + } + + if (config.ice_unwritable_timeout_or_default() > + config.ice_inactive_timeout_or_default()) { + return RTCError(RTCErrorType::INVALID_PARAMETER, + "The timeout period for the writability state to become " + "UNRELIABLE is longer than that to become TIMEOUT."); + } + + return RTCError::OK(); +} + +const Connection* P2PTransportChannel::selected_connection() const { + RTC_DCHECK_RUN_ON(network_thread_); + return selected_connection_; +} + +int P2PTransportChannel::check_receiving_interval() const { + RTC_DCHECK_RUN_ON(network_thread_); + return std::max(MIN_CHECK_RECEIVING_INTERVAL, + config_.receiving_timeout_or_default() / 10); +} + +void P2PTransportChannel::MaybeStartGathering() { + RTC_DCHECK_RUN_ON(network_thread_); + // TODO(bugs.webrtc.org/14605): ensure tie_breaker_ is set. + if (ice_parameters_.ufrag.empty() || ice_parameters_.pwd.empty()) { + RTC_LOG(LS_ERROR) + << "Cannot gather candidates because ICE parameters are empty" + " ufrag: " + << ice_parameters_.ufrag << " pwd: " << ice_parameters_.pwd; + return; + } + // Start gathering if we never started before, or if an ICE restart occurred. + if (allocator_sessions_.empty() || + IceCredentialsChanged(allocator_sessions_.back()->ice_ufrag(), + allocator_sessions_.back()->ice_pwd(), + ice_parameters_.ufrag, ice_parameters_.pwd)) { + if (gathering_state_ != kIceGatheringGathering) { + gathering_state_ = kIceGatheringGathering; + SignalGatheringState(this); + } + + if (!allocator_sessions_.empty()) { + IceRestartState state; + if (writable()) { + state = IceRestartState::CONNECTED; + } else if (IsGettingPorts()) { + state = IceRestartState::CONNECTING; + } else { + state = IceRestartState::DISCONNECTED; + } + RTC_HISTOGRAM_ENUMERATION("WebRTC.PeerConnection.IceRestartState", + static_cast(state), + static_cast(IceRestartState::MAX_VALUE)); + } + + for (const auto& session : allocator_sessions_) { + if (session->IsStopped()) { + continue; + } + session->StopGettingPorts(); + } + + // Time for a new allocator. + std::unique_ptr pooled_session = + allocator_->TakePooledSession(transport_name(), component(), + ice_parameters_.ufrag, + ice_parameters_.pwd); + if (pooled_session) { + pooled_session->set_ice_tiebreaker(tiebreaker_); + AddAllocatorSession(std::move(pooled_session)); + PortAllocatorSession* raw_pooled_session = + allocator_sessions_.back().get(); + // Process the pooled session's existing candidates/ports, if they exist. + OnCandidatesReady(raw_pooled_session, + raw_pooled_session->ReadyCandidates()); + for (PortInterface* port : allocator_sessions_.back()->ReadyPorts()) { + OnPortReady(raw_pooled_session, port); + } + if (allocator_sessions_.back()->CandidatesAllocationDone()) { + OnCandidatesAllocationDone(raw_pooled_session); + } + } else { + AddAllocatorSession(allocator_->CreateSession( + transport_name(), component(), ice_parameters_.ufrag, + ice_parameters_.pwd)); + allocator_sessions_.back()->set_ice_tiebreaker(tiebreaker_); + allocator_sessions_.back()->StartGettingPorts(); + } + } +} + +// A new port is available, attempt to make connections for it +void P2PTransportChannel::OnPortReady(PortAllocatorSession* session, + PortInterface* port) { + RTC_DCHECK_RUN_ON(network_thread_); + + // Set in-effect options on the new port + for (OptionMap::const_iterator it = options_.begin(); it != options_.end(); + ++it) { + int val = port->SetOption(it->first, it->second); + if (val < 0) { + // Errors are frequent, so use LS_INFO. bugs.webrtc.org/9221 + RTC_LOG(LS_INFO) << port->ToString() << ": SetOption(" << it->first + << ", " << it->second + << ") failed: " << port->GetError(); + } + } + + // Remember the ports and candidates, and signal that candidates are ready. + // The session will handle this, and send an initiate/accept/modify message + // if one is pending. + + port->SetIceRole(ice_role_); + port->SetIceTiebreaker(tiebreaker_); + ports_.push_back(port); + port->SignalUnknownAddress.connect(this, + &P2PTransportChannel::OnUnknownAddress); + port->SubscribePortDestroyed( + [this](PortInterface* port) { OnPortDestroyed(port); }); + + port->SignalRoleConflict.connect(this, &P2PTransportChannel::OnRoleConflict); + port->SignalSentPacket.connect(this, &P2PTransportChannel::OnSentPacket); + + // Attempt to create a connection from this new port to all of the remote + // candidates that we were given so far. + + std::vector::iterator iter; + for (iter = remote_candidates_.begin(); iter != remote_candidates_.end(); + ++iter) { + CreateConnection(port, *iter, iter->origin_port()); + } + + ice_adapter_->OnImmediateSortAndSwitchRequest( + IceSwitchReason::NEW_CONNECTION_FROM_LOCAL_CANDIDATE); +} + +// A new candidate is available, let listeners know +void P2PTransportChannel::OnCandidatesReady( + PortAllocatorSession* session, + const std::vector& candidates) { + RTC_DCHECK_RUN_ON(network_thread_); + for (size_t i = 0; i < candidates.size(); ++i) { + SignalCandidateGathered(this, candidates[i]); + } +} + +void P2PTransportChannel::OnCandidateError( + PortAllocatorSession* session, + const IceCandidateErrorEvent& event) { + RTC_DCHECK(network_thread_ == rtc::Thread::Current()); + SignalCandidateError(this, event); +} + +void P2PTransportChannel::OnCandidatesAllocationDone( + PortAllocatorSession* session) { + RTC_DCHECK_RUN_ON(network_thread_); + if (config_.gather_continually()) { + RTC_LOG(LS_INFO) << "P2PTransportChannel: " << transport_name() + << ", component " << component() + << " gathering complete, but using continual " + "gathering so not changing gathering state."; + return; + } + gathering_state_ = kIceGatheringComplete; + RTC_LOG(LS_INFO) << "P2PTransportChannel: " << transport_name() + << ", component " << component() << " gathering complete"; + SignalGatheringState(this); +} + +// Handle stun packets +void P2PTransportChannel::OnUnknownAddress(PortInterface* port, + const rtc::SocketAddress& address, + ProtocolType proto, + IceMessage* stun_msg, + const std::string& remote_username, + bool port_muxed) { + RTC_DCHECK_RUN_ON(network_thread_); + + // Port has received a valid stun packet from an address that no Connection + // is currently available for. See if we already have a candidate with the + // address. If it isn't we need to create new candidate for it. + // + // TODO(qingsi): There is a caveat of the logic below if we have remote + // candidates with hostnames. We could create a prflx candidate that is + // identical to a host candidate that are currently in the process of name + // resolution. We would not have a duplicate candidate since when adding the + // resolved host candidate, FinishingAddingRemoteCandidate does + // MaybeUpdatePeerReflexiveCandidate, and the prflx candidate would be updated + // to a host candidate. As a result, for a brief moment we would have a prflx + // candidate showing a private IP address, though we do not signal prflx + // candidates to applications and we could obfuscate the IP addresses of prflx + // candidates in P2PTransportChannel::GetStats. The difficulty of preventing + // creating the prflx from the beginning is that we do not have a reliable way + // to claim two candidates are identical without the address information. If + // we always pause the addition of a prflx candidate when there is ongoing + // name resolution and dedup after we have a resolved address, we run into the + // risk of losing/delaying the addition of a non-identical candidate that + // could be the only way to have a connection, if the resolution never + // completes or is significantly delayed. + const Candidate* candidate = nullptr; + for (const Candidate& c : remote_candidates_) { + if (c.username() == remote_username && c.address() == address && + c.protocol() == ProtoToString(proto)) { + candidate = &c; + break; + } + } + + uint32_t remote_generation = 0; + std::string remote_password; + // The STUN binding request may arrive after setRemoteDescription and before + // adding remote candidate, so we need to set the password to the shared + // password and set the generation if the user name matches. + const IceParameters* ice_param = + FindRemoteIceFromUfrag(remote_username, &remote_generation); + // Note: if not found, the remote_generation will still be 0. + if (ice_param != nullptr) { + remote_password = ice_param->pwd; + } + + Candidate remote_candidate; + bool remote_candidate_is_new = (candidate == nullptr); + if (!remote_candidate_is_new) { + remote_candidate = *candidate; + } else { + // Create a new candidate with this address. + // The priority of the candidate is set to the PRIORITY attribute + // from the request. + const StunUInt32Attribute* priority_attr = + stun_msg->GetUInt32(STUN_ATTR_PRIORITY); + if (!priority_attr) { + RTC_LOG(LS_WARNING) << "P2PTransportChannel::OnUnknownAddress - " + "No STUN_ATTR_PRIORITY found in the " + "stun request message"; + port->SendBindingErrorResponse(stun_msg, address, STUN_ERROR_BAD_REQUEST, + STUN_ERROR_REASON_BAD_REQUEST); + return; + } + int remote_candidate_priority = priority_attr->value(); + + uint16_t network_id = 0; + uint16_t network_cost = 0; + const StunUInt32Attribute* network_attr = + stun_msg->GetUInt32(STUN_ATTR_GOOG_NETWORK_INFO); + if (network_attr) { + uint32_t network_info = network_attr->value(); + network_id = static_cast(network_info >> 16); + network_cost = static_cast(network_info); + } + + // RFC 5245 + // If the source transport address of the request does not match any + // existing remote candidates, it represents a new peer reflexive remote + // candidate. + remote_candidate = Candidate( + component(), ProtoToString(proto), address, remote_candidate_priority, + remote_username, remote_password, PRFLX_PORT_TYPE, remote_generation, + "", network_id, network_cost); + if (proto == PROTO_TCP) { + remote_candidate.set_tcptype(TCPTYPE_ACTIVE_STR); + } + + // From RFC 5245, section-7.2.1.3: + // The foundation of the candidate is set to an arbitrary value, different + // from the foundation for all other remote candidates. + remote_candidate.set_foundation( + rtc::ToString(rtc::ComputeCrc32(remote_candidate.id()))); + } + + // RFC5245, the agent constructs a pair whose local candidate is equal to + // the transport address on which the STUN request was received, and a + // remote candidate equal to the source transport address where the + // request came from. + + // There shouldn't be an existing connection with this remote address. + // When ports are muxed, this channel might get multiple unknown address + // signals. In that case if the connection is already exists, we should + // simply ignore the signal otherwise send server error. + if (port->GetConnection(remote_candidate.address())) { + if (port_muxed) { + RTC_LOG(LS_INFO) << "Connection already exists for peer reflexive " + "candidate: " + << remote_candidate.ToSensitiveString(); + return; + } else { + RTC_DCHECK_NOTREACHED(); + port->SendBindingErrorResponse(stun_msg, address, STUN_ERROR_SERVER_ERROR, + STUN_ERROR_REASON_SERVER_ERROR); + return; + } + } + + Connection* connection = + port->CreateConnection(remote_candidate, PortInterface::ORIGIN_THIS_PORT); + if (!connection) { + // This could happen in some scenarios. For example, a TurnPort may have + // had a refresh request timeout, so it won't create connections. + port->SendBindingErrorResponse(stun_msg, address, STUN_ERROR_SERVER_ERROR, + STUN_ERROR_REASON_SERVER_ERROR); + return; + } + + RTC_LOG(LS_INFO) << "Adding connection from " + << (remote_candidate_is_new ? "peer reflexive" + : "resurrected") + << " candidate: " << remote_candidate.ToSensitiveString(); + AddConnection(connection); + connection->HandleStunBindingOrGoogPingRequest(stun_msg); + + // Update the list of connections since we just added another. We do this + // after sending the response since it could (in principle) delete the + // connection in question. + ice_adapter_->OnImmediateSortAndSwitchRequest( + IceSwitchReason::NEW_CONNECTION_FROM_UNKNOWN_REMOTE_ADDRESS); +} + +void P2PTransportChannel::OnCandidateFilterChanged(uint32_t prev_filter, + uint32_t cur_filter) { + RTC_DCHECK_RUN_ON(network_thread_); + if (prev_filter == cur_filter || allocator_session() == nullptr) { + return; + } + if (config_.surface_ice_candidates_on_ice_transport_type_changed) { + allocator_session()->SetCandidateFilter(cur_filter); + } +} + +void P2PTransportChannel::OnRoleConflict(PortInterface* port) { + SignalRoleConflict(this); // STUN ping will be sent when SetRole is called + // from Transport. +} + +const IceParameters* P2PTransportChannel::FindRemoteIceFromUfrag( + absl::string_view ufrag, + uint32_t* generation) { + RTC_DCHECK_RUN_ON(network_thread_); + const auto& params = remote_ice_parameters_; + auto it = std::find_if( + params.rbegin(), params.rend(), + [ufrag](const IceParameters& param) { return param.ufrag == ufrag; }); + if (it == params.rend()) { + // Not found. + return nullptr; + } + *generation = params.rend() - it - 1; + return &(*it); +} + +void P2PTransportChannel::OnNominated(Connection* conn) { + RTC_DCHECK_RUN_ON(network_thread_); + RTC_DCHECK(ice_role_ == ICEROLE_CONTROLLED); + + if (selected_connection_ == conn) { + return; + } + + if (ice_field_trials_.send_ping_on_nomination_ice_controlled && + conn != nullptr) { + SendPingRequestInternal(conn); + } + + // TODO(qingsi): RequestSortAndStateUpdate will eventually call + // MaybeSwitchSelectedConnection again. Rewrite this logic. + if (ice_adapter_->OnImmediateSwitchRequest( + IceSwitchReason::NOMINATION_ON_CONTROLLED_SIDE, conn)) { + // Now that we have selected a connection, it is time to prune other + // connections and update the read/write state of the channel. + ice_adapter_->OnSortAndSwitchRequest( + IceSwitchReason::NOMINATION_ON_CONTROLLED_SIDE); + } else { + RTC_LOG(LS_INFO) + << "Not switching the selected connection on controlled side yet: " + << conn->ToString(); + } +} + +void P2PTransportChannel::ResolveHostnameCandidate(const Candidate& candidate) { + RTC_DCHECK_RUN_ON(network_thread_); + if (!async_dns_resolver_factory_) { + RTC_LOG(LS_WARNING) << "Dropping ICE candidate with hostname address " + "(no AsyncResolverFactory)"; + return; + } + + auto resolver = async_dns_resolver_factory_->Create(); + auto resptr = resolver.get(); + resolvers_.emplace_back(candidate, std::move(resolver)); + resptr->Start(candidate.address(), + [this, resptr]() { OnCandidateResolved(resptr); }); + RTC_LOG(LS_INFO) << "Asynchronously resolving ICE candidate hostname " + << candidate.address().HostAsSensitiveURIString(); +} + +void P2PTransportChannel::AddRemoteCandidate(const Candidate& candidate) { + RTC_DCHECK_RUN_ON(network_thread_); + + uint32_t generation = GetRemoteCandidateGeneration(candidate); + // If a remote candidate with a previous generation arrives, drop it. + if (generation < remote_ice_generation()) { + RTC_LOG(LS_WARNING) << "Dropping a remote candidate because its ufrag " + << candidate.username() + << " indicates it was for a previous generation."; + return; + } + + Candidate new_remote_candidate(candidate); + new_remote_candidate.set_generation(generation); + // ICE candidates don't need to have username and password set, but + // the code below this (specifically, ConnectionRequest::Prepare in + // port.cc) uses the remote candidates's username. So, we set it + // here. + if (remote_ice()) { + if (candidate.username().empty()) { + new_remote_candidate.set_username(remote_ice()->ufrag); + } + if (new_remote_candidate.username() == remote_ice()->ufrag) { + if (candidate.password().empty()) { + new_remote_candidate.set_password(remote_ice()->pwd); + } + } else { + // The candidate belongs to the next generation. Its pwd will be set + // when the new remote ICE credentials arrive. + RTC_LOG(LS_WARNING) + << "A remote candidate arrives with an unknown ufrag: " + << candidate.username(); + } + } + + if (new_remote_candidate.address().IsUnresolvedIP()) { + // Don't do DNS lookups if the IceTransportPolicy is "none" or "relay". + bool sharing_host = ((allocator_->candidate_filter() & CF_HOST) != 0); + bool sharing_stun = ((allocator_->candidate_filter() & CF_REFLEXIVE) != 0); + if (sharing_host || sharing_stun) { + ResolveHostnameCandidate(new_remote_candidate); + } + return; + } + + FinishAddingRemoteCandidate(new_remote_candidate); +} + +P2PTransportChannel::CandidateAndResolver::CandidateAndResolver( + const Candidate& candidate, + std::unique_ptr&& resolver) + : candidate_(candidate), resolver_(std::move(resolver)) {} + +P2PTransportChannel::CandidateAndResolver::~CandidateAndResolver() {} + +void P2PTransportChannel::OnCandidateResolved( + webrtc::AsyncDnsResolverInterface* resolver) { + RTC_DCHECK_RUN_ON(network_thread_); + auto p = + absl::c_find_if(resolvers_, [resolver](const CandidateAndResolver& cr) { + return cr.resolver_.get() == resolver; + }); + if (p == resolvers_.end()) { + RTC_LOG(LS_ERROR) << "Unexpected AsyncDnsResolver return"; + RTC_DCHECK_NOTREACHED(); + return; + } + Candidate candidate = p->candidate_; + AddRemoteCandidateWithResult(candidate, resolver->result()); + // Now we can delete the resolver. + // TODO(bugs.webrtc.org/12651): Replace the stuff below with + // resolvers_.erase(p); + std::unique_ptr to_delete = + std::move(p->resolver_); + // Delay the actual deletion of the resolver until the lambda executes. + network_thread_->PostTask([to_delete = std::move(to_delete)] {}); + resolvers_.erase(p); +} + +void P2PTransportChannel::AddRemoteCandidateWithResult( + Candidate candidate, + const webrtc::AsyncDnsResolverResult& result) { + RTC_DCHECK_RUN_ON(network_thread_); + if (result.GetError()) { + RTC_LOG(LS_WARNING) << "Failed to resolve ICE candidate hostname " + << candidate.address().HostAsSensitiveURIString() + << " with error " << result.GetError(); + return; + } + + rtc::SocketAddress resolved_address; + // Prefer IPv6 to IPv4 if we have it (see RFC 5245 Section 15.1). + // TODO(zstein): This won't work if we only have IPv4 locally but receive an + // AAAA DNS record. + bool have_address = result.GetResolvedAddress(AF_INET6, &resolved_address) || + result.GetResolvedAddress(AF_INET, &resolved_address); + if (!have_address) { + RTC_LOG(LS_INFO) << "ICE candidate hostname " + << candidate.address().HostAsSensitiveURIString() + << " could not be resolved"; + return; + } + + RTC_LOG(LS_INFO) << "Resolved ICE candidate hostname " + << candidate.address().HostAsSensitiveURIString() << " to " + << resolved_address.ipaddr().ToSensitiveString(); + candidate.set_address(resolved_address); + FinishAddingRemoteCandidate(candidate); +} + +void P2PTransportChannel::FinishAddingRemoteCandidate( + const Candidate& new_remote_candidate) { + 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()) { + conn->MaybeUpdatePeerReflexiveCandidate(new_remote_candidate); + } + + // Create connections to this remote candidate. + CreateConnections(new_remote_candidate, NULL); + + // Resort the connections list, which may have new elements. + ice_adapter_->OnImmediateSortAndSwitchRequest( + IceSwitchReason::NEW_CONNECTION_FROM_REMOTE_CANDIDATE); +} + +void P2PTransportChannel::RemoveRemoteCandidate( + const Candidate& cand_to_remove) { + RTC_DCHECK_RUN_ON(network_thread_); + auto iter = + std::remove_if(remote_candidates_.begin(), remote_candidates_.end(), + [cand_to_remove](const Candidate& candidate) { + return cand_to_remove.MatchesForRemoval(candidate); + }); + if (iter != remote_candidates_.end()) { + RTC_LOG(LS_VERBOSE) << "Removed remote candidate " + << cand_to_remove.ToSensitiveString(); + remote_candidates_.erase(iter, remote_candidates_.end()); + } +} + +void P2PTransportChannel::RemoveAllRemoteCandidates() { + RTC_DCHECK_RUN_ON(network_thread_); + remote_candidates_.clear(); +} + +// Creates connections from all of the ports that we care about to the given +// remote candidate. The return value is true if we created a connection from +// the origin port. +bool P2PTransportChannel::CreateConnections(const Candidate& remote_candidate, + PortInterface* origin_port) { + RTC_DCHECK_RUN_ON(network_thread_); + + // If we've already seen the new remote candidate (in the current candidate + // generation), then we shouldn't try creating connections for it. + // We either already have a connection for it, or we previously created one + // and then later pruned it. If we don't return, the channel will again + // re-create any connections that were previously pruned, which will then + // immediately be re-pruned, churning the network for no purpose. + // This only applies to candidates received over signaling (i.e. origin_port + // is NULL). + if (!origin_port && IsDuplicateRemoteCandidate(remote_candidate)) { + // return true to indicate success, without creating any new connections. + return true; + } + + // Add a new connection for this candidate to every port that allows such a + // connection (i.e., if they have compatible protocols) and that does not + // already have a connection to an equivalent candidate. We must be careful + // to make sure that the origin port is included, even if it was pruned, + // since that may be the only port that can create this connection. + bool created = false; + std::vector::reverse_iterator it; + for (it = ports_.rbegin(); it != ports_.rend(); ++it) { + if (CreateConnection(*it, remote_candidate, origin_port)) { + if (*it == origin_port) + created = true; + } + } + + if ((origin_port != NULL) && !absl::c_linear_search(ports_, origin_port)) { + if (CreateConnection(origin_port, remote_candidate, origin_port)) + created = true; + } + + // Remember this remote candidate so that we can add it to future ports. + RememberRemoteCandidate(remote_candidate, origin_port); + + return created; +} + +// Setup a connection object for the local and remote candidate combination. +// And then listen to connection object for changes. +bool P2PTransportChannel::CreateConnection(PortInterface* port, + const Candidate& remote_candidate, + PortInterface* origin_port) { + RTC_DCHECK_RUN_ON(network_thread_); + if (!port->SupportsProtocol(remote_candidate.protocol())) { + return false; + } + + if (ice_field_trials_.skip_relay_to_non_relay_connections) { + if ((port->Type() != remote_candidate.type()) && + (port->Type() == RELAY_PORT_TYPE || + remote_candidate.type() == RELAY_PORT_TYPE)) { + RTC_LOG(LS_INFO) << ToString() << ": skip creating connection " + << port->Type() << " to " << remote_candidate.type(); + return false; + } + } + + // Look for an existing connection with this remote address. If one is not + // found or it is found but the existing remote candidate has an older + // generation, then we can create a new connection for this address. + Connection* connection = port->GetConnection(remote_candidate.address()); + if (connection == nullptr || connection->remote_candidate().generation() < + remote_candidate.generation()) { + // Don't create a connection if this is a candidate we received in a + // message and we are not allowed to make outgoing connections. + PortInterface::CandidateOrigin origin = GetOrigin(port, origin_port); + if (origin == PortInterface::ORIGIN_MESSAGE && incoming_only_) { + return false; + } + Connection* connection = port->CreateConnection(remote_candidate, origin); + if (!connection) { + return false; + } + AddConnection(connection); + RTC_LOG(LS_INFO) << ToString() + << ": Created connection with origin: " << origin + << ", total: " << connections().size(); + return true; + } + + // No new connection was created. + // It is not legal to try to change any of the parameters of an existing + // connection; however, the other side can send a duplicate candidate. + if (!remote_candidate.IsEquivalent(connection->remote_candidate())) { + RTC_LOG(LS_INFO) << "Attempt to change a remote candidate." + " Existing remote candidate: " + << connection->remote_candidate().ToSensitiveString() + << "New remote candidate: " + << remote_candidate.ToSensitiveString(); + } + return false; +} + +bool P2PTransportChannel::FindConnection(const Connection* connection) const { + RTC_DCHECK_RUN_ON(network_thread_); + return absl::c_linear_search(connections(), connection); +} + +uint32_t P2PTransportChannel::GetRemoteCandidateGeneration( + const Candidate& candidate) { + RTC_DCHECK_RUN_ON(network_thread_); + // If the candidate has a ufrag, use it to find the generation. + if (!candidate.username().empty()) { + uint32_t generation = 0; + if (!FindRemoteIceFromUfrag(candidate.username(), &generation)) { + // If the ufrag is not found, assume the next/future generation. + generation = static_cast(remote_ice_parameters_.size()); + } + return generation; + } + // If candidate generation is set, use that. + if (candidate.generation() > 0) { + return candidate.generation(); + } + // Otherwise, assume the generation from remote ice parameters. + return remote_ice_generation(); +} + +// Check if remote candidate is already cached. +bool P2PTransportChannel::IsDuplicateRemoteCandidate( + const Candidate& candidate) { + RTC_DCHECK_RUN_ON(network_thread_); + for (size_t i = 0; i < remote_candidates_.size(); ++i) { + if (remote_candidates_[i].IsEquivalent(candidate)) { + return true; + } + } + return false; +} + +// Maintain our remote candidate list, adding this new remote one. +void P2PTransportChannel::RememberRemoteCandidate( + const Candidate& remote_candidate, + PortInterface* origin_port) { + RTC_DCHECK_RUN_ON(network_thread_); + // Remove any candidates whose generation is older than this one. The + // presence of a new generation indicates that the old ones are not useful. + size_t i = 0; + while (i < remote_candidates_.size()) { + if (remote_candidates_[i].generation() < remote_candidate.generation()) { + RTC_LOG(LS_INFO) << "Pruning candidate from old generation: " + << remote_candidates_[i].address().ToSensitiveString(); + remote_candidates_.erase(remote_candidates_.begin() + i); + } else { + i += 1; + } + } + + // Make sure this candidate is not a duplicate. + if (IsDuplicateRemoteCandidate(remote_candidate)) { + RTC_LOG(LS_INFO) << "Duplicate candidate: " + << remote_candidate.ToSensitiveString(); + return; + } + + // Try this candidate for all future ports. + remote_candidates_.push_back(RemoteCandidate(remote_candidate, origin_port)); +} + +// Set options on ourselves is simply setting options on all of our available +// port objects. +int P2PTransportChannel::SetOption(rtc::Socket::Option opt, int value) { + RTC_DCHECK_RUN_ON(network_thread_); + if (ice_field_trials_.override_dscp && opt == rtc::Socket::OPT_DSCP) { + value = *ice_field_trials_.override_dscp; + } + + OptionMap::iterator it = options_.find(opt); + if (it == options_.end()) { + options_.insert(std::make_pair(opt, value)); + } else if (it->second == value) { + return 0; + } else { + it->second = value; + } + + for (PortInterface* port : ports_) { + int val = port->SetOption(opt, value); + if (val < 0) { + // Because this also occurs deferred, probably no point in reporting an + // error + RTC_LOG(LS_WARNING) << "SetOption(" << opt << ", " << value + << ") failed: " << port->GetError(); + } + } + return 0; +} + +bool P2PTransportChannel::GetOption(rtc::Socket::Option opt, int* value) { + RTC_DCHECK_RUN_ON(network_thread_); + + const auto& found = options_.find(opt); + if (found == options_.end()) { + return false; + } + *value = found->second; + return true; +} + +int P2PTransportChannel::GetError() { + RTC_DCHECK_RUN_ON(network_thread_); + return error_; +} + +// Send data to the other side, using our selected connection. +int P2PTransportChannel::SendPacket(const char* data, + size_t len, + const rtc::PacketOptions& options, + int flags) { + RTC_DCHECK_RUN_ON(network_thread_); + if (flags != 0) { + error_ = EINVAL; + return -1; + } + // If we don't think the connection is working yet, return ENOTCONN + // instead of sending a packet that will probably be dropped. + if (!ReadyToSend(selected_connection_)) { + error_ = ENOTCONN; + return -1; + } + + packets_sent_++; + last_sent_packet_id_ = options.packet_id; + rtc::PacketOptions modified_options(options); + modified_options.info_signaled_after_sent.packet_type = + rtc::PacketType::kData; + int sent = selected_connection_->Send(data, len, modified_options); + if (sent <= 0) { + RTC_DCHECK(sent < 0); + error_ = selected_connection_->GetError(); + return sent; + } + + bytes_sent_ += sent; + return sent; +} + +bool P2PTransportChannel::GetStats(IceTransportStats* ice_transport_stats) { + RTC_DCHECK_RUN_ON(network_thread_); + // Gather candidate and candidate pair stats. + ice_transport_stats->candidate_stats_list.clear(); + ice_transport_stats->connection_infos.clear(); + + if (!allocator_sessions_.empty()) { + allocator_session()->GetCandidateStatsFromReadyPorts( + &ice_transport_stats->candidate_stats_list); + } + + // TODO(qingsi): Remove naming inconsistency for candidate pair/connection. + for (Connection* connection : connections()) { + ConnectionInfo stats = connection->stats(); + stats.local_candidate = SanitizeLocalCandidate(stats.local_candidate); + stats.remote_candidate = SanitizeRemoteCandidate(stats.remote_candidate); + stats.best_connection = (selected_connection_ == connection); + ice_transport_stats->connection_infos.push_back(std::move(stats)); + } + + ice_transport_stats->selected_candidate_pair_changes = + selected_candidate_pair_changes_; + + ice_transport_stats->bytes_sent = bytes_sent_; + ice_transport_stats->bytes_received = bytes_received_; + ice_transport_stats->packets_sent = packets_sent_; + ice_transport_stats->packets_received = packets_received_; + + ice_transport_stats->ice_role = GetIceRole(); + ice_transport_stats->ice_local_username_fragment = ice_parameters_.ufrag; + ice_transport_stats->ice_state = ComputeIceTransportState(); + + return true; +} + +absl::optional P2PTransportChannel::network_route() const { + RTC_DCHECK_RUN_ON(network_thread_); + return network_route_; +} + +rtc::DiffServCodePoint P2PTransportChannel::DefaultDscpValue() const { + RTC_DCHECK_RUN_ON(network_thread_); + OptionMap::const_iterator it = options_.find(rtc::Socket::OPT_DSCP); + if (it == options_.end()) { + return rtc::DSCP_NO_CHANGE; + } + return static_cast(it->second); +} + +rtc::ArrayView P2PTransportChannel::connections() const { + RTC_DCHECK_RUN_ON(network_thread_); + return ice_adapter_->LegacyConnections(); +} + +void P2PTransportChannel::RemoveConnectionForTest(Connection* connection) { + RTC_DCHECK_RUN_ON(network_thread_); + RTC_DCHECK(FindConnection(connection)); + connection->SignalDestroyed.disconnect(this); + RemoveConnection(connection); + RTC_DCHECK(!FindConnection(connection)); + if (selected_connection_ == connection) + selected_connection_ = nullptr; + connection->Destroy(); +} + +// Monitor connection states. +void P2PTransportChannel::UpdateConnectionStates() { + RTC_DCHECK_RUN_ON(network_thread_); + int64_t now = rtc::TimeMillis(); + + // We need to copy the list of connections since some may delete themselves + // when we call UpdateState. + // NOTE: We copy the connections() vector in case `UpdateState` triggers the + // Connection to be destroyed (which will cause a callback that alters + // the connections() vector). + std::vector copy(connections().begin(), connections().end()); + for (Connection* c : copy) { + c->UpdateState(now); + } +} + +// Prepare for best candidate sorting. +// TODO(bugs.webrtc.org/14367) remove once refactor lands. +void P2PTransportChannel::RequestSortAndStateUpdate( + IceSwitchReason reason_to_sort) { + RTC_DCHECK_RUN_ON(network_thread_); + if (!sort_dirty_) { + network_thread_->PostTask( + SafeTask(task_safety_.flag(), [this, reason_to_sort]() { + SortConnectionsAndUpdateState(reason_to_sort); + })); + sort_dirty_ = true; + } +} + +// TODO(bugs.webrtc.org/14367) remove once refactor lands. +void P2PTransportChannel::MaybeStartPinging() { + RTC_DCHECK_RUN_ON(network_thread_); + if (started_pinging_) { + return; + } + + if (ice_adapter_->LegacyHasPingableConnection()) { + RTC_LOG(LS_INFO) << ToString() + << ": Have a pingable connection for the first time; " + "starting to ping."; + network_thread_->PostTask( + SafeTask(task_safety_.flag(), [this]() { CheckAndPing(); })); + regathering_controller_->Start(); + started_pinging_ = true; + } +} + +void P2PTransportChannel::OnStartedPinging() { + RTC_DCHECK_RUN_ON(network_thread_); + RTC_LOG(LS_INFO) << ToString() + << ": Have a pingable connection for the first time; " + "starting to ping."; + regathering_controller_->Start(); +} + +bool P2PTransportChannel::IsPortPruned(const Port* port) const { + RTC_DCHECK_RUN_ON(network_thread_); + return !absl::c_linear_search(ports_, port); +} + +bool P2PTransportChannel::IsRemoteCandidatePruned(const Candidate& cand) const { + RTC_DCHECK_RUN_ON(network_thread_); + return !absl::c_linear_search(remote_candidates_, cand); +} + +bool P2PTransportChannel::PresumedWritable(const Connection* conn) const { + RTC_DCHECK_RUN_ON(network_thread_); + 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)); +} + +// Sort the available connections to find the best one. We also monitor +// the number of available connections and the current state. +// TODO(bugs.webrtc.org/14367) remove once refactor lands. +void P2PTransportChannel::SortConnectionsAndUpdateState( + IceSwitchReason reason_to_sort) { + RTC_DCHECK_RUN_ON(network_thread_); + + // Make sure the connection states are up-to-date since this affects how they + // will be sorted. + UpdateConnectionStates(); + + // Any changes after this point will require a re-sort. + sort_dirty_ = false; + + // 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( + reason_to_sort, + ice_adapter_->LegacySortAndSwitchConnection(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 + // 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 (AllowedToPruneConnections()) { + PruneConnections(); + } + + // Check if all connections are timedout. + bool all_connections_timedout = true; + for (const Connection* conn : connections()) { + if (conn->write_state() != Connection::STATE_WRITE_TIMEOUT) { + all_connections_timedout = false; + break; + } + } + + // Now update the writable state of the channel with the information we have + // so far. + if (all_connections_timedout) { + HandleAllTimedOut(); + } + + // Update the state of this channel. + UpdateTransportState(); + + // 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 P2PTransportChannel::UpdateState() { + // Check if all connections are timedout. + bool all_connections_timedout = true; + for (const Connection* conn : connections()) { + if (conn->write_state() != Connection::STATE_WRITE_TIMEOUT) { + all_connections_timedout = false; + break; + } + } + + // Now update the writable state of the channel with the information we have + // so far. + if (all_connections_timedout) { + HandleAllTimedOut(); + } + + // Update the state of this channel. + UpdateTransportState(); +} + +bool P2PTransportChannel::AllowedToPruneConnections() const { + RTC_DCHECK_RUN_ON(network_thread_); + return ice_role_ == ICEROLE_CONTROLLING || + (selected_connection_ && selected_connection_->nominated()); +} + +// TODO(bugs.webrtc.org/14367) remove once refactor lands. +void P2PTransportChannel::PruneConnections() { + RTC_DCHECK_RUN_ON(network_thread_); + std::vector connections_to_prune = + ice_adapter_->LegacyPruneConnections(); + PruneConnections(connections_to_prune); +} + +bool P2PTransportChannel::PruneConnections( + rtc::ArrayView connections) { + RTC_DCHECK_RUN_ON(network_thread_); + if (!AllowedToPruneConnections()) { + RTC_LOG(LS_WARNING) << "Not allowed to prune connections"; + return false; + } + for (const Connection* conn : connections) { + FromIceController(conn)->Prune(); + } + return true; +} + +rtc::NetworkRoute P2PTransportChannel::ConfigureNetworkRoute( + const Connection* conn) { + RTC_DCHECK_RUN_ON(network_thread_); + return { + .connected = ReadyToSend(conn), + .local = CreateRouteEndpointFromCandidate( + /* local= */ true, conn->local_candidate(), + /* uses_turn= */ + conn->port()->Type() == RELAY_PORT_TYPE), + .remote = CreateRouteEndpointFromCandidate( + /* local= */ false, conn->remote_candidate(), + /* uses_turn= */ conn->remote_candidate().type() == RELAY_PORT_TYPE), + .last_sent_packet_id = last_sent_packet_id_, + .packet_overhead = + conn->local_candidate().address().ipaddr().overhead() + + GetProtocolOverhead(conn->local_candidate().protocol())}; +} + +void P2PTransportChannel::SwitchSelectedConnection( + const Connection* new_connection, + IceSwitchReason reason) { + RTC_DCHECK_RUN_ON(network_thread_); + SwitchSelectedConnectionInternal(FromIceController(new_connection), reason); +} + +// Change the selected connection, and let listeners know. +void P2PTransportChannel::SwitchSelectedConnectionInternal( + Connection* conn, + IceSwitchReason reason) { + RTC_DCHECK_RUN_ON(network_thread_); + // Note: if conn is NULL, the previous `selected_connection_` has been + // destroyed, so don't use it. + Connection* old_selected_connection = selected_connection_; + selected_connection_ = conn; + LogCandidatePairConfig(conn, webrtc::IceCandidatePairConfigType::kSelected); + network_route_.reset(); + if (old_selected_connection) { + old_selected_connection->set_selected(false); + } + if (selected_connection_) { + ++nomination_; + selected_connection_->set_selected(true); + if (old_selected_connection) { + RTC_LOG(LS_INFO) << ToString() << ": Previous selected connection: " + << old_selected_connection->ToString(); + } + RTC_LOG(LS_INFO) << ToString() << ": New selected connection: " + << selected_connection_->ToString(); + SignalRouteChange(this, selected_connection_->remote_candidate()); + // This is a temporary, but safe fix to webrtc issue 5705. + // TODO(honghaiz): Make all ENOTCONN error routed through the transport + // channel so that it knows whether the media channel is allowed to + // send; then it will only signal ready-to-send if the media channel + // has been disallowed to send. + if (selected_connection_->writable() || + PresumedWritable(selected_connection_)) { + SignalReadyToSend(this); + } + + network_route_.emplace(ConfigureNetworkRoute(selected_connection_)); + } else { + RTC_LOG(LS_INFO) << ToString() << ": No selected connection"; + } + + if (conn != nullptr && ice_role_ == ICEROLE_CONTROLLING && + ((ice_field_trials_.send_ping_on_switch_ice_controlling && + old_selected_connection != nullptr) || + ice_field_trials_.send_ping_on_selected_ice_controlling)) { + SendPingRequestInternal(conn); + } + + SignalNetworkRouteChanged(network_route_); + + // Create event for candidate pair change. + if (selected_connection_) { + CandidatePairChangeEvent pair_change; + pair_change.reason = IceSwitchReasonToString(reason); + pair_change.selected_candidate_pair = *GetSelectedCandidatePair(); + pair_change.last_data_received_ms = + selected_connection_->last_data_received(); + + if (old_selected_connection) { + pair_change.estimated_disconnected_time_ms = + ComputeEstimatedDisconnectedTimeMs(rtc::TimeMillis(), + old_selected_connection); + } else { + pair_change.estimated_disconnected_time_ms = 0; + } + + SignalCandidatePairChanged(pair_change); + } + + ++selected_candidate_pair_changes_; + + ice_adapter_->OnConnectionSwitched(selected_connection_); +} + +int64_t P2PTransportChannel::ComputeEstimatedDisconnectedTimeMs( + int64_t now_ms, + Connection* old_connection) { + // TODO(jonaso): nicer keeps estimate of how frequently data _should_ be + // received, this could be used to give better estimate (if needed). + int64_t last_data_or_old_ping = + std::max(old_connection->last_received(), last_data_received_ms_); + return (now_ms - last_data_or_old_ping); +} + +// Warning: UpdateTransportState should eventually be called whenever a +// connection is added, deleted, or the write state of any connection changes so +// that the transport controller will get the up-to-date channel state. However +// it should not be called too often; in the case that multiple connection +// states change, it should be called after all the connection states have +// changed. For example, we call this at the end of +// SortConnectionsAndUpdateState. +void P2PTransportChannel::UpdateTransportState() { + RTC_DCHECK_RUN_ON(network_thread_); + // If our selected connection is "presumed writable" (TURN-TURN with no + // CreatePermission required), act like we're already writable to the upper + // layers, so they can start media quicker. + bool writable = + selected_connection_ && (selected_connection_->writable() || + PresumedWritable(selected_connection_)); + SetWritable(writable); + + bool receiving = false; + for (const Connection* connection : connections()) { + if (connection->receiving()) { + receiving = true; + break; + } + } + SetReceiving(receiving); + + IceTransportState state = ComputeState(); + webrtc::IceTransportState current_standardized_state = + ComputeIceTransportState(); + + if (state_ != state) { + RTC_LOG(LS_INFO) << ToString() << ": Transport channel state changed from " + << static_cast(state_) << " to " + << static_cast(state); + // Check that the requested transition is allowed. Note that + // P2PTransportChannel does not (yet) implement a direct mapping of the + // ICE states from the standard; the difference is covered by + // TransportController and PeerConnection. + switch (state_) { + case IceTransportState::STATE_INIT: + // TODO(deadbeef): Once we implement end-of-candidates signaling, + // we shouldn't go from INIT to COMPLETED. + RTC_DCHECK(state == IceTransportState::STATE_CONNECTING || + state == IceTransportState::STATE_COMPLETED || + state == IceTransportState::STATE_FAILED); + break; + case IceTransportState::STATE_CONNECTING: + RTC_DCHECK(state == IceTransportState::STATE_COMPLETED || + state == IceTransportState::STATE_FAILED); + break; + case IceTransportState::STATE_COMPLETED: + // TODO(deadbeef): Once we implement end-of-candidates signaling, + // we shouldn't go from COMPLETED to CONNECTING. + // Though we *can* go from COMPlETED to FAILED, if consent expires. + RTC_DCHECK(state == IceTransportState::STATE_CONNECTING || + state == IceTransportState::STATE_FAILED); + break; + case IceTransportState::STATE_FAILED: + // TODO(deadbeef): Once we implement end-of-candidates signaling, + // we shouldn't go from FAILED to CONNECTING or COMPLETED. + RTC_DCHECK(state == IceTransportState::STATE_CONNECTING || + state == IceTransportState::STATE_COMPLETED); + break; + default: + RTC_DCHECK_NOTREACHED(); + break; + } + state_ = state; + SignalStateChanged(this); + } + + if (standardized_state_ != current_standardized_state) { + standardized_state_ = current_standardized_state; + SignalIceTransportStateChanged(this); + } +} + +void P2PTransportChannel::MaybeStopPortAllocatorSessions() { + RTC_DCHECK_RUN_ON(network_thread_); + if (!IsGettingPorts()) { + return; + } + + for (const auto& session : allocator_sessions_) { + if (session->IsStopped()) { + continue; + } + // If gathering continually, keep the last session running so that + // it can gather candidates if the networks change. + if (config_.gather_continually() && session == allocator_sessions_.back()) { + session->ClearGettingPorts(); + } else { + session->StopGettingPorts(); + } + } +} + +void P2PTransportChannel::OnSelectedConnectionDestroyed() { + RTC_DCHECK_RUN_ON(network_thread_); + RTC_LOG(LS_INFO) << "Selected connection destroyed. Will choose a new one."; + IceSwitchReason reason = IceSwitchReason::SELECTED_CONNECTION_DESTROYED; + SwitchSelectedConnectionInternal(nullptr, reason); + ice_adapter_->OnSortAndSwitchRequest(reason); +} + +// If all connections timed out, delete them all. +void P2PTransportChannel::HandleAllTimedOut() { + RTC_DCHECK_RUN_ON(network_thread_); + bool update_selected_connection = false; + std::vector copy(connections().begin(), connections().end()); + for (Connection* connection : copy) { + if (selected_connection_ == connection) { + selected_connection_ = nullptr; + update_selected_connection = true; + } + connection->SignalDestroyed.disconnect(this); + RemoveConnection(connection); + connection->Destroy(); + } + + if (update_selected_connection) + OnSelectedConnectionDestroyed(); +} + +bool P2PTransportChannel::ReadyToSend(const Connection* connection) const { + RTC_DCHECK_RUN_ON(network_thread_); + // 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)); +} + +// Handle queued up check-and-ping request +// TODO(bugs.webrtc.org/14367) remove once refactor lands. +void P2PTransportChannel::CheckAndPing() { + RTC_DCHECK_RUN_ON(network_thread_); + // Make sure the states of the connections are up-to-date (since this + // affects which ones are pingable). + UpdateConnectionStates(); + + auto result = ice_adapter_->LegacySelectConnectionToPing(last_ping_sent_ms_); + TimeDelta delay = TimeDelta::Millis(result.recheck_delay_ms); + + if (result.connection.value_or(nullptr)) { + SendPingRequest(result.connection.value()); + } + + network_thread_->PostDelayedTask( + SafeTask(task_safety_.flag(), [this]() { CheckAndPing(); }), delay); +} + +// This method is only for unit testing. +Connection* P2PTransportChannel::FindNextPingableConnection() { + RTC_DCHECK_RUN_ON(network_thread_); + const Connection* conn = ice_adapter_->FindNextPingableConnection(); + if (conn) { + return FromIceController(conn); + } else { + return nullptr; + } +} + +int64_t P2PTransportChannel::GetLastPingSentMs() const { + RTC_DCHECK_RUN_ON(network_thread_); + return last_ping_sent_ms_; +} + +void P2PTransportChannel::SendPingRequest(const Connection* connection) { + RTC_DCHECK_RUN_ON(network_thread_); + SendPingRequestInternal(FromIceController(connection)); +} + +void P2PTransportChannel::SendPingRequestInternal(Connection* connection) { + RTC_DCHECK_RUN_ON(network_thread_); + PingConnection(connection); + MarkConnectionPinged(connection); +} + +// 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_); + ice_adapter_->OnConnectionPinged(conn); +} + +// Apart from sending ping from `conn` this method also updates +// `use_candidate_attr` and `nomination` flags. One of the flags is set to +// nominate `conn` if this channel is in CONTROLLING. +void P2PTransportChannel::PingConnection(Connection* conn) { + RTC_DCHECK_RUN_ON(network_thread_); + bool use_candidate_attr = false; + uint32_t nomination = 0; + if (ice_role_ == ICEROLE_CONTROLLING) { + bool renomination_supported = ice_parameters_.renomination && + !remote_ice_parameters_.empty() && + remote_ice_parameters_.back().renomination; + if (renomination_supported) { + nomination = GetNominationAttr(conn); + } else { + use_candidate_attr = GetUseCandidateAttr(conn); + } + } + conn->set_nomination(nomination); + conn->set_use_candidate_attr(use_candidate_attr); + last_ping_sent_ms_ = rtc::TimeMillis(); + conn->Ping(last_ping_sent_ms_); +} + +uint32_t P2PTransportChannel::GetNominationAttr(Connection* conn) const { + RTC_DCHECK_RUN_ON(network_thread_); + return (conn == selected_connection_) ? nomination_ : 0; +} + +// Nominate a connection based on the NominationMode. +bool P2PTransportChannel::GetUseCandidateAttr(Connection* conn) const { + RTC_DCHECK_RUN_ON(network_thread_); + return ice_adapter_->GetUseCandidateAttribute( + conn, config_.default_nomination_mode, remote_ice_mode_); +} + +// When a connection's state changes, we need to figure out who to use as +// the selected connection again. It could have become usable, or become +// unusable. +void P2PTransportChannel::OnConnectionStateChange(Connection* connection) { + RTC_DCHECK_RUN_ON(network_thread_); + + // May stop the allocator session when at least one connection becomes + // strongly connected after starting to get ports and the local candidate of + // the connection is at the latest generation. It is not enough to check + // that the connection becomes weakly connected because the connection may + // be changing from (writable, receiving) to (writable, not receiving). + if (ice_field_trials_.stop_gather_on_strongly_connected) { + bool strongly_connected = !connection->weak(); + bool latest_generation = connection->local_candidate().generation() >= + allocator_session()->generation(); + if (strongly_connected && latest_generation) { + MaybeStopPortAllocatorSessions(); + } + } + // We have to unroll the stack before doing this because we may be changing + // the state of connections while sorting. + ice_adapter_->OnSortAndSwitchRequest( + IceSwitchReason::CONNECT_STATE_CHANGE); // "candidate pair state + // changed"); +} + +// When a connection is removed, edit it out, and then update our best +// connection. +void P2PTransportChannel::OnConnectionDestroyed(Connection* connection) { + RTC_DCHECK_RUN_ON(network_thread_); + + // Note: the previous selected_connection_ may be destroyed by now, so don't + // use it. + + // Remove this connection from the list. + RemoveConnection(connection); + + RTC_LOG(LS_INFO) << ToString() << ": Removed connection " << connection + << " (" << 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 + // looks at the current selected connection in order to avoid switching + // between fairly similar ones. Since this connection is no longer an + // option, we can just set selected to nullptr and re-choose a best assuming + // that there was no selected connection. + if (selected_connection_ == connection) { + OnSelectedConnectionDestroyed(); + } else { + // If a non-selected connection was destroyed, we don't need to re-sort but + // we do need to update state, because we could be switching to "failed" or + // "completed". + UpdateTransportState(); + } +} + +void P2PTransportChannel::RemoveConnection(const Connection* connection) { + RTC_DCHECK_RUN_ON(network_thread_); + auto it = absl::c_find(connections_, connection); + RTC_DCHECK(it != connections_.end()); + connections_.erase(it); + ice_adapter_->OnConnectionDestroyed(connection); +} + +// When a port is destroyed, remove it from our list of ports to use for +// connection attempts. +void P2PTransportChannel::OnPortDestroyed(PortInterface* port) { + RTC_DCHECK_RUN_ON(network_thread_); + + ports_.erase(std::remove(ports_.begin(), ports_.end(), port), ports_.end()); + pruned_ports_.erase( + std::remove(pruned_ports_.begin(), pruned_ports_.end(), port), + pruned_ports_.end()); + RTC_LOG(LS_INFO) << "Removed port because it is destroyed: " << ports_.size() + << " remaining"; +} + +void P2PTransportChannel::OnPortsPruned( + PortAllocatorSession* session, + const std::vector& ports) { + RTC_DCHECK_RUN_ON(network_thread_); + for (PortInterface* port : ports) { + if (PrunePort(port)) { + RTC_LOG(LS_INFO) << "Removed port: " << port->ToString() << " " + << ports_.size() << " remaining"; + } + } +} + +void P2PTransportChannel::OnCandidatesRemoved( + PortAllocatorSession* session, + const std::vector& candidates) { + RTC_DCHECK_RUN_ON(network_thread_); + // Do not signal candidate removals if continual gathering is not enabled, + // or if this is not the last session because an ICE restart would have + // signaled the remote side to remove all candidates in previous sessions. + if (!config_.gather_continually() || session != allocator_session()) { + return; + } + + std::vector candidates_to_remove; + for (Candidate candidate : candidates) { + candidate.set_transport_name(transport_name()); + candidates_to_remove.push_back(candidate); + } + SignalCandidatesRemoved(this, candidates_to_remove); +} + +void P2PTransportChannel::PruneAllPorts() { + RTC_DCHECK_RUN_ON(network_thread_); + pruned_ports_.insert(pruned_ports_.end(), ports_.begin(), ports_.end()); + ports_.clear(); +} + +bool P2PTransportChannel::PrunePort(PortInterface* port) { + RTC_DCHECK_RUN_ON(network_thread_); + auto it = absl::c_find(ports_, port); + // Don't need to do anything if the port has been deleted from the port + // list. + if (it == ports_.end()) { + return false; + } + ports_.erase(it); + pruned_ports_.push_back(port); + return true; +} + +// We data is available, let listeners know +void P2PTransportChannel::OnReadPacket(Connection* connection, + const char* data, + size_t len, + int64_t packet_time_us) { + RTC_DCHECK_RUN_ON(network_thread_); + + if (connection == selected_connection_) { + // Let the client know of an incoming packet + packets_received_++; + bytes_received_ += len; + RTC_DCHECK(connection->last_data_received() >= last_data_received_ms_); + last_data_received_ms_ = + std::max(last_data_received_ms_, connection->last_data_received()); + 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; + + packets_received_++; + bytes_received_ += len; + RTC_DCHECK(connection->last_data_received() >= last_data_received_ms_); + last_data_received_ms_ = + std::max(last_data_received_ms_, connection->last_data_received()); + + // Let the client know of an incoming packet + SignalReadPacket(this, data, len, packet_time_us, 0); + + // May need to switch the sending connection based on the receiving media + // path if this is the controlled side. + if (ice_role_ == ICEROLE_CONTROLLED) { + ice_adapter_->OnImmediateSwitchRequest(IceSwitchReason::DATA_RECEIVED, + connection); + } +} + +void P2PTransportChannel::OnSentPacket(const rtc::SentPacket& sent_packet) { + RTC_DCHECK_RUN_ON(network_thread_); + + SignalSentPacket(this, sent_packet); +} + +void P2PTransportChannel::OnReadyToSend(Connection* connection) { + RTC_DCHECK_RUN_ON(network_thread_); + if (connection == selected_connection_ && writable()) { + SignalReadyToSend(this); + } +} + +void P2PTransportChannel::SetWritable(bool writable) { + RTC_DCHECK_RUN_ON(network_thread_); + if (writable_ == writable) { + return; + } + RTC_LOG(LS_VERBOSE) << ToString() << ": Changed writable_ to " << writable; + writable_ = writable; + if (writable_) { + has_been_writable_ = true; + SignalReadyToSend(this); + } + SignalWritableState(this); +} + +void P2PTransportChannel::SetReceiving(bool receiving) { + RTC_DCHECK_RUN_ON(network_thread_); + if (receiving_ == receiving) { + return; + } + receiving_ = receiving; + SignalReceivingState(this); +} + +Candidate P2PTransportChannel::SanitizeLocalCandidate( + const Candidate& c) const { + RTC_DCHECK_RUN_ON(network_thread_); + // Delegates to the port allocator. + return allocator_->SanitizeCandidate(c); +} + +Candidate P2PTransportChannel::SanitizeRemoteCandidate( + const Candidate& c) const { + RTC_DCHECK_RUN_ON(network_thread_); + // If the remote endpoint signaled us an mDNS candidate, we assume it + // is supposed to be sanitized. + bool use_hostname_address = absl::EndsWith(c.address().hostname(), LOCAL_TLD); + // Remove the address for prflx remote candidates. See + // https://w3c.github.io/webrtc-stats/#dom-rtcicecandidatestats. + use_hostname_address |= c.type() == PRFLX_PORT_TYPE; + return c.ToSanitizedCopy(use_hostname_address, + false /* filter_related_address */); +} + +void P2PTransportChannel::LogCandidatePairConfig( + Connection* conn, + webrtc::IceCandidatePairConfigType type) { + RTC_DCHECK_RUN_ON(network_thread_); + if (conn == nullptr) { + return; + } + ice_event_log_.LogCandidatePairConfig(type, conn->id(), + conn->ToLogDescription()); +} + +P2PTransportChannel::IceControllerAdapter::IceControllerAdapter( + const IceControllerFactoryArgs& args, + IceControllerFactoryInterface* ice_controller_factory, + ActiveIceControllerFactoryInterface* active_ice_controller_factory, + const webrtc::FieldTrialsView* field_trials, + P2PTransportChannel* transport) + : transport_(transport) { + if (UseActiveIceControllerFieldTrialEnabled(field_trials)) { + if (active_ice_controller_factory) { + ActiveIceControllerFactoryArgs active_args{args, + /* ice_agent= */ transport}; + active_ice_controller_ = + active_ice_controller_factory->Create(active_args); + } else { + active_ice_controller_ = std::make_unique( + /* ice_agent= */ transport, ice_controller_factory, args); + } + } else { + if (ice_controller_factory != nullptr) { + legacy_ice_controller_ = ice_controller_factory->Create(args); + } else { + legacy_ice_controller_ = std::make_unique(args); + } + } +} + +P2PTransportChannel::IceControllerAdapter::~IceControllerAdapter() = default; + +void P2PTransportChannel::IceControllerAdapter::SetIceConfig( + const IceConfig& config) { + active_ice_controller_ ? active_ice_controller_->SetIceConfig(config) + : legacy_ice_controller_->SetIceConfig(config); +} + +void P2PTransportChannel::IceControllerAdapter::OnConnectionAdded( + const Connection* connection) { + active_ice_controller_ ? active_ice_controller_->OnConnectionAdded(connection) + : legacy_ice_controller_->AddConnection(connection); +} + +void P2PTransportChannel::IceControllerAdapter::OnConnectionSwitched( + const Connection* connection) { + active_ice_controller_ + ? active_ice_controller_->OnConnectionSwitched(connection) + : legacy_ice_controller_->SetSelectedConnection(connection); +} + +void P2PTransportChannel::IceControllerAdapter::OnConnectionPinged( + const Connection* connection) { + active_ice_controller_ + ? active_ice_controller_->OnConnectionPinged(connection) + : legacy_ice_controller_->MarkConnectionPinged(connection); +} + +void P2PTransportChannel::IceControllerAdapter::OnConnectionDestroyed( + const Connection* connection) { + active_ice_controller_ + ? active_ice_controller_->OnConnectionDestroyed(connection) + : legacy_ice_controller_->OnConnectionDestroyed(connection); +} + +void P2PTransportChannel::IceControllerAdapter::OnConnectionUpdated( + const Connection* connection) { + if (active_ice_controller_) { + active_ice_controller_->OnConnectionUpdated(connection); + return; + } + RTC_DCHECK_NOTREACHED(); +} + +void P2PTransportChannel::IceControllerAdapter::OnSortAndSwitchRequest( + IceSwitchReason reason) { + active_ice_controller_ + ? active_ice_controller_->OnSortAndSwitchRequest(reason) + : transport_->RequestSortAndStateUpdate(reason); +} + +void P2PTransportChannel::IceControllerAdapter::OnImmediateSortAndSwitchRequest( + IceSwitchReason reason) { + active_ice_controller_ + ? active_ice_controller_->OnImmediateSortAndSwitchRequest(reason) + : transport_->SortConnectionsAndUpdateState(reason); +} + +bool P2PTransportChannel::IceControllerAdapter::OnImmediateSwitchRequest( + IceSwitchReason reason, + const Connection* connection) { + return active_ice_controller_ + ? active_ice_controller_->OnImmediateSwitchRequest(reason, + connection) + : transport_->MaybeSwitchSelectedConnection(connection, reason); +} + +bool P2PTransportChannel::IceControllerAdapter::GetUseCandidateAttribute( + const cricket::Connection* connection, + cricket::NominationMode mode, + cricket::IceMode remote_ice_mode) const { + return active_ice_controller_ + ? active_ice_controller_->GetUseCandidateAttribute( + connection, mode, remote_ice_mode) + : legacy_ice_controller_->GetUseCandidateAttr(connection, mode, + remote_ice_mode); +} + +const Connection* +P2PTransportChannel::IceControllerAdapter::FindNextPingableConnection() { + return active_ice_controller_ + ? active_ice_controller_->FindNextPingableConnection() + : legacy_ice_controller_->FindNextPingableConnection(); +} + +rtc::ArrayView +P2PTransportChannel::IceControllerAdapter::LegacyConnections() const { + RTC_DCHECK_RUN_ON(transport_->network_thread_); + if (active_ice_controller_) { + return rtc::ArrayView(transport_->connections_.data(), + transport_->connections_.size()); + } + + rtc::ArrayView res = legacy_ice_controller_->connections(); + return rtc::ArrayView(const_cast(res.data()), + res.size()); +} + +bool P2PTransportChannel::IceControllerAdapter::LegacyHasPingableConnection() + const { + if (active_ice_controller_) { + RTC_DCHECK_NOTREACHED(); + } + return legacy_ice_controller_->HasPingableConnection(); +} + +IceControllerInterface::PingResult +P2PTransportChannel::IceControllerAdapter::LegacySelectConnectionToPing( + int64_t last_ping_sent_ms) { + if (active_ice_controller_) { + RTC_DCHECK_NOTREACHED(); + } + return legacy_ice_controller_->SelectConnectionToPing(last_ping_sent_ms); +} + +IceControllerInterface::SwitchResult +P2PTransportChannel::IceControllerAdapter::LegacyShouldSwitchConnection( + IceSwitchReason reason, + const Connection* connection) { + if (active_ice_controller_) { + RTC_DCHECK_NOTREACHED(); + } + return legacy_ice_controller_->ShouldSwitchConnection(reason, connection); +} + +IceControllerInterface::SwitchResult +P2PTransportChannel::IceControllerAdapter::LegacySortAndSwitchConnection( + IceSwitchReason reason) { + if (active_ice_controller_) { + RTC_DCHECK_NOTREACHED(); + } + return legacy_ice_controller_->SortAndSwitchConnection(reason); +} + +std::vector +P2PTransportChannel::IceControllerAdapter::LegacyPruneConnections() { + if (active_ice_controller_) { + RTC_DCHECK_NOTREACHED(); + } + return legacy_ice_controller_->PruneConnections(); +} + +} // namespace cricket -- cgit v1.2.3