| /* |
| * Copyright 2015 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 "pc/transportcontroller.h" |
| |
| #include <algorithm> |
| #include <memory> |
| #include <utility> |
| |
| #include "p2p/base/port.h" |
| #include "rtc_base/bind.h" |
| #include "rtc_base/checks.h" |
| #include "rtc_base/ptr_util.h" |
| #include "rtc_base/thread.h" |
| |
| using webrtc::SdpType; |
| |
| namespace { |
| |
| enum { |
| MSG_ICECONNECTIONSTATE, |
| MSG_RECEIVING, |
| MSG_ICEGATHERINGSTATE, |
| MSG_CANDIDATESGATHERED, |
| }; |
| |
| struct CandidatesData : public rtc::MessageData { |
| CandidatesData(const std::string& transport_name, |
| const cricket::Candidates& candidates) |
| : transport_name(transport_name), candidates(candidates) {} |
| |
| std::string transport_name; |
| cricket::Candidates candidates; |
| }; |
| |
| bool VerifyCandidate(const cricket::Candidate& cand, std::string* error) { |
| // No address zero. |
| if (cand.address().IsNil() || cand.address().IsAnyIP()) { |
| *error = "candidate has address of zero"; |
| return false; |
| } |
| |
| // Disallow all ports below 1024, except for 80 and 443 on public addresses. |
| int port = cand.address().port(); |
| if (cand.protocol() == cricket::TCP_PROTOCOL_NAME && |
| (cand.tcptype() == cricket::TCPTYPE_ACTIVE_STR || port == 0)) { |
| // Expected for active-only candidates per |
| // http://tools.ietf.org/html/rfc6544#section-4.5 so no error. |
| // Libjingle clients emit port 0, in "active" mode. |
| return true; |
| } |
| if (port < 1024) { |
| if ((port != 80) && (port != 443)) { |
| *error = "candidate has port below 1024, but not 80 or 443"; |
| return false; |
| } |
| |
| if (cand.address().IsPrivateIP()) { |
| *error = "candidate has port of 80 or 443 with private IP address"; |
| return false; |
| } |
| } |
| |
| return true; |
| } |
| |
| bool VerifyCandidates(const cricket::Candidates& candidates, |
| std::string* error) { |
| for (const cricket::Candidate& candidate : candidates) { |
| if (!VerifyCandidate(candidate, error)) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| } // namespace |
| |
| namespace cricket { |
| |
| // This class groups the DTLS and ICE channels, and helps keep track of |
| // how many external objects (BaseChannels) reference each channel. |
| class TransportController::ChannelPair { |
| public: |
| // TODO(deadbeef): Change the types of |dtls| and |ice| to |
| // DtlsTransport and P2PTransportChannelWrapper, once TransportChannelImpl is |
| // removed. |
| ChannelPair(DtlsTransportInternal* dtls, IceTransportInternal* ice) |
| : ice_(ice), dtls_(dtls) {} |
| |
| // Currently, all ICE-related calls still go through this DTLS channel. But |
| // that will change once we get rid of TransportChannelImpl, and the DTLS |
| // channel interface no longer includes ICE-specific methods. |
| const DtlsTransportInternal* dtls() const { return dtls_.get(); } |
| DtlsTransportInternal* dtls() { return dtls_.get(); } |
| const IceTransportInternal* ice() const { return ice_.get(); } |
| IceTransportInternal* ice() { return ice_.get(); } |
| |
| private: |
| std::unique_ptr<IceTransportInternal> ice_; |
| std::unique_ptr<DtlsTransportInternal> dtls_; |
| |
| RTC_DISALLOW_COPY_AND_ASSIGN(ChannelPair); |
| }; |
| |
| TransportController::TransportController( |
| rtc::Thread* signaling_thread, |
| rtc::Thread* network_thread, |
| PortAllocator* port_allocator, |
| bool redetermine_role_on_ice_restart, |
| const rtc::CryptoOptions& crypto_options, |
| webrtc::RtcEventLog* event_log) |
| : signaling_thread_(signaling_thread), |
| network_thread_(network_thread), |
| port_allocator_(port_allocator), |
| redetermine_role_on_ice_restart_(redetermine_role_on_ice_restart), |
| crypto_options_(crypto_options), |
| event_log_(event_log) {} |
| |
| TransportController::~TransportController() { |
| // Channel destructors may try to send packets, so this needs to happen on |
| // the network thread. |
| network_thread_->Invoke<void>( |
| RTC_FROM_HERE, |
| rtc::Bind(&TransportController::DestroyAllChannels_n, this)); |
| } |
| |
| bool TransportController::SetSslMaxProtocolVersion( |
| rtc::SSLProtocolVersion version) { |
| return network_thread_->Invoke<bool>( |
| RTC_FROM_HERE, rtc::Bind(&TransportController::SetSslMaxProtocolVersion_n, |
| this, version)); |
| } |
| |
| void TransportController::SetIceConfig(const IceConfig& config) { |
| network_thread_->Invoke<void>( |
| RTC_FROM_HERE, |
| rtc::Bind(&TransportController::SetIceConfig_n, this, config)); |
| } |
| |
| void TransportController::SetIceRole(IceRole ice_role) { |
| network_thread_->Invoke<void>( |
| RTC_FROM_HERE, |
| rtc::Bind(&TransportController::SetIceRole_n, this, ice_role)); |
| } |
| |
| void TransportController::SetNeedsIceRestartFlag() { |
| for (auto& kv : transports_) { |
| kv.second->SetNeedsIceRestartFlag(); |
| } |
| } |
| |
| bool TransportController::NeedsIceRestart( |
| const std::string& transport_name) const { |
| const JsepTransport* transport = GetJsepTransport(transport_name); |
| if (!transport) { |
| return false; |
| } |
| return transport->NeedsIceRestart(); |
| } |
| |
| bool TransportController::GetSslRole(const std::string& transport_name, |
| rtc::SSLRole* role) const { |
| return network_thread_->Invoke<bool>( |
| RTC_FROM_HERE, rtc::Bind(&TransportController::GetSslRole_n, this, |
| transport_name, role)); |
| } |
| |
| bool TransportController::SetLocalCertificate( |
| const rtc::scoped_refptr<rtc::RTCCertificate>& certificate) { |
| return network_thread_->Invoke<bool>( |
| RTC_FROM_HERE, rtc::Bind(&TransportController::SetLocalCertificate_n, |
| this, certificate)); |
| } |
| |
| bool TransportController::GetLocalCertificate( |
| const std::string& transport_name, |
| rtc::scoped_refptr<rtc::RTCCertificate>* certificate) const { |
| if (network_thread_->IsCurrent()) { |
| return GetLocalCertificate_n(transport_name, certificate); |
| } |
| return network_thread_->Invoke<bool>( |
| RTC_FROM_HERE, rtc::Bind(&TransportController::GetLocalCertificate_n, |
| this, transport_name, certificate)); |
| } |
| |
| std::unique_ptr<rtc::SSLCertChain> TransportController::GetRemoteSSLCertChain( |
| const std::string& transport_name) const { |
| if (!network_thread_->IsCurrent()) { |
| return network_thread_->Invoke<std::unique_ptr<rtc::SSLCertChain>>( |
| RTC_FROM_HERE, [&] { return GetRemoteSSLCertChain(transport_name); }); |
| } |
| |
| // Get the certificate from the RTP channel's DTLS handshake. Should be |
| // identical to the RTCP channel's, since they were given the same remote |
| // fingerprint. |
| const RefCountedChannel* ch = |
| GetChannel_n(transport_name, cricket::ICE_CANDIDATE_COMPONENT_RTP); |
| if (!ch) { |
| return nullptr; |
| } |
| return ch->dtls()->GetRemoteSSLCertChain(); |
| } |
| |
| bool TransportController::SetLocalTransportDescription( |
| const std::string& transport_name, |
| const TransportDescription& tdesc, |
| SdpType type, |
| std::string* err) { |
| return network_thread_->Invoke<bool>( |
| RTC_FROM_HERE, |
| rtc::Bind(&TransportController::SetLocalTransportDescription_n, this, |
| transport_name, tdesc, type, err)); |
| } |
| |
| bool TransportController::SetRemoteTransportDescription( |
| const std::string& transport_name, |
| const TransportDescription& tdesc, |
| SdpType type, |
| std::string* err) { |
| return network_thread_->Invoke<bool>( |
| RTC_FROM_HERE, |
| rtc::Bind(&TransportController::SetRemoteTransportDescription_n, this, |
| transport_name, tdesc, type, err)); |
| } |
| |
| void TransportController::MaybeStartGathering() { |
| network_thread_->Invoke<void>( |
| RTC_FROM_HERE, |
| rtc::Bind(&TransportController::MaybeStartGathering_n, this)); |
| } |
| |
| bool TransportController::AddRemoteCandidates(const std::string& transport_name, |
| const Candidates& candidates, |
| std::string* err) { |
| return network_thread_->Invoke<bool>( |
| RTC_FROM_HERE, rtc::Bind(&TransportController::AddRemoteCandidates_n, |
| this, transport_name, candidates, err)); |
| } |
| |
| bool TransportController::RemoveRemoteCandidates(const Candidates& candidates, |
| std::string* err) { |
| return network_thread_->Invoke<bool>( |
| RTC_FROM_HERE, rtc::Bind(&TransportController::RemoveRemoteCandidates_n, |
| this, candidates, err)); |
| } |
| |
| bool TransportController::ReadyForRemoteCandidates( |
| const std::string& transport_name) const { |
| return network_thread_->Invoke<bool>( |
| RTC_FROM_HERE, rtc::Bind(&TransportController::ReadyForRemoteCandidates_n, |
| this, transport_name)); |
| } |
| |
| bool TransportController::GetStats(const std::string& transport_name, |
| TransportStats* stats) { |
| if (network_thread_->IsCurrent()) { |
| return GetStats_n(transport_name, stats); |
| } |
| return network_thread_->Invoke<bool>( |
| RTC_FROM_HERE, |
| rtc::Bind(&TransportController::GetStats_n, this, transport_name, stats)); |
| } |
| |
| void TransportController::SetMetricsObserver( |
| webrtc::MetricsObserverInterface* metrics_observer) { |
| return network_thread_->Invoke<void>( |
| RTC_FROM_HERE, rtc::Bind(&TransportController::SetMetricsObserver_n, this, |
| metrics_observer)); |
| } |
| |
| DtlsTransportInternal* TransportController::CreateDtlsTransport( |
| const std::string& transport_name, |
| int component) { |
| return network_thread_->Invoke<DtlsTransportInternal*>( |
| RTC_FROM_HERE, rtc::Bind(&TransportController::CreateDtlsTransport_n, |
| this, transport_name, component)); |
| } |
| |
| DtlsTransportInternal* TransportController::CreateDtlsTransport_n( |
| const std::string& transport_name, |
| int component) { |
| RTC_DCHECK(network_thread_->IsCurrent()); |
| |
| RefCountedChannel* existing_channel = GetChannel_n(transport_name, component); |
| if (existing_channel) { |
| // Channel already exists; increment reference count and return. |
| existing_channel->AddRef(); |
| return existing_channel->dtls(); |
| } |
| |
| // Need to create a new channel. |
| JsepTransport* transport = GetOrCreateJsepTransport(transport_name); |
| |
| // Create DTLS channel wrapping ICE channel, and configure it. |
| IceTransportInternal* ice = |
| CreateIceTransportChannel_n(transport_name, component); |
| DtlsTransportInternal* dtls = |
| CreateDtlsTransportChannel_n(transport_name, component, ice); |
| dtls->ice_transport()->SetMetricsObserver(metrics_observer_); |
| dtls->ice_transport()->SetIceRole(ice_role_); |
| dtls->ice_transport()->SetIceTiebreaker(ice_tiebreaker_); |
| dtls->ice_transport()->SetIceConfig(ice_config_); |
| if (certificate_) { |
| bool set_cert_success = dtls->SetLocalCertificate(certificate_); |
| RTC_DCHECK(set_cert_success); |
| } |
| |
| // Connect to signals offered by the channels. Currently, the DTLS channel |
| // forwards signals from the ICE channel, so we only need to connect to the |
| // DTLS channel. In the future this won't be the case. |
| dtls->SignalWritableState.connect( |
| this, &TransportController::OnChannelWritableState_n); |
| dtls->SignalReceivingState.connect( |
| this, &TransportController::OnChannelReceivingState_n); |
| dtls->SignalDtlsHandshakeError.connect( |
| this, &TransportController::OnDtlsHandshakeError); |
| dtls->ice_transport()->SignalGatheringState.connect( |
| this, &TransportController::OnChannelGatheringState_n); |
| dtls->ice_transport()->SignalCandidateGathered.connect( |
| this, &TransportController::OnChannelCandidateGathered_n); |
| dtls->ice_transport()->SignalCandidatesRemoved.connect( |
| this, &TransportController::OnChannelCandidatesRemoved_n); |
| dtls->ice_transport()->SignalRoleConflict.connect( |
| this, &TransportController::OnChannelRoleConflict_n); |
| dtls->ice_transport()->SignalStateChanged.connect( |
| this, &TransportController::OnChannelStateChanged_n); |
| RefCountedChannel* new_pair = new RefCountedChannel(dtls, ice); |
| new_pair->AddRef(); |
| channels_.insert(channels_.end(), new_pair); |
| bool channel_added = transport->AddChannel(dtls, component); |
| RTC_DCHECK(channel_added); |
| // Adding a channel could cause aggregate state to change. |
| UpdateAggregateStates_n(); |
| return dtls; |
| } |
| |
| void TransportController::DestroyDtlsTransport( |
| const std::string& transport_name, |
| int component) { |
| network_thread_->Invoke<void>( |
| RTC_FROM_HERE, rtc::Bind(&TransportController::DestroyDtlsTransport_n, |
| this, transport_name, component)); |
| } |
| |
| void TransportController::DestroyDtlsTransport_n( |
| const std::string& transport_name, |
| int component) { |
| RTC_DCHECK(network_thread_->IsCurrent()); |
| auto it = GetChannelIterator_n(transport_name, component); |
| if (it == channels_.end()) { |
| RTC_LOG(LS_WARNING) << "Attempting to delete " << transport_name |
| << " TransportChannel " << component |
| << ", which doesn't exist."; |
| return; |
| } |
| // Release one reference to the RefCountedChannel, and do additional cleanup |
| // only if it was the last one. Matches the AddRef logic in |
| // CreateDtlsTransport_n. |
| if ((*it)->Release() == rtc::RefCountReleaseStatus::kOtherRefsRemained) { |
| return; |
| } |
| channels_.erase(it); |
| |
| JsepTransport* t = GetJsepTransport(transport_name); |
| bool channel_removed = t->RemoveChannel(component); |
| RTC_DCHECK(channel_removed); |
| // Just as we create a Transport when its first channel is created, |
| // we delete it when its last channel is deleted. |
| if (!t->HasChannels()) { |
| transports_.erase(transport_name); |
| } |
| // Removing a channel could cause aggregate state to change. |
| UpdateAggregateStates_n(); |
| } |
| |
| webrtc::SrtpTransport* TransportController::CreateSdesTransport( |
| const std::string& transport_name, |
| bool rtcp_mux_enabled) { |
| if (!network_thread_->IsCurrent()) { |
| return network_thread_->Invoke<webrtc::SrtpTransport*>(RTC_FROM_HERE, [&] { |
| return CreateSdesTransport(transport_name, rtcp_mux_enabled); |
| }); |
| } |
| |
| auto existing_rtp_transport = FindRtpTransport(transport_name); |
| |
| if (existing_rtp_transport) { |
| // For SRTP transport wrapper, the |srtp_transport| is expected to be |
| // non-null and |dtls_srtp_transport| is expected to be a nullptr. |
| if (!existing_rtp_transport->srtp_transport || |
| existing_rtp_transport->dtls_srtp_transport) { |
| RTC_LOG(LS_ERROR) |
| << "Failed to create an RTP transport for SDES using name: " |
| << transport_name << " because the type doesn't match."; |
| return nullptr; |
| } |
| existing_rtp_transport->AddRef(); |
| return existing_rtp_transport->srtp_transport; |
| } |
| |
| auto new_srtp_transport = |
| rtc::MakeUnique<webrtc::SrtpTransport>(rtcp_mux_enabled); |
| |
| // The SDES should use an IceTransport rather than a DtlsTransport. We call |
| // |CreateDtlsTransport_n| here because the DtlsTransport will downgrade to an |
| // wrapper over IceTransport if we don't set the certificates and it will just |
| // forward the packets and signals without using DTLS. The support of SDES |
| // will be removed once all the downstream application stop using it. |
| new_srtp_transport->SetRtpPacketTransport(CreateDtlsTransport_n( |
| transport_name, cricket::ICE_CANDIDATE_COMPONENT_RTP)); |
| if (!rtcp_mux_enabled) { |
| new_srtp_transport->SetRtcpPacketTransport(CreateDtlsTransport_n( |
| transport_name, cricket::ICE_CANDIDATE_COMPONENT_RTCP)); |
| } |
| |
| #if defined(ENABLE_EXTERNAL_AUTH) |
| new_srtp_transport->EnableExternalAuth(); |
| #endif |
| |
| auto new_rtp_transport_wrapper = new RefCountedRtpTransport(); |
| new_rtp_transport_wrapper->srtp_transport = new_srtp_transport.get(); |
| new_rtp_transport_wrapper->rtp_transport = std::move(new_srtp_transport); |
| new_rtp_transport_wrapper->AddRef(); |
| rtp_transports_[transport_name] = new_rtp_transport_wrapper; |
| return rtp_transports_[transport_name]->srtp_transport; |
| } |
| |
| webrtc::DtlsSrtpTransport* TransportController::CreateDtlsSrtpTransport( |
| const std::string& transport_name, |
| bool rtcp_mux_enabled) { |
| if (!network_thread_->IsCurrent()) { |
| return network_thread_->Invoke<webrtc::DtlsSrtpTransport*>( |
| RTC_FROM_HERE, [&] { |
| return CreateDtlsSrtpTransport(transport_name, rtcp_mux_enabled); |
| }); |
| } |
| auto existing_rtp_transport = FindRtpTransport(transport_name); |
| |
| if (existing_rtp_transport) { |
| // For DTLS-SRTP transport wrapper, the |dtls_srtp_transport| is expected to |
| // be non-null and |srtp_transport| is expected to be a nullptr. |
| if (existing_rtp_transport->srtp_transport || |
| !existing_rtp_transport->dtls_srtp_transport) { |
| RTC_LOG(LS_ERROR) |
| << "Failed to create an RTP transport for DTLS-SRTP using name: " |
| << transport_name << " because the type doesn't match."; |
| return nullptr; |
| } |
| existing_rtp_transport->AddRef(); |
| return existing_rtp_transport->dtls_srtp_transport; |
| } |
| |
| auto new_srtp_transport = |
| rtc::MakeUnique<webrtc::SrtpTransport>(rtcp_mux_enabled); |
| |
| #if defined(ENABLE_EXTERNAL_AUTH) |
| new_srtp_transport->EnableExternalAuth(); |
| #endif |
| |
| auto new_dtls_srtp_transport = |
| rtc::MakeUnique<webrtc::DtlsSrtpTransport>(std::move(new_srtp_transport)); |
| |
| auto rtp_dtls_transport = CreateDtlsTransport_n( |
| transport_name, cricket::ICE_CANDIDATE_COMPONENT_RTP); |
| auto rtcp_dtls_transport = |
| rtcp_mux_enabled |
| ? nullptr |
| : CreateDtlsTransport_n(transport_name, |
| cricket::ICE_CANDIDATE_COMPONENT_RTCP); |
| |
| new_dtls_srtp_transport->SetDtlsTransports(rtp_dtls_transport, |
| rtcp_dtls_transport); |
| |
| auto new_rtp_transport_wrapper = new RefCountedRtpTransport(); |
| new_rtp_transport_wrapper->dtls_srtp_transport = |
| new_dtls_srtp_transport.get(); |
| new_rtp_transport_wrapper->rtp_transport = std::move(new_dtls_srtp_transport); |
| new_rtp_transport_wrapper->AddRef(); |
| rtp_transports_[transport_name] = new_rtp_transport_wrapper; |
| return rtp_transports_[transport_name]->dtls_srtp_transport; |
| } |
| |
| void TransportController::DestroyTransport(const std::string& transport_name) { |
| if (!network_thread_->IsCurrent()) { |
| network_thread_->Invoke<void>(RTC_FROM_HERE, |
| [&] { DestroyTransport(transport_name); }); |
| return; |
| } |
| |
| auto existing_rtp_transport = FindRtpTransport(transport_name); |
| if (!existing_rtp_transport) { |
| RTC_LOG(LS_WARNING) << "Attempting to delete " << transport_name |
| << " transport , which doesn't exist."; |
| return; |
| } |
| if (existing_rtp_transport->Release() == |
| rtc::RefCountReleaseStatus::kDroppedLastRef) { |
| rtp_transports_.erase(transport_name); |
| } |
| return; |
| } |
| |
| std::vector<std::string> TransportController::transport_names_for_testing() { |
| std::vector<std::string> ret; |
| for (const auto& kv : transports_) { |
| ret.push_back(kv.first); |
| } |
| return ret; |
| } |
| |
| std::vector<DtlsTransportInternal*> |
| TransportController::channels_for_testing() { |
| std::vector<DtlsTransportInternal*> ret; |
| for (RefCountedChannel* channel : channels_) { |
| ret.push_back(channel->dtls()); |
| } |
| return ret; |
| } |
| |
| DtlsTransportInternal* TransportController::get_channel_for_testing( |
| const std::string& transport_name, |
| int component) { |
| RefCountedChannel* ch = GetChannel_n(transport_name, component); |
| return ch ? ch->dtls() : nullptr; |
| } |
| |
| IceTransportInternal* TransportController::CreateIceTransportChannel_n( |
| const std::string& transport_name, |
| int component) { |
| return new P2PTransportChannel(transport_name, component, port_allocator_, |
| event_log_); |
| } |
| |
| DtlsTransportInternal* TransportController::CreateDtlsTransportChannel_n( |
| const std::string&, |
| int, |
| IceTransportInternal* ice) { |
| DtlsTransport* dtls = new DtlsTransport(ice, crypto_options_); |
| dtls->SetSslMaxProtocolVersion(ssl_max_version_); |
| return dtls; |
| } |
| |
| void TransportController::OnMessage(rtc::Message* pmsg) { |
| RTC_DCHECK(signaling_thread_->IsCurrent()); |
| |
| switch (pmsg->message_id) { |
| case MSG_ICECONNECTIONSTATE: { |
| rtc::TypedMessageData<IceConnectionState>* data = |
| static_cast<rtc::TypedMessageData<IceConnectionState>*>(pmsg->pdata); |
| SignalConnectionState(data->data()); |
| delete data; |
| break; |
| } |
| case MSG_RECEIVING: { |
| rtc::TypedMessageData<bool>* data = |
| static_cast<rtc::TypedMessageData<bool>*>(pmsg->pdata); |
| SignalReceiving(data->data()); |
| delete data; |
| break; |
| } |
| case MSG_ICEGATHERINGSTATE: { |
| rtc::TypedMessageData<IceGatheringState>* data = |
| static_cast<rtc::TypedMessageData<IceGatheringState>*>(pmsg->pdata); |
| SignalGatheringState(data->data()); |
| delete data; |
| break; |
| } |
| case MSG_CANDIDATESGATHERED: { |
| CandidatesData* data = static_cast<CandidatesData*>(pmsg->pdata); |
| SignalCandidatesGathered(data->transport_name, data->candidates); |
| delete data; |
| break; |
| } |
| default: |
| RTC_NOTREACHED(); |
| } |
| } |
| |
| const TransportController::RefCountedRtpTransport* |
| TransportController::FindRtpTransport(const std::string& transport_name) { |
| auto it = rtp_transports_.find(transport_name); |
| return it == rtp_transports_.end() ? nullptr : it->second; |
| } |
| |
| std::vector<TransportController::RefCountedChannel*>::iterator |
| TransportController::GetChannelIterator_n(const std::string& transport_name, |
| int component) { |
| RTC_DCHECK(network_thread_->IsCurrent()); |
| return std::find_if(channels_.begin(), channels_.end(), |
| [transport_name, component](RefCountedChannel* channel) { |
| return channel->dtls()->transport_name() == |
| transport_name && |
| channel->dtls()->component() == component; |
| }); |
| } |
| |
| std::vector<TransportController::RefCountedChannel*>::const_iterator |
| TransportController::GetChannelIterator_n(const std::string& transport_name, |
| int component) const { |
| RTC_DCHECK(network_thread_->IsCurrent()); |
| return std::find_if( |
| channels_.begin(), channels_.end(), |
| [transport_name, component](const RefCountedChannel* channel) { |
| return channel->dtls()->transport_name() == transport_name && |
| channel->dtls()->component() == component; |
| }); |
| } |
| |
| const JsepTransport* TransportController::GetJsepTransport( |
| const std::string& transport_name) const { |
| auto it = transports_.find(transport_name); |
| return (it == transports_.end()) ? nullptr : it->second.get(); |
| } |
| |
| JsepTransport* TransportController::GetJsepTransport( |
| const std::string& transport_name) { |
| auto it = transports_.find(transport_name); |
| return (it == transports_.end()) ? nullptr : it->second.get(); |
| } |
| |
| const TransportController::RefCountedChannel* TransportController::GetChannel_n( |
| const std::string& transport_name, |
| int component) const { |
| RTC_DCHECK(network_thread_->IsCurrent()); |
| auto it = GetChannelIterator_n(transport_name, component); |
| return (it == channels_.end()) ? nullptr : *it; |
| } |
| |
| TransportController::RefCountedChannel* TransportController::GetChannel_n( |
| const std::string& transport_name, |
| int component) { |
| RTC_DCHECK(network_thread_->IsCurrent()); |
| auto it = GetChannelIterator_n(transport_name, component); |
| return (it == channels_.end()) ? nullptr : *it; |
| } |
| |
| JsepTransport* TransportController::GetOrCreateJsepTransport( |
| const std::string& transport_name) { |
| RTC_DCHECK(network_thread_->IsCurrent()); |
| |
| JsepTransport* transport = GetJsepTransport(transport_name); |
| if (transport) { |
| return transport; |
| } |
| |
| transport = new JsepTransport(transport_name, certificate_); |
| transports_[transport_name] = std::unique_ptr<JsepTransport>(transport); |
| return transport; |
| } |
| |
| void TransportController::DestroyAllChannels_n() { |
| RTC_DCHECK(network_thread_->IsCurrent()); |
| transports_.clear(); |
| // TODO(nisse): If |channels_| were a vector of scoped_refptr, we |
| // wouldn't need this strange hack. |
| for (RefCountedChannel* channel : channels_) { |
| // Even though these objects are normally ref-counted, if |
| // TransportController is deleted while they still have references, just |
| // remove all references. |
| while (channel->Release() == |
| rtc::RefCountReleaseStatus::kOtherRefsRemained) { |
| } |
| } |
| channels_.clear(); |
| } |
| |
| bool TransportController::SetSslMaxProtocolVersion_n( |
| rtc::SSLProtocolVersion version) { |
| RTC_DCHECK(network_thread_->IsCurrent()); |
| |
| // Max SSL version can only be set before transports are created. |
| if (!transports_.empty()) { |
| return false; |
| } |
| |
| ssl_max_version_ = version; |
| return true; |
| } |
| |
| void TransportController::SetIceConfig_n(const IceConfig& config) { |
| RTC_DCHECK(network_thread_->IsCurrent()); |
| |
| ice_config_ = config; |
| for (auto& channel : channels_) { |
| channel->dtls()->ice_transport()->SetIceConfig(ice_config_); |
| } |
| } |
| |
| void TransportController::SetIceRole_n(IceRole ice_role) { |
| RTC_DCHECK(network_thread_->IsCurrent()); |
| |
| ice_role_ = ice_role; |
| for (auto& channel : channels_) { |
| channel->dtls()->ice_transport()->SetIceRole(ice_role_); |
| } |
| } |
| |
| bool TransportController::GetSslRole_n(const std::string& transport_name, |
| rtc::SSLRole* role) const { |
| RTC_DCHECK(network_thread_->IsCurrent()); |
| |
| const JsepTransport* t = GetJsepTransport(transport_name); |
| if (!t) { |
| return false; |
| } |
| rtc::Optional<rtc::SSLRole> current_role = t->GetSslRole(); |
| if (!current_role) { |
| return false; |
| } |
| *role = *current_role; |
| return true; |
| } |
| |
| bool TransportController::SetLocalCertificate_n( |
| const rtc::scoped_refptr<rtc::RTCCertificate>& certificate) { |
| RTC_DCHECK(network_thread_->IsCurrent()); |
| |
| // Can't change a certificate, or set a null certificate. |
| if (certificate_ || !certificate) { |
| return false; |
| } |
| certificate_ = certificate; |
| |
| // Set certificate for JsepTransport, which verifies it matches the |
| // fingerprint in SDP, and DTLS transport. |
| // Fallback from DTLS to SDES is not supported. |
| for (auto& kv : transports_) { |
| kv.second->SetLocalCertificate(certificate_); |
| } |
| for (auto& channel : channels_) { |
| bool set_cert_success = channel->dtls()->SetLocalCertificate(certificate_); |
| RTC_DCHECK(set_cert_success); |
| } |
| return true; |
| } |
| |
| bool TransportController::GetLocalCertificate_n( |
| const std::string& transport_name, |
| rtc::scoped_refptr<rtc::RTCCertificate>* certificate) const { |
| RTC_DCHECK(network_thread_->IsCurrent()); |
| |
| const JsepTransport* t = GetJsepTransport(transport_name); |
| if (!t) { |
| return false; |
| } |
| return t->GetLocalCertificate(certificate); |
| } |
| |
| bool TransportController::SetLocalTransportDescription_n( |
| const std::string& transport_name, |
| const TransportDescription& tdesc, |
| SdpType type, |
| std::string* err) { |
| RTC_DCHECK(network_thread_->IsCurrent()); |
| |
| JsepTransport* transport = GetJsepTransport(transport_name); |
| if (!transport) { |
| // If we didn't find a transport, that's not an error; |
| // it could have been deleted as a result of bundling. |
| // TODO(deadbeef): Make callers smarter so they won't attempt to set a |
| // description on a deleted transport. |
| return true; |
| } |
| |
| // The initial offer side may use ICE Lite, in which case, per RFC5245 Section |
| // 5.1.1, the answer side should take the controlling role if it is in the |
| // full ICE mode. |
| // |
| // When both sides use ICE Lite, the initial offer side must take the |
| // controlling role, and this is the default logic implemented in |
| // SetLocalDescription in PeerConnection. |
| if (transport->remote_description() && |
| transport->remote_description()->ice_mode == ICEMODE_LITE && |
| ice_role_ == ICEROLE_CONTROLLED && tdesc.ice_mode == ICEMODE_FULL) { |
| SetIceRole_n(ICEROLE_CONTROLLING); |
| } |
| |
| // Older versions of Chrome expect the ICE role to be re-determined when an |
| // ICE restart occurs, and also don't perform conflict resolution correctly, |
| // so for now we can't safely stop doing this, unless the application opts in |
| // by setting |redetermine_role_on_ice_restart_| to false. |
| // See: https://bugs.chromium.org/p/chromium/issues/detail?id=628676 |
| // TODO(deadbeef): Remove this when these old versions of Chrome reach a low |
| // enough population. |
| if (redetermine_role_on_ice_restart_ && transport->local_description() && |
| IceCredentialsChanged(transport->local_description()->ice_ufrag, |
| transport->local_description()->ice_pwd, |
| tdesc.ice_ufrag, tdesc.ice_pwd) && |
| // Don't change the ICE role if the remote endpoint is ICE lite; we |
| // should always be controlling in that case. |
| (!transport->remote_description() || |
| transport->remote_description()->ice_mode != ICEMODE_LITE)) { |
| IceRole new_ice_role = |
| (type == SdpType::kOffer) ? ICEROLE_CONTROLLING : ICEROLE_CONTROLLED; |
| SetIceRole(new_ice_role); |
| } |
| |
| RTC_LOG(LS_INFO) << "Set local transport description on " << transport_name; |
| return transport->SetLocalTransportDescription(tdesc, type, err); |
| } |
| |
| bool TransportController::SetRemoteTransportDescription_n( |
| const std::string& transport_name, |
| const TransportDescription& tdesc, |
| SdpType type, |
| std::string* err) { |
| RTC_DCHECK(network_thread_->IsCurrent()); |
| |
| // If our role is ICEROLE_CONTROLLED and the remote endpoint supports only |
| // ice_lite, this local endpoint should take the CONTROLLING role. |
| // TODO(deadbeef): This is a session-level attribute, so it really shouldn't |
| // be in a TransportDescription in the first place... |
| if (ice_role_ == ICEROLE_CONTROLLED && tdesc.ice_mode == ICEMODE_LITE) { |
| SetIceRole_n(ICEROLE_CONTROLLING); |
| } |
| |
| JsepTransport* transport = GetJsepTransport(transport_name); |
| if (!transport) { |
| // If we didn't find a transport, that's not an error; |
| // it could have been deleted as a result of bundling. |
| // TODO(deadbeef): Make callers smarter so they won't attempt to set a |
| // description on a deleted transport. |
| return true; |
| } |
| |
| // If we use ICE Lite and the remote endpoint uses the full implementation of |
| // ICE, the local endpoint must take the controlled role, and the other side |
| // must be the controlling role. |
| if (transport->local_description() && |
| transport->local_description()->ice_mode == ICEMODE_LITE && |
| ice_role_ == ICEROLE_CONTROLLING && tdesc.ice_mode == ICEMODE_FULL) { |
| SetIceRole_n(ICEROLE_CONTROLLED); |
| } |
| |
| RTC_LOG(LS_INFO) << "Set remote transport description on " << transport_name; |
| return transport->SetRemoteTransportDescription(tdesc, type, err); |
| } |
| |
| void TransportController::MaybeStartGathering_n() { |
| for (auto& channel : channels_) { |
| channel->dtls()->ice_transport()->MaybeStartGathering(); |
| } |
| } |
| |
| bool TransportController::AddRemoteCandidates_n( |
| const std::string& transport_name, |
| const Candidates& candidates, |
| std::string* err) { |
| RTC_DCHECK(network_thread_->IsCurrent()); |
| |
| // Verify each candidate before passing down to the transport layer. |
| if (!VerifyCandidates(candidates, err)) { |
| return false; |
| } |
| |
| JsepTransport* transport = GetJsepTransport(transport_name); |
| if (!transport) { |
| // If we didn't find a transport, that's not an error; |
| // it could have been deleted as a result of bundling. |
| return true; |
| } |
| |
| for (const Candidate& candidate : candidates) { |
| RefCountedChannel* channel = |
| GetChannel_n(transport_name, candidate.component()); |
| if (!channel) { |
| *err = "Candidate has an unknown component: " + candidate.ToString() + |
| " for content: " + transport_name; |
| return false; |
| } |
| channel->dtls()->ice_transport()->AddRemoteCandidate(candidate); |
| } |
| return true; |
| } |
| |
| bool TransportController::RemoveRemoteCandidates_n(const Candidates& candidates, |
| std::string* err) { |
| RTC_DCHECK(network_thread_->IsCurrent()); |
| |
| // Verify each candidate before passing down to the transport layer. |
| if (!VerifyCandidates(candidates, err)) { |
| return false; |
| } |
| |
| std::map<std::string, Candidates> candidates_by_transport_name; |
| for (const Candidate& cand : candidates) { |
| if (!cand.transport_name().empty()) { |
| candidates_by_transport_name[cand.transport_name()].push_back(cand); |
| } else { |
| RTC_LOG(LS_ERROR) << "Not removing candidate because it does not have a " |
| "transport name set: " |
| << cand.ToString(); |
| } |
| } |
| |
| bool result = true; |
| for (const auto& kv : candidates_by_transport_name) { |
| const std::string& transport_name = kv.first; |
| const Candidates& candidates = kv.second; |
| JsepTransport* transport = GetJsepTransport(transport_name); |
| if (!transport) { |
| // If we didn't find a transport, that's not an error; |
| // it could have been deleted as a result of bundling. |
| continue; |
| } |
| for (const Candidate& candidate : candidates) { |
| RefCountedChannel* channel = |
| GetChannel_n(transport_name, candidate.component()); |
| if (channel) { |
| channel->dtls()->ice_transport()->RemoveRemoteCandidate(candidate); |
| } |
| } |
| } |
| return result; |
| } |
| |
| bool TransportController::ReadyForRemoteCandidates_n( |
| const std::string& transport_name) const { |
| RTC_DCHECK(network_thread_->IsCurrent()); |
| |
| const JsepTransport* transport = GetJsepTransport(transport_name); |
| if (!transport) { |
| return false; |
| } |
| return transport->ready_for_remote_candidates(); |
| } |
| |
| bool TransportController::GetStats_n(const std::string& transport_name, |
| TransportStats* stats) { |
| RTC_DCHECK(network_thread_->IsCurrent()); |
| |
| JsepTransport* transport = GetJsepTransport(transport_name); |
| if (!transport) { |
| return false; |
| } |
| return transport->GetStats(stats); |
| } |
| |
| void TransportController::SetMetricsObserver_n( |
| webrtc::MetricsObserverInterface* metrics_observer) { |
| RTC_DCHECK(network_thread_->IsCurrent()); |
| metrics_observer_ = metrics_observer; |
| for (auto& channel : channels_) { |
| channel->dtls()->ice_transport()->SetMetricsObserver(metrics_observer); |
| } |
| } |
| |
| void TransportController::OnChannelWritableState_n( |
| rtc::PacketTransportInternal* transport) { |
| RTC_DCHECK(network_thread_->IsCurrent()); |
| RTC_LOG(LS_INFO) << " Transport " << transport->transport_name() |
| << " writability changed to " << transport->writable() |
| << "."; |
| UpdateAggregateStates_n(); |
| } |
| |
| void TransportController::OnChannelReceivingState_n( |
| rtc::PacketTransportInternal* transport) { |
| RTC_DCHECK(network_thread_->IsCurrent()); |
| UpdateAggregateStates_n(); |
| } |
| |
| void TransportController::OnChannelGatheringState_n( |
| IceTransportInternal* channel) { |
| RTC_DCHECK(network_thread_->IsCurrent()); |
| UpdateAggregateStates_n(); |
| } |
| |
| void TransportController::OnChannelCandidateGathered_n( |
| IceTransportInternal* channel, |
| const Candidate& candidate) { |
| RTC_DCHECK(network_thread_->IsCurrent()); |
| |
| // We should never signal peer-reflexive candidates. |
| if (candidate.type() == PRFLX_PORT_TYPE) { |
| RTC_NOTREACHED(); |
| return; |
| } |
| std::vector<Candidate> candidates; |
| candidates.push_back(candidate); |
| CandidatesData* data = |
| new CandidatesData(channel->transport_name(), candidates); |
| signaling_thread_->Post(RTC_FROM_HERE, this, MSG_CANDIDATESGATHERED, data); |
| } |
| |
| void TransportController::OnChannelCandidatesRemoved_n( |
| IceTransportInternal* channel, |
| const Candidates& candidates) { |
| invoker_.AsyncInvoke<void>( |
| RTC_FROM_HERE, signaling_thread_, |
| rtc::Bind(&TransportController::OnChannelCandidatesRemoved, this, |
| candidates)); |
| } |
| |
| void TransportController::OnChannelCandidatesRemoved( |
| const Candidates& candidates) { |
| RTC_DCHECK(signaling_thread_->IsCurrent()); |
| SignalCandidatesRemoved(candidates); |
| } |
| |
| void TransportController::OnChannelRoleConflict_n( |
| IceTransportInternal* channel) { |
| RTC_DCHECK(network_thread_->IsCurrent()); |
| // Note: since the role conflict is handled entirely on the network thread, |
| // we don't need to worry about role conflicts occurring on two ports at once. |
| // The first one encountered should immediately reverse the role. |
| IceRole reversed_role = (ice_role_ == ICEROLE_CONTROLLING) |
| ? ICEROLE_CONTROLLED |
| : ICEROLE_CONTROLLING; |
| RTC_LOG(LS_INFO) << "Got role conflict; switching to " |
| << (reversed_role == ICEROLE_CONTROLLING ? "controlling" |
| : "controlled") |
| << " role."; |
| SetIceRole_n(reversed_role); |
| } |
| |
| void TransportController::OnChannelStateChanged_n( |
| IceTransportInternal* channel) { |
| RTC_DCHECK(network_thread_->IsCurrent()); |
| RTC_LOG(LS_INFO) << channel->transport_name() << " TransportChannel " |
| << channel->component() |
| << " state changed. Check if state is complete."; |
| UpdateAggregateStates_n(); |
| } |
| |
| void TransportController::UpdateAggregateStates_n() { |
| RTC_DCHECK(network_thread_->IsCurrent()); |
| |
| IceConnectionState new_connection_state = kIceConnectionConnecting; |
| IceGatheringState new_gathering_state = kIceGatheringNew; |
| bool any_receiving = false; |
| bool any_failed = false; |
| bool all_connected = !channels_.empty(); |
| bool all_completed = !channels_.empty(); |
| bool any_gathering = false; |
| bool all_done_gathering = !channels_.empty(); |
| for (const auto& channel : channels_) { |
| any_receiving = any_receiving || channel->dtls()->receiving(); |
| any_failed = any_failed || channel->dtls()->ice_transport()->GetState() == |
| IceTransportState::STATE_FAILED; |
| all_connected = all_connected && channel->dtls()->writable(); |
| all_completed = |
| all_completed && channel->dtls()->writable() && |
| channel->dtls()->ice_transport()->GetState() == |
| IceTransportState::STATE_COMPLETED && |
| channel->dtls()->ice_transport()->GetIceRole() == ICEROLE_CONTROLLING && |
| channel->dtls()->ice_transport()->gathering_state() == |
| kIceGatheringComplete; |
| any_gathering = |
| any_gathering || |
| channel->dtls()->ice_transport()->gathering_state() != kIceGatheringNew; |
| all_done_gathering = all_done_gathering && |
| channel->dtls()->ice_transport()->gathering_state() == |
| kIceGatheringComplete; |
| } |
| if (any_failed) { |
| new_connection_state = kIceConnectionFailed; |
| } else if (all_completed) { |
| new_connection_state = kIceConnectionCompleted; |
| } else if (all_connected) { |
| new_connection_state = kIceConnectionConnected; |
| } |
| if (connection_state_ != new_connection_state) { |
| connection_state_ = new_connection_state; |
| signaling_thread_->Post( |
| RTC_FROM_HERE, this, MSG_ICECONNECTIONSTATE, |
| new rtc::TypedMessageData<IceConnectionState>(new_connection_state)); |
| } |
| |
| if (receiving_ != any_receiving) { |
| receiving_ = any_receiving; |
| signaling_thread_->Post(RTC_FROM_HERE, this, MSG_RECEIVING, |
| new rtc::TypedMessageData<bool>(any_receiving)); |
| } |
| |
| if (all_done_gathering) { |
| new_gathering_state = kIceGatheringComplete; |
| } else if (any_gathering) { |
| new_gathering_state = kIceGatheringGathering; |
| } |
| if (gathering_state_ != new_gathering_state) { |
| gathering_state_ = new_gathering_state; |
| signaling_thread_->Post( |
| RTC_FROM_HERE, this, MSG_ICEGATHERINGSTATE, |
| new rtc::TypedMessageData<IceGatheringState>(new_gathering_state)); |
| } |
| } |
| |
| void TransportController::OnDtlsHandshakeError(rtc::SSLHandshakeError error) { |
| SignalDtlsHandshakeError(error); |
| } |
| |
| } // namespace cricket |