| /* |
| * Copyright 2017 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/jsep_transport_controller.h" |
| |
| #include <memory> |
| #include <utility> |
| |
| #include "absl/algorithm/container.h" |
| #include "api/ice_transport_factory.h" |
| #include "api/transport/datagram_transport_interface.h" |
| #include "api/transport/media/media_transport_interface.h" |
| #include "p2p/base/ice_transport_internal.h" |
| #include "p2p/base/port.h" |
| #include "pc/datagram_rtp_transport.h" |
| #include "pc/srtp_filter.h" |
| #include "rtc_base/bind.h" |
| #include "rtc_base/checks.h" |
| #include "rtc_base/thread.h" |
| |
| using webrtc::SdpType; |
| |
| namespace { |
| |
| webrtc::RTCError VerifyCandidate(const cricket::Candidate& cand) { |
| // No address zero. |
| if (cand.address().IsNil() || cand.address().IsAnyIP()) { |
| return webrtc::RTCError(webrtc::RTCErrorType::INVALID_PARAMETER, |
| "candidate has address of zero"); |
| } |
| |
| // 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 webrtc::RTCError::OK(); |
| } |
| if (port < 1024) { |
| if ((port != 80) && (port != 443)) { |
| return webrtc::RTCError( |
| webrtc::RTCErrorType::INVALID_PARAMETER, |
| "candidate has port below 1024, but not 80 or 443"); |
| } |
| |
| if (cand.address().IsPrivateIP()) { |
| return webrtc::RTCError( |
| webrtc::RTCErrorType::INVALID_PARAMETER, |
| "candidate has port of 80 or 443 with private IP address"); |
| } |
| } |
| |
| // Disallow ICE-TCP with a private IP address. |
| if (cand.protocol() == cricket::TCP_PROTOCOL_NAME && |
| cand.address().IsPrivateIP()) { |
| return webrtc::RTCError(webrtc::RTCErrorType::INVALID_PARAMETER, |
| "candidate is TCP and has a private IP address"); |
| } |
| |
| return webrtc::RTCError::OK(); |
| } |
| |
| webrtc::RTCError VerifyCandidates(const cricket::Candidates& candidates) { |
| for (const cricket::Candidate& candidate : candidates) { |
| webrtc::RTCError error = VerifyCandidate(candidate); |
| if (!error.ok()) { |
| return error; |
| } |
| } |
| return webrtc::RTCError::OK(); |
| } |
| |
| } // namespace |
| |
| namespace webrtc { |
| |
| JsepTransportController::JsepTransportController( |
| rtc::Thread* signaling_thread, |
| rtc::Thread* network_thread, |
| cricket::PortAllocator* port_allocator, |
| AsyncResolverFactory* async_resolver_factory, |
| Config config) |
| : signaling_thread_(signaling_thread), |
| network_thread_(network_thread), |
| port_allocator_(port_allocator), |
| async_resolver_factory_(async_resolver_factory), |
| config_(config) { |
| // The |transport_observer| is assumed to be non-null. |
| RTC_DCHECK(config_.transport_observer); |
| RTC_DCHECK(config_.rtcp_handler); |
| RTC_DCHECK(config_.ice_transport_factory); |
| } |
| |
| JsepTransportController::~JsepTransportController() { |
| // 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(&JsepTransportController::DestroyAllJsepTransports_n, this)); |
| } |
| |
| RTCError JsepTransportController::SetLocalDescription( |
| SdpType type, |
| const cricket::SessionDescription* description) { |
| if (!network_thread_->IsCurrent()) { |
| return network_thread_->Invoke<RTCError>( |
| RTC_FROM_HERE, [=] { return SetLocalDescription(type, description); }); |
| } |
| |
| if (!initial_offerer_.has_value()) { |
| initial_offerer_.emplace(type == SdpType::kOffer); |
| if (*initial_offerer_) { |
| SetIceRole_n(cricket::ICEROLE_CONTROLLING); |
| } else { |
| SetIceRole_n(cricket::ICEROLE_CONTROLLED); |
| } |
| } |
| return ApplyDescription_n(/*local=*/true, type, description); |
| } |
| |
| RTCError JsepTransportController::SetRemoteDescription( |
| SdpType type, |
| const cricket::SessionDescription* description) { |
| if (!network_thread_->IsCurrent()) { |
| return network_thread_->Invoke<RTCError>( |
| RTC_FROM_HERE, [=] { return SetRemoteDescription(type, description); }); |
| } |
| |
| return ApplyDescription_n(/*local=*/false, type, description); |
| } |
| |
| RtpTransportInternal* JsepTransportController::GetRtpTransport( |
| const std::string& mid) const { |
| auto jsep_transport = GetJsepTransportForMid(mid); |
| if (!jsep_transport) { |
| return nullptr; |
| } |
| return jsep_transport->rtp_transport(); |
| } |
| |
| MediaTransportConfig JsepTransportController::GetMediaTransportConfig( |
| const std::string& mid) const { |
| auto jsep_transport = GetJsepTransportForMid(mid); |
| if (!jsep_transport) { |
| return MediaTransportConfig(); |
| } |
| |
| DatagramTransportInterface* datagram_transport = nullptr; |
| if (config_.use_datagram_transport) { |
| datagram_transport = jsep_transport->datagram_transport(); |
| } |
| |
| if (datagram_transport) { |
| return MediaTransportConfig( |
| /*rtp_max_packet_size=*/datagram_transport->GetLargestDatagramSize()); |
| } else { |
| return MediaTransportConfig(); |
| } |
| } |
| |
| DataChannelTransportInterface* JsepTransportController::GetDataChannelTransport( |
| const std::string& mid) const { |
| auto jsep_transport = GetJsepTransportForMid(mid); |
| if (!jsep_transport) { |
| return nullptr; |
| } |
| return jsep_transport->data_channel_transport(); |
| } |
| |
| cricket::DtlsTransportInternal* JsepTransportController::GetDtlsTransport( |
| const std::string& mid) { |
| auto jsep_transport = GetJsepTransportForMid(mid); |
| if (!jsep_transport) { |
| return nullptr; |
| } |
| return jsep_transport->rtp_dtls_transport(); |
| } |
| |
| const cricket::DtlsTransportInternal* |
| JsepTransportController::GetRtcpDtlsTransport(const std::string& mid) const { |
| auto jsep_transport = GetJsepTransportForMid(mid); |
| if (!jsep_transport) { |
| return nullptr; |
| } |
| return jsep_transport->rtcp_dtls_transport(); |
| } |
| |
| rtc::scoped_refptr<webrtc::DtlsTransport> |
| JsepTransportController::LookupDtlsTransportByMid(const std::string& mid) { |
| auto jsep_transport = GetJsepTransportForMid(mid); |
| if (!jsep_transport) { |
| return nullptr; |
| } |
| return jsep_transport->RtpDtlsTransport(); |
| } |
| |
| rtc::scoped_refptr<SctpTransport> JsepTransportController::GetSctpTransport( |
| const std::string& mid) const { |
| auto jsep_transport = GetJsepTransportForMid(mid); |
| if (!jsep_transport) { |
| return nullptr; |
| } |
| return jsep_transport->SctpTransport(); |
| } |
| |
| void JsepTransportController::SetIceConfig(const cricket::IceConfig& config) { |
| if (!network_thread_->IsCurrent()) { |
| network_thread_->Invoke<void>(RTC_FROM_HERE, [&] { SetIceConfig(config); }); |
| return; |
| } |
| |
| ice_config_ = config; |
| for (auto& dtls : GetDtlsTransports()) { |
| dtls->ice_transport()->SetIceConfig(ice_config_); |
| } |
| } |
| |
| void JsepTransportController::SetNeedsIceRestartFlag() { |
| for (auto& kv : jsep_transports_by_name_) { |
| kv.second->SetNeedsIceRestartFlag(); |
| } |
| } |
| |
| bool JsepTransportController::NeedsIceRestart( |
| const std::string& transport_name) const { |
| const cricket::JsepTransport* transport = |
| GetJsepTransportByName(transport_name); |
| if (!transport) { |
| return false; |
| } |
| return transport->needs_ice_restart(); |
| } |
| |
| absl::optional<rtc::SSLRole> JsepTransportController::GetDtlsRole( |
| const std::string& mid) const { |
| if (!network_thread_->IsCurrent()) { |
| return network_thread_->Invoke<absl::optional<rtc::SSLRole>>( |
| RTC_FROM_HERE, [&] { return GetDtlsRole(mid); }); |
| } |
| |
| const cricket::JsepTransport* t = GetJsepTransportForMid(mid); |
| if (!t) { |
| return absl::optional<rtc::SSLRole>(); |
| } |
| return t->GetDtlsRole(); |
| } |
| |
| bool JsepTransportController::SetLocalCertificate( |
| const rtc::scoped_refptr<rtc::RTCCertificate>& certificate) { |
| if (!network_thread_->IsCurrent()) { |
| return network_thread_->Invoke<bool>( |
| RTC_FROM_HERE, [&] { return SetLocalCertificate(certificate); }); |
| } |
| |
| // 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 : jsep_transports_by_name_) { |
| kv.second->SetLocalCertificate(certificate_); |
| } |
| for (auto& dtls : GetDtlsTransports()) { |
| bool set_cert_success = dtls->SetLocalCertificate(certificate_); |
| RTC_DCHECK(set_cert_success); |
| } |
| return true; |
| } |
| |
| rtc::scoped_refptr<rtc::RTCCertificate> |
| JsepTransportController::GetLocalCertificate( |
| const std::string& transport_name) const { |
| if (!network_thread_->IsCurrent()) { |
| return network_thread_->Invoke<rtc::scoped_refptr<rtc::RTCCertificate>>( |
| RTC_FROM_HERE, [&] { return GetLocalCertificate(transport_name); }); |
| } |
| |
| const cricket::JsepTransport* t = GetJsepTransportByName(transport_name); |
| if (!t) { |
| return nullptr; |
| } |
| return t->GetLocalCertificate(); |
| } |
| |
| std::unique_ptr<rtc::SSLCertChain> |
| JsepTransportController::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 transport's DTLS handshake. Should be |
| // identical to the RTCP transport's, since they were given the same remote |
| // fingerprint. |
| auto jsep_transport = GetJsepTransportByName(transport_name); |
| if (!jsep_transport) { |
| return nullptr; |
| } |
| auto dtls = jsep_transport->rtp_dtls_transport(); |
| if (!dtls) { |
| return nullptr; |
| } |
| |
| return dtls->GetRemoteSSLCertChain(); |
| } |
| |
| void JsepTransportController::MaybeStartGathering() { |
| if (!network_thread_->IsCurrent()) { |
| network_thread_->Invoke<void>(RTC_FROM_HERE, |
| [&] { MaybeStartGathering(); }); |
| return; |
| } |
| |
| for (auto& dtls : GetDtlsTransports()) { |
| dtls->ice_transport()->MaybeStartGathering(); |
| } |
| } |
| |
| RTCError JsepTransportController::AddRemoteCandidates( |
| const std::string& transport_name, |
| const cricket::Candidates& candidates) { |
| if (!network_thread_->IsCurrent()) { |
| return network_thread_->Invoke<RTCError>(RTC_FROM_HERE, [&] { |
| return AddRemoteCandidates(transport_name, candidates); |
| }); |
| } |
| |
| // Verify each candidate before passing down to the transport layer. |
| RTCError error = VerifyCandidates(candidates); |
| if (!error.ok()) { |
| return error; |
| } |
| auto jsep_transport = GetJsepTransportByName(transport_name); |
| if (!jsep_transport) { |
| RTC_LOG(LS_WARNING) << "Not adding candidate because the JsepTransport " |
| "doesn't exist. Ignore it."; |
| return RTCError::OK(); |
| } |
| return jsep_transport->AddRemoteCandidates(candidates); |
| } |
| |
| RTCError JsepTransportController::RemoveRemoteCandidates( |
| const cricket::Candidates& candidates) { |
| if (!network_thread_->IsCurrent()) { |
| return network_thread_->Invoke<RTCError>( |
| RTC_FROM_HERE, [&] { return RemoveRemoteCandidates(candidates); }); |
| } |
| |
| // Verify each candidate before passing down to the transport layer. |
| RTCError error = VerifyCandidates(candidates); |
| if (!error.ok()) { |
| return error; |
| } |
| |
| std::map<std::string, cricket::Candidates> candidates_by_transport_name; |
| for (const cricket::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.ToSensitiveString(); |
| } |
| } |
| |
| for (const auto& kv : candidates_by_transport_name) { |
| const std::string& transport_name = kv.first; |
| const cricket::Candidates& candidates = kv.second; |
| cricket::JsepTransport* jsep_transport = |
| GetJsepTransportByName(transport_name); |
| if (!jsep_transport) { |
| RTC_LOG(LS_WARNING) |
| << "Not removing candidate because the JsepTransport doesn't exist."; |
| continue; |
| } |
| for (const cricket::Candidate& candidate : candidates) { |
| cricket::DtlsTransportInternal* dtls = |
| candidate.component() == cricket::ICE_CANDIDATE_COMPONENT_RTP |
| ? jsep_transport->rtp_dtls_transport() |
| : jsep_transport->rtcp_dtls_transport(); |
| if (dtls) { |
| dtls->ice_transport()->RemoveRemoteCandidate(candidate); |
| } |
| } |
| } |
| return RTCError::OK(); |
| } |
| |
| bool JsepTransportController::GetStats(const std::string& transport_name, |
| cricket::TransportStats* stats) { |
| if (!network_thread_->IsCurrent()) { |
| return network_thread_->Invoke<bool>( |
| RTC_FROM_HERE, [=] { return GetStats(transport_name, stats); }); |
| } |
| |
| cricket::JsepTransport* transport = GetJsepTransportByName(transport_name); |
| if (!transport) { |
| return false; |
| } |
| return transport->GetStats(stats); |
| } |
| |
| void JsepTransportController::SetActiveResetSrtpParams( |
| bool active_reset_srtp_params) { |
| if (!network_thread_->IsCurrent()) { |
| network_thread_->Invoke<void>(RTC_FROM_HERE, [=] { |
| SetActiveResetSrtpParams(active_reset_srtp_params); |
| }); |
| return; |
| } |
| |
| RTC_LOG(INFO) |
| << "Updating the active_reset_srtp_params for JsepTransportController: " |
| << active_reset_srtp_params; |
| config_.active_reset_srtp_params = active_reset_srtp_params; |
| for (auto& kv : jsep_transports_by_name_) { |
| kv.second->SetActiveResetSrtpParams(active_reset_srtp_params); |
| } |
| } |
| |
| void JsepTransportController::SetMediaTransportSettings( |
| bool use_datagram_transport, |
| bool use_datagram_transport_for_data_channels, |
| bool use_datagram_transport_for_data_channels_receive_only) { |
| config_.use_datagram_transport = use_datagram_transport; |
| config_.use_datagram_transport_for_data_channels = |
| use_datagram_transport_for_data_channels; |
| config_.use_datagram_transport_for_data_channels_receive_only = |
| use_datagram_transport_for_data_channels_receive_only; |
| } |
| |
| void JsepTransportController::RollbackTransportForMids( |
| const std::vector<std::string>& mids) { |
| if (!network_thread_->IsCurrent()) { |
| network_thread_->Invoke<void>(RTC_FROM_HERE, |
| [=] { RollbackTransportForMids(mids); }); |
| return; |
| } |
| for (auto&& mid : mids) { |
| RemoveTransportForMid(mid); |
| } |
| for (auto&& mid : mids) { |
| MaybeDestroyJsepTransport(mid); |
| } |
| } |
| |
| rtc::scoped_refptr<webrtc::IceTransportInterface> |
| JsepTransportController::CreateIceTransport(const std::string& transport_name, |
| bool rtcp) { |
| int component = rtcp ? cricket::ICE_CANDIDATE_COMPONENT_RTCP |
| : cricket::ICE_CANDIDATE_COMPONENT_RTP; |
| |
| IceTransportInit init; |
| init.set_port_allocator(port_allocator_); |
| init.set_async_resolver_factory(async_resolver_factory_); |
| init.set_event_log(config_.event_log); |
| return config_.ice_transport_factory->CreateIceTransport( |
| transport_name, component, std::move(init)); |
| } |
| |
| std::unique_ptr<cricket::DtlsTransportInternal> |
| JsepTransportController::CreateDtlsTransport( |
| const cricket::ContentInfo& content_info, |
| cricket::IceTransportInternal* ice, |
| DatagramTransportInterface* datagram_transport) { |
| RTC_DCHECK(network_thread_->IsCurrent()); |
| |
| std::unique_ptr<cricket::DtlsTransportInternal> dtls; |
| |
| if (datagram_transport) { |
| RTC_DCHECK(config_.use_datagram_transport || |
| config_.use_datagram_transport_for_data_channels); |
| } else if (config_.dtls_transport_factory) { |
| dtls = config_.dtls_transport_factory->CreateDtlsTransport( |
| ice, config_.crypto_options); |
| } else { |
| dtls = std::make_unique<cricket::DtlsTransport>(ice, config_.crypto_options, |
| config_.event_log); |
| } |
| |
| RTC_DCHECK(dtls); |
| dtls->SetSslMaxProtocolVersion(config_.ssl_max_version); |
| 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 DTLS and ICE transport. |
| dtls->SignalWritableState.connect( |
| this, &JsepTransportController::OnTransportWritableState_n); |
| dtls->SignalReceivingState.connect( |
| this, &JsepTransportController::OnTransportReceivingState_n); |
| dtls->SignalDtlsHandshakeError.connect( |
| this, &JsepTransportController::OnDtlsHandshakeError); |
| dtls->ice_transport()->SignalGatheringState.connect( |
| this, &JsepTransportController::OnTransportGatheringState_n); |
| dtls->ice_transport()->SignalCandidateGathered.connect( |
| this, &JsepTransportController::OnTransportCandidateGathered_n); |
| dtls->ice_transport()->SignalCandidateError.connect( |
| this, &JsepTransportController::OnTransportCandidateError_n); |
| dtls->ice_transport()->SignalCandidatesRemoved.connect( |
| this, &JsepTransportController::OnTransportCandidatesRemoved_n); |
| dtls->ice_transport()->SignalRoleConflict.connect( |
| this, &JsepTransportController::OnTransportRoleConflict_n); |
| dtls->ice_transport()->SignalStateChanged.connect( |
| this, &JsepTransportController::OnTransportStateChanged_n); |
| dtls->ice_transport()->SignalIceTransportStateChanged.connect( |
| this, &JsepTransportController::OnTransportStateChanged_n); |
| dtls->ice_transport()->SignalCandidatePairChanged.connect( |
| this, &JsepTransportController::OnTransportCandidatePairChanged_n); |
| return dtls; |
| } |
| |
| std::unique_ptr<webrtc::RtpTransport> |
| JsepTransportController::CreateUnencryptedRtpTransport( |
| const std::string& transport_name, |
| rtc::PacketTransportInternal* rtp_packet_transport, |
| rtc::PacketTransportInternal* rtcp_packet_transport) { |
| RTC_DCHECK(network_thread_->IsCurrent()); |
| auto unencrypted_rtp_transport = |
| std::make_unique<RtpTransport>(rtcp_packet_transport == nullptr); |
| unencrypted_rtp_transport->SetRtpPacketTransport(rtp_packet_transport); |
| if (rtcp_packet_transport) { |
| unencrypted_rtp_transport->SetRtcpPacketTransport(rtcp_packet_transport); |
| } |
| return unencrypted_rtp_transport; |
| } |
| |
| std::unique_ptr<webrtc::SrtpTransport> |
| JsepTransportController::CreateSdesTransport( |
| const std::string& transport_name, |
| cricket::DtlsTransportInternal* rtp_dtls_transport, |
| cricket::DtlsTransportInternal* rtcp_dtls_transport) { |
| RTC_DCHECK(network_thread_->IsCurrent()); |
| auto srtp_transport = |
| std::make_unique<webrtc::SrtpTransport>(rtcp_dtls_transport == nullptr); |
| RTC_DCHECK(rtp_dtls_transport); |
| srtp_transport->SetRtpPacketTransport(rtp_dtls_transport); |
| if (rtcp_dtls_transport) { |
| srtp_transport->SetRtcpPacketTransport(rtcp_dtls_transport); |
| } |
| if (config_.enable_external_auth) { |
| srtp_transport->EnableExternalAuth(); |
| } |
| return srtp_transport; |
| } |
| |
| std::unique_ptr<webrtc::DtlsSrtpTransport> |
| JsepTransportController::CreateDtlsSrtpTransport( |
| const std::string& transport_name, |
| cricket::DtlsTransportInternal* rtp_dtls_transport, |
| cricket::DtlsTransportInternal* rtcp_dtls_transport) { |
| RTC_DCHECK(network_thread_->IsCurrent()); |
| auto dtls_srtp_transport = std::make_unique<webrtc::DtlsSrtpTransport>( |
| rtcp_dtls_transport == nullptr); |
| if (config_.enable_external_auth) { |
| dtls_srtp_transport->EnableExternalAuth(); |
| } |
| |
| dtls_srtp_transport->SetDtlsTransports(rtp_dtls_transport, |
| rtcp_dtls_transport); |
| dtls_srtp_transport->SetActiveResetSrtpParams( |
| config_.active_reset_srtp_params); |
| dtls_srtp_transport->SignalDtlsStateChange.connect( |
| this, &JsepTransportController::UpdateAggregateStates_n); |
| return dtls_srtp_transport; |
| } |
| |
| std::vector<cricket::DtlsTransportInternal*> |
| JsepTransportController::GetDtlsTransports() { |
| std::vector<cricket::DtlsTransportInternal*> dtls_transports; |
| for (auto it = jsep_transports_by_name_.begin(); |
| it != jsep_transports_by_name_.end(); ++it) { |
| auto jsep_transport = it->second.get(); |
| RTC_DCHECK(jsep_transport); |
| if (jsep_transport->rtp_dtls_transport()) { |
| dtls_transports.push_back(jsep_transport->rtp_dtls_transport()); |
| } |
| |
| if (jsep_transport->rtcp_dtls_transport()) { |
| dtls_transports.push_back(jsep_transport->rtcp_dtls_transport()); |
| } |
| } |
| return dtls_transports; |
| } |
| |
| RTCError JsepTransportController::ApplyDescription_n( |
| bool local, |
| SdpType type, |
| const cricket::SessionDescription* description) { |
| RTC_DCHECK(network_thread_->IsCurrent()); |
| RTC_DCHECK(description); |
| |
| if (local) { |
| local_desc_ = description; |
| } else { |
| remote_desc_ = description; |
| } |
| |
| RTCError error; |
| error = ValidateAndMaybeUpdateBundleGroup(local, type, description); |
| if (!error.ok()) { |
| return error; |
| } |
| |
| std::vector<int> merged_encrypted_extension_ids; |
| absl::optional<std::string> bundle_media_alt_protocol; |
| absl::optional<std::string> bundle_data_alt_protocol; |
| if (bundle_group_) { |
| merged_encrypted_extension_ids = |
| MergeEncryptedHeaderExtensionIdsForBundle(description); |
| error = GetAltProtocolsForBundle(description, &bundle_media_alt_protocol, |
| &bundle_data_alt_protocol); |
| if (!error.ok()) { |
| return error; |
| } |
| } |
| |
| for (const cricket::ContentInfo& content_info : description->contents()) { |
| // Don't create transports for rejected m-lines and bundled m-lines." |
| if (content_info.rejected || |
| (IsBundled(content_info.name) && content_info.name != *bundled_mid())) { |
| continue; |
| } |
| error = MaybeCreateJsepTransport(local, content_info, *description); |
| if (!error.ok()) { |
| return error; |
| } |
| } |
| |
| RTC_DCHECK(description->contents().size() == |
| description->transport_infos().size()); |
| for (size_t i = 0; i < description->contents().size(); ++i) { |
| const cricket::ContentInfo& content_info = description->contents()[i]; |
| const cricket::MediaContentDescription* media_description = |
| content_info.media_description(); |
| const cricket::TransportInfo& transport_info = |
| description->transport_infos()[i]; |
| if (content_info.rejected) { |
| HandleRejectedContent(content_info, description); |
| continue; |
| } |
| |
| if (IsBundled(content_info.name) && content_info.name != *bundled_mid()) { |
| if (!HandleBundledContent(content_info)) { |
| return RTCError(RTCErrorType::INVALID_PARAMETER, |
| "Failed to process the bundled m= section."); |
| } |
| continue; |
| } |
| |
| error = ValidateContent(content_info); |
| if (!error.ok()) { |
| return error; |
| } |
| |
| std::vector<int> extension_ids; |
| absl::optional<std::string> media_alt_protocol; |
| absl::optional<std::string> data_alt_protocol; |
| if (bundled_mid() && content_info.name == *bundled_mid()) { |
| extension_ids = merged_encrypted_extension_ids; |
| media_alt_protocol = bundle_media_alt_protocol; |
| data_alt_protocol = bundle_data_alt_protocol; |
| } else { |
| extension_ids = GetEncryptedHeaderExtensionIds(content_info); |
| switch (media_description->type()) { |
| case cricket::MEDIA_TYPE_AUDIO: |
| case cricket::MEDIA_TYPE_VIDEO: |
| media_alt_protocol = media_description->alt_protocol(); |
| break; |
| case cricket::MEDIA_TYPE_DATA: |
| data_alt_protocol = media_description->alt_protocol(); |
| break; |
| } |
| } |
| |
| int rtp_abs_sendtime_extn_id = |
| GetRtpAbsSendTimeHeaderExtensionId(content_info); |
| |
| cricket::JsepTransport* transport = |
| GetJsepTransportForMid(content_info.name); |
| RTC_DCHECK(transport); |
| |
| SetIceRole_n(DetermineIceRole(transport, transport_info, type, local)); |
| |
| cricket::JsepTransportDescription jsep_description = |
| CreateJsepTransportDescription(content_info, transport_info, |
| extension_ids, rtp_abs_sendtime_extn_id, |
| media_alt_protocol, data_alt_protocol); |
| if (local) { |
| error = |
| transport->SetLocalJsepTransportDescription(jsep_description, type); |
| } else { |
| error = |
| transport->SetRemoteJsepTransportDescription(jsep_description, type); |
| } |
| |
| if (!error.ok()) { |
| LOG_AND_RETURN_ERROR(RTCErrorType::INVALID_PARAMETER, |
| "Failed to apply the description for " + |
| content_info.name + ": " + error.message()); |
| } |
| } |
| return RTCError::OK(); |
| } |
| |
| RTCError JsepTransportController::ValidateAndMaybeUpdateBundleGroup( |
| bool local, |
| SdpType type, |
| const cricket::SessionDescription* description) { |
| RTC_DCHECK(description); |
| const cricket::ContentGroup* new_bundle_group = |
| description->GetGroupByName(cricket::GROUP_TYPE_BUNDLE); |
| |
| // The BUNDLE group containing a MID that no m= section has is invalid. |
| if (new_bundle_group) { |
| for (const auto& content_name : new_bundle_group->content_names()) { |
| if (!description->GetContentByName(content_name)) { |
| return RTCError(RTCErrorType::INVALID_PARAMETER, |
| "The BUNDLE group contains MID:" + content_name + |
| " matching no m= section."); |
| } |
| } |
| } |
| |
| if (type == SdpType::kAnswer) { |
| const cricket::ContentGroup* offered_bundle_group = |
| local ? remote_desc_->GetGroupByName(cricket::GROUP_TYPE_BUNDLE) |
| : local_desc_->GetGroupByName(cricket::GROUP_TYPE_BUNDLE); |
| |
| if (new_bundle_group) { |
| // The BUNDLE group in answer should be a subset of offered group. |
| for (const auto& content_name : new_bundle_group->content_names()) { |
| if (!offered_bundle_group || |
| !offered_bundle_group->HasContentName(content_name)) { |
| return RTCError(RTCErrorType::INVALID_PARAMETER, |
| "The BUNDLE group in answer contains a MID that was " |
| "not in the offered group."); |
| } |
| } |
| } |
| |
| if (bundle_group_) { |
| for (const auto& content_name : bundle_group_->content_names()) { |
| // An answer that removes m= sections from pre-negotiated BUNDLE group |
| // without rejecting it, is invalid. |
| if (!new_bundle_group || |
| !new_bundle_group->HasContentName(content_name)) { |
| auto* content_info = description->GetContentByName(content_name); |
| if (!content_info || !content_info->rejected) { |
| return RTCError(RTCErrorType::INVALID_PARAMETER, |
| "Answer cannot remove m= section " + content_name + |
| " from already-established BUNDLE group."); |
| } |
| } |
| } |
| } |
| } |
| |
| if (config_.bundle_policy == |
| PeerConnectionInterface::kBundlePolicyMaxBundle && |
| !description->HasGroup(cricket::GROUP_TYPE_BUNDLE)) { |
| return RTCError(RTCErrorType::INVALID_PARAMETER, |
| "max-bundle is used but no bundle group found."); |
| } |
| |
| if (ShouldUpdateBundleGroup(type, description)) { |
| bundle_group_ = *new_bundle_group; |
| } |
| |
| if (!bundled_mid()) { |
| return RTCError::OK(); |
| } |
| |
| auto bundled_content = description->GetContentByName(*bundled_mid()); |
| if (!bundled_content) { |
| return RTCError( |
| RTCErrorType::INVALID_PARAMETER, |
| "An m= section associated with the BUNDLE-tag doesn't exist."); |
| } |
| |
| // If the |bundled_content| is rejected, other contents in the bundle group |
| // should be rejected. |
| if (bundled_content->rejected) { |
| for (const auto& content_name : bundle_group_->content_names()) { |
| auto other_content = description->GetContentByName(content_name); |
| if (!other_content->rejected) { |
| return RTCError( |
| RTCErrorType::INVALID_PARAMETER, |
| "The m= section:" + content_name + " should be rejected."); |
| } |
| } |
| } |
| |
| return RTCError::OK(); |
| } |
| |
| RTCError JsepTransportController::ValidateContent( |
| const cricket::ContentInfo& content_info) { |
| if (config_.rtcp_mux_policy == |
| PeerConnectionInterface::kRtcpMuxPolicyRequire && |
| content_info.type == cricket::MediaProtocolType::kRtp && |
| !content_info.media_description()->rtcp_mux()) { |
| return RTCError(RTCErrorType::INVALID_PARAMETER, |
| "The m= section:" + content_info.name + |
| " is invalid. RTCP-MUX is not " |
| "enabled when it is required."); |
| } |
| return RTCError::OK(); |
| } |
| |
| void JsepTransportController::HandleRejectedContent( |
| const cricket::ContentInfo& content_info, |
| const cricket::SessionDescription* description) { |
| // If the content is rejected, let the |
| // BaseChannel/SctpTransport change the RtpTransport/DtlsTransport first, |
| // then destroy the cricket::JsepTransport. |
| RemoveTransportForMid(content_info.name); |
| if (content_info.name == bundled_mid()) { |
| for (const auto& content_name : bundle_group_->content_names()) { |
| RemoveTransportForMid(content_name); |
| } |
| bundle_group_.reset(); |
| } else if (IsBundled(content_info.name)) { |
| // Remove the rejected content from the |bundle_group_|. |
| bundle_group_->RemoveContentName(content_info.name); |
| // Reset the bundle group if nothing left. |
| if (!bundle_group_->FirstContentName()) { |
| bundle_group_.reset(); |
| } |
| } |
| MaybeDestroyJsepTransport(content_info.name); |
| } |
| |
| bool JsepTransportController::HandleBundledContent( |
| const cricket::ContentInfo& content_info) { |
| auto jsep_transport = GetJsepTransportByName(*bundled_mid()); |
| RTC_DCHECK(jsep_transport); |
| // If the content is bundled, let the |
| // BaseChannel/SctpTransport change the RtpTransport/DtlsTransport first, |
| // then destroy the cricket::JsepTransport. |
| if (SetTransportForMid(content_info.name, jsep_transport)) { |
| // TODO(bugs.webrtc.org/9719) For media transport this is far from ideal, |
| // because it means that we first create media transport and start |
| // connecting it, and then we destroy it. We will need to address it before |
| // video path is enabled. |
| MaybeDestroyJsepTransport(content_info.name); |
| return true; |
| } |
| return false; |
| } |
| |
| bool JsepTransportController::SetTransportForMid( |
| const std::string& mid, |
| cricket::JsepTransport* jsep_transport) { |
| RTC_DCHECK(jsep_transport); |
| if (mid_to_transport_[mid] == jsep_transport) { |
| return true; |
| } |
| |
| mid_to_transport_[mid] = jsep_transport; |
| return config_.transport_observer->OnTransportChanged( |
| mid, jsep_transport->rtp_transport(), jsep_transport->RtpDtlsTransport(), |
| jsep_transport->data_channel_transport()); |
| } |
| |
| void JsepTransportController::RemoveTransportForMid(const std::string& mid) { |
| bool ret = config_.transport_observer->OnTransportChanged(mid, nullptr, |
| nullptr, nullptr); |
| // Calling OnTransportChanged with nullptr should always succeed, since it is |
| // only expected to fail when adding media to a transport (not removing). |
| RTC_DCHECK(ret); |
| mid_to_transport_.erase(mid); |
| } |
| |
| cricket::JsepTransportDescription |
| JsepTransportController::CreateJsepTransportDescription( |
| const cricket::ContentInfo& content_info, |
| const cricket::TransportInfo& transport_info, |
| const std::vector<int>& encrypted_extension_ids, |
| int rtp_abs_sendtime_extn_id, |
| absl::optional<std::string> media_alt_protocol, |
| absl::optional<std::string> data_alt_protocol) { |
| const cricket::MediaContentDescription* content_desc = |
| content_info.media_description(); |
| RTC_DCHECK(content_desc); |
| bool rtcp_mux_enabled = content_info.type == cricket::MediaProtocolType::kSctp |
| ? true |
| : content_desc->rtcp_mux(); |
| |
| return cricket::JsepTransportDescription( |
| rtcp_mux_enabled, content_desc->cryptos(), encrypted_extension_ids, |
| rtp_abs_sendtime_extn_id, transport_info.description, media_alt_protocol, |
| data_alt_protocol); |
| } |
| |
| bool JsepTransportController::ShouldUpdateBundleGroup( |
| SdpType type, |
| const cricket::SessionDescription* description) { |
| if (config_.bundle_policy == |
| PeerConnectionInterface::kBundlePolicyMaxBundle) { |
| return true; |
| } |
| |
| if (type != SdpType::kAnswer) { |
| return false; |
| } |
| |
| RTC_DCHECK(local_desc_ && remote_desc_); |
| const cricket::ContentGroup* local_bundle = |
| local_desc_->GetGroupByName(cricket::GROUP_TYPE_BUNDLE); |
| const cricket::ContentGroup* remote_bundle = |
| remote_desc_->GetGroupByName(cricket::GROUP_TYPE_BUNDLE); |
| return local_bundle && remote_bundle; |
| } |
| |
| std::vector<int> JsepTransportController::GetEncryptedHeaderExtensionIds( |
| const cricket::ContentInfo& content_info) { |
| const cricket::MediaContentDescription* content_desc = |
| content_info.media_description(); |
| |
| if (!config_.crypto_options.srtp.enable_encrypted_rtp_header_extensions) { |
| return std::vector<int>(); |
| } |
| |
| std::vector<int> encrypted_header_extension_ids; |
| for (const auto& extension : content_desc->rtp_header_extensions()) { |
| if (!extension.encrypt) { |
| continue; |
| } |
| if (!absl::c_linear_search(encrypted_header_extension_ids, extension.id)) { |
| encrypted_header_extension_ids.push_back(extension.id); |
| } |
| } |
| return encrypted_header_extension_ids; |
| } |
| |
| std::vector<int> |
| JsepTransportController::MergeEncryptedHeaderExtensionIdsForBundle( |
| const cricket::SessionDescription* description) { |
| RTC_DCHECK(description); |
| RTC_DCHECK(bundle_group_); |
| |
| std::vector<int> merged_ids; |
| // Union the encrypted header IDs in the group when bundle is enabled. |
| for (const cricket::ContentInfo& content_info : description->contents()) { |
| if (bundle_group_->HasContentName(content_info.name)) { |
| std::vector<int> extension_ids = |
| GetEncryptedHeaderExtensionIds(content_info); |
| for (int id : extension_ids) { |
| if (!absl::c_linear_search(merged_ids, id)) { |
| merged_ids.push_back(id); |
| } |
| } |
| } |
| } |
| return merged_ids; |
| } |
| |
| RTCError JsepTransportController::GetAltProtocolsForBundle( |
| const cricket::SessionDescription* description, |
| absl::optional<std::string>* media_alt_protocol, |
| absl::optional<std::string>* data_alt_protocol) { |
| RTC_DCHECK(description); |
| RTC_DCHECK(bundle_group_); |
| RTC_DCHECK(media_alt_protocol); |
| RTC_DCHECK(data_alt_protocol); |
| |
| bool found_media = false; |
| bool found_data = false; |
| for (const cricket::ContentInfo& content : description->contents()) { |
| if (bundle_group_->HasContentName(content.name)) { |
| const cricket::MediaContentDescription* media_description = |
| content.media_description(); |
| switch (media_description->type()) { |
| case cricket::MEDIA_TYPE_AUDIO: |
| case cricket::MEDIA_TYPE_VIDEO: |
| if (found_media && |
| *media_alt_protocol != media_description->alt_protocol()) { |
| return RTCError(RTCErrorType::INVALID_PARAMETER, |
| "The BUNDLE group contains conflicting " |
| "alt-protocols for media ('" + |
| media_alt_protocol->value_or("") + "' and '" + |
| media_description->alt_protocol().value_or("") + |
| "')"); |
| } |
| found_media = true; |
| *media_alt_protocol = media_description->alt_protocol(); |
| break; |
| case cricket::MEDIA_TYPE_DATA: |
| if (found_data && |
| *data_alt_protocol != media_description->alt_protocol()) { |
| return RTCError(RTCErrorType::INVALID_PARAMETER, |
| "The BUNDLE group contains conflicting " |
| "alt-protocols for data ('" + |
| data_alt_protocol->value_or("") + "' and '" + |
| media_description->alt_protocol().value_or("") + |
| "')"); |
| } |
| found_data = true; |
| *data_alt_protocol = media_description->alt_protocol(); |
| break; |
| } |
| } |
| } |
| return RTCError::OK(); |
| } |
| |
| int JsepTransportController::GetRtpAbsSendTimeHeaderExtensionId( |
| const cricket::ContentInfo& content_info) { |
| if (!config_.enable_external_auth) { |
| return -1; |
| } |
| |
| const cricket::MediaContentDescription* content_desc = |
| content_info.media_description(); |
| |
| const webrtc::RtpExtension* send_time_extension = |
| webrtc::RtpExtension::FindHeaderExtensionByUri( |
| content_desc->rtp_header_extensions(), |
| webrtc::RtpExtension::kAbsSendTimeUri); |
| return send_time_extension ? send_time_extension->id : -1; |
| } |
| |
| const cricket::JsepTransport* JsepTransportController::GetJsepTransportForMid( |
| const std::string& mid) const { |
| auto it = mid_to_transport_.find(mid); |
| return it == mid_to_transport_.end() ? nullptr : it->second; |
| } |
| |
| cricket::JsepTransport* JsepTransportController::GetJsepTransportForMid( |
| const std::string& mid) { |
| auto it = mid_to_transport_.find(mid); |
| return it == mid_to_transport_.end() ? nullptr : it->second; |
| } |
| |
| const cricket::JsepTransport* JsepTransportController::GetJsepTransportByName( |
| const std::string& transport_name) const { |
| auto it = jsep_transports_by_name_.find(transport_name); |
| return (it == jsep_transports_by_name_.end()) ? nullptr : it->second.get(); |
| } |
| |
| cricket::JsepTransport* JsepTransportController::GetJsepTransportByName( |
| const std::string& transport_name) { |
| auto it = jsep_transports_by_name_.find(transport_name); |
| return (it == jsep_transports_by_name_.end()) ? nullptr : it->second.get(); |
| } |
| |
| // TODO(sukhanov): Refactor to avoid code duplication for Media and Datagram |
| // transports setup. |
| std::unique_ptr<webrtc::DatagramTransportInterface> |
| JsepTransportController::MaybeCreateDatagramTransport( |
| const cricket::ContentInfo& content_info, |
| const cricket::SessionDescription& description, |
| bool local) { |
| if (config_.media_transport_factory == nullptr) { |
| return nullptr; |
| } |
| |
| if (!(config_.use_datagram_transport || |
| config_.use_datagram_transport_for_data_channels)) { |
| return nullptr; |
| } |
| |
| // Caller (offerer) datagram transport. |
| if (offer_datagram_transport_) { |
| RTC_DCHECK(local); |
| RTC_LOG(LS_INFO) << "Offered datagram transport has now been activated."; |
| return std::move(offer_datagram_transport_); |
| } |
| |
| const cricket::TransportDescription* transport_description = |
| description.GetTransportDescriptionByName(content_info.mid()); |
| RTC_DCHECK(transport_description) |
| << "Missing transport description for mid=" << content_info.mid(); |
| |
| if (!transport_description->opaque_parameters) { |
| RTC_LOG(LS_INFO) |
| << "No opaque transport parameters, not creating datagram transport"; |
| return nullptr; |
| } |
| |
| if (transport_description->opaque_parameters->protocol != |
| config_.media_transport_factory->GetTransportName()) { |
| RTC_LOG(LS_INFO) << "Opaque transport parameters for protocol=" |
| << transport_description->opaque_parameters->protocol |
| << ", which does not match supported protocol=" |
| << config_.media_transport_factory->GetTransportName(); |
| return nullptr; |
| } |
| |
| RTC_DCHECK(!local); |
| // When bundle is enabled, two JsepTransports are created, and then |
| // the second transport is destroyed (right away). |
| // For datagram transport, we don't want to create the second |
| // datagram transport in the first place. |
| RTC_LOG(LS_INFO) << "Returning new, client datagram transport."; |
| |
| MediaTransportSettings settings; |
| settings.is_caller = local; |
| settings.remote_transport_parameters = |
| transport_description->opaque_parameters->parameters; |
| settings.event_log = config_.event_log; |
| |
| auto datagram_transport_result = |
| config_.media_transport_factory->CreateDatagramTransport(network_thread_, |
| settings); |
| |
| if (!datagram_transport_result.ok()) { |
| // Datagram transport negotiation will fail and we'll fall back to RTP. |
| return nullptr; |
| } |
| |
| if (!datagram_transport_result.value() |
| ->SetRemoteTransportParameters( |
| transport_description->opaque_parameters->parameters) |
| .ok()) { |
| // Datagram transport negotiation failed (parameters are incompatible). |
| // Fall back to RTP. |
| return nullptr; |
| } |
| |
| return datagram_transport_result.MoveValue(); |
| } |
| |
| RTCError JsepTransportController::MaybeCreateJsepTransport( |
| bool local, |
| const cricket::ContentInfo& content_info, |
| const cricket::SessionDescription& description) { |
| RTC_DCHECK(network_thread_->IsCurrent()); |
| cricket::JsepTransport* transport = GetJsepTransportByName(content_info.name); |
| if (transport) { |
| return RTCError::OK(); |
| } |
| |
| const cricket::MediaContentDescription* content_desc = |
| content_info.media_description(); |
| if (certificate_ && !content_desc->cryptos().empty()) { |
| return RTCError(RTCErrorType::INVALID_PARAMETER, |
| "SDES and DTLS-SRTP cannot be enabled at the same time."); |
| } |
| |
| rtc::scoped_refptr<webrtc::IceTransportInterface> ice = |
| CreateIceTransport(content_info.name, /*rtcp=*/false); |
| RTC_DCHECK(ice); |
| |
| std::unique_ptr<DatagramTransportInterface> datagram_transport = |
| MaybeCreateDatagramTransport(content_info, description, local); |
| if (datagram_transport) { |
| datagram_transport->Connect(ice->internal()); |
| } |
| |
| std::unique_ptr<cricket::DtlsTransportInternal> rtp_dtls_transport = |
| CreateDtlsTransport(content_info, ice->internal(), nullptr); |
| |
| std::unique_ptr<cricket::DtlsTransportInternal> rtcp_dtls_transport; |
| std::unique_ptr<RtpTransport> unencrypted_rtp_transport; |
| std::unique_ptr<SrtpTransport> sdes_transport; |
| std::unique_ptr<DtlsSrtpTransport> dtls_srtp_transport; |
| std::unique_ptr<RtpTransportInternal> datagram_rtp_transport; |
| |
| rtc::scoped_refptr<webrtc::IceTransportInterface> rtcp_ice; |
| if (config_.rtcp_mux_policy != |
| PeerConnectionInterface::kRtcpMuxPolicyRequire && |
| content_info.type == cricket::MediaProtocolType::kRtp) { |
| RTC_DCHECK(datagram_transport == nullptr); |
| rtcp_ice = CreateIceTransport(content_info.name, /*rtcp=*/true); |
| rtcp_dtls_transport = |
| CreateDtlsTransport(content_info, rtcp_ice->internal(), |
| /*datagram_transport=*/nullptr); |
| } |
| |
| // Only create a datagram RTP transport if the datagram transport should be |
| // used for RTP. |
| if (datagram_transport && config_.use_datagram_transport) { |
| // TODO(sukhanov): We use unencrypted RTP transport over DatagramTransport, |
| // because MediaTransport encrypts. In the future we may want to |
| // implement our own version of RtpTransport over MediaTransport, because |
| // it will give us more control over things like: |
| // - Fusing |
| // - Rtp header compression |
| // - Handling Rtcp feedback. |
| RTC_LOG(LS_INFO) << "Creating UnencryptedRtpTransport, because datagram " |
| "transport is used."; |
| RTC_DCHECK(!rtcp_dtls_transport); |
| datagram_rtp_transport = std::make_unique<DatagramRtpTransport>( |
| content_info.media_description()->rtp_header_extensions(), |
| ice->internal(), datagram_transport.get()); |
| } |
| |
| if (config_.disable_encryption) { |
| RTC_LOG(LS_INFO) |
| << "Creating UnencryptedRtpTransport, becayse encryption is disabled."; |
| unencrypted_rtp_transport = CreateUnencryptedRtpTransport( |
| content_info.name, rtp_dtls_transport.get(), rtcp_dtls_transport.get()); |
| } else if (!content_desc->cryptos().empty()) { |
| sdes_transport = CreateSdesTransport( |
| content_info.name, rtp_dtls_transport.get(), rtcp_dtls_transport.get()); |
| RTC_LOG(LS_INFO) << "Creating SdesTransport."; |
| } else { |
| RTC_LOG(LS_INFO) << "Creating DtlsSrtpTransport."; |
| dtls_srtp_transport = CreateDtlsSrtpTransport( |
| content_info.name, rtp_dtls_transport.get(), rtcp_dtls_transport.get()); |
| } |
| |
| std::unique_ptr<cricket::SctpTransportInternal> sctp_transport; |
| if (config_.sctp_factory) { |
| sctp_transport = |
| config_.sctp_factory->CreateSctpTransport(rtp_dtls_transport.get()); |
| } |
| |
| DataChannelTransportInterface* data_channel_transport = nullptr; |
| if (config_.use_datagram_transport_for_data_channels) { |
| data_channel_transport = datagram_transport.get(); |
| } |
| |
| std::unique_ptr<cricket::JsepTransport> jsep_transport = |
| std::make_unique<cricket::JsepTransport>( |
| content_info.name, certificate_, std::move(ice), std::move(rtcp_ice), |
| std::move(unencrypted_rtp_transport), std::move(sdes_transport), |
| std::move(dtls_srtp_transport), std::move(datagram_rtp_transport), |
| std::move(rtp_dtls_transport), std::move(rtcp_dtls_transport), |
| std::move(sctp_transport), std::move(datagram_transport), |
| data_channel_transport); |
| |
| jsep_transport->rtp_transport()->SignalRtcpPacketReceived.connect( |
| this, &JsepTransportController::OnRtcpPacketReceived_n); |
| |
| jsep_transport->SignalRtcpMuxActive.connect( |
| this, &JsepTransportController::UpdateAggregateStates_n); |
| jsep_transport->SignalDataChannelTransportNegotiated.connect( |
| this, &JsepTransportController::OnDataChannelTransportNegotiated_n); |
| SetTransportForMid(content_info.name, jsep_transport.get()); |
| |
| jsep_transports_by_name_[content_info.name] = std::move(jsep_transport); |
| UpdateAggregateStates_n(); |
| return RTCError::OK(); |
| } |
| |
| void JsepTransportController::MaybeDestroyJsepTransport( |
| const std::string& mid) { |
| auto jsep_transport = GetJsepTransportByName(mid); |
| if (!jsep_transport) { |
| return; |
| } |
| |
| // Don't destroy the JsepTransport if there are still media sections referring |
| // to it. |
| for (const auto& kv : mid_to_transport_) { |
| if (kv.second == jsep_transport) { |
| return; |
| } |
| } |
| |
| jsep_transports_by_name_.erase(mid); |
| UpdateAggregateStates_n(); |
| } |
| |
| void JsepTransportController::DestroyAllJsepTransports_n() { |
| RTC_DCHECK(network_thread_->IsCurrent()); |
| |
| for (const auto& jsep_transport : jsep_transports_by_name_) { |
| config_.transport_observer->OnTransportChanged(jsep_transport.first, |
| nullptr, nullptr, nullptr); |
| } |
| |
| jsep_transports_by_name_.clear(); |
| } |
| |
| void JsepTransportController::SetIceRole_n(cricket::IceRole ice_role) { |
| RTC_DCHECK(network_thread_->IsCurrent()); |
| |
| ice_role_ = ice_role; |
| for (auto& dtls : GetDtlsTransports()) { |
| dtls->ice_transport()->SetIceRole(ice_role_); |
| } |
| } |
| |
| cricket::IceRole JsepTransportController::DetermineIceRole( |
| cricket::JsepTransport* jsep_transport, |
| const cricket::TransportInfo& transport_info, |
| SdpType type, |
| bool local) { |
| cricket::IceRole ice_role = ice_role_; |
| auto tdesc = transport_info.description; |
| if (local) { |
| // 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 JsepTransportController. |
| if (jsep_transport->remote_description() && |
| jsep_transport->remote_description()->transport_desc.ice_mode == |
| cricket::ICEMODE_LITE && |
| ice_role_ == cricket::ICEROLE_CONTROLLED && |
| tdesc.ice_mode == cricket::ICEMODE_FULL) { |
| ice_role = cricket::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 |config_.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 (config_.redetermine_role_on_ice_restart && |
| jsep_transport->local_description() && |
| cricket::IceCredentialsChanged( |
| jsep_transport->local_description()->transport_desc.ice_ufrag, |
| jsep_transport->local_description()->transport_desc.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. |
| (!jsep_transport->remote_description() || |
| jsep_transport->remote_description()->transport_desc.ice_mode != |
| cricket::ICEMODE_LITE)) { |
| ice_role = (type == SdpType::kOffer) ? cricket::ICEROLE_CONTROLLING |
| : cricket::ICEROLE_CONTROLLED; |
| } |
| } else { |
| // If our role is cricket::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_ == cricket::ICEROLE_CONTROLLED && |
| tdesc.ice_mode == cricket::ICEMODE_LITE) { |
| ice_role = cricket::ICEROLE_CONTROLLING; |
| } |
| |
| // 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 (jsep_transport->local_description() && |
| jsep_transport->local_description()->transport_desc.ice_mode == |
| cricket::ICEMODE_LITE && |
| ice_role_ == cricket::ICEROLE_CONTROLLING && |
| tdesc.ice_mode == cricket::ICEMODE_FULL) { |
| ice_role = cricket::ICEROLE_CONTROLLED; |
| } |
| } |
| |
| return ice_role; |
| } |
| |
| void JsepTransportController::OnTransportWritableState_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 JsepTransportController::OnTransportReceivingState_n( |
| rtc::PacketTransportInternal* transport) { |
| RTC_DCHECK(network_thread_->IsCurrent()); |
| UpdateAggregateStates_n(); |
| } |
| |
| void JsepTransportController::OnTransportGatheringState_n( |
| cricket::IceTransportInternal* transport) { |
| RTC_DCHECK(network_thread_->IsCurrent()); |
| UpdateAggregateStates_n(); |
| } |
| |
| void JsepTransportController::OnTransportCandidateGathered_n( |
| cricket::IceTransportInternal* transport, |
| const cricket::Candidate& candidate) { |
| RTC_DCHECK(network_thread_->IsCurrent()); |
| |
| // We should never signal peer-reflexive candidates. |
| if (candidate.type() == cricket::PRFLX_PORT_TYPE) { |
| RTC_NOTREACHED(); |
| return; |
| } |
| std::string transport_name = transport->transport_name(); |
| invoker_.AsyncInvoke<void>( |
| RTC_FROM_HERE, signaling_thread_, [this, transport_name, candidate] { |
| SignalIceCandidatesGathered(transport_name, {candidate}); |
| }); |
| } |
| |
| void JsepTransportController::OnTransportCandidateError_n( |
| cricket::IceTransportInternal* transport, |
| const cricket::IceCandidateErrorEvent& event) { |
| RTC_DCHECK(network_thread_->IsCurrent()); |
| |
| invoker_.AsyncInvoke<void>(RTC_FROM_HERE, signaling_thread_, |
| [this, event] { SignalIceCandidateError(event); }); |
| } |
| void JsepTransportController::OnTransportCandidatesRemoved_n( |
| cricket::IceTransportInternal* transport, |
| const cricket::Candidates& candidates) { |
| invoker_.AsyncInvoke<void>( |
| RTC_FROM_HERE, signaling_thread_, |
| [this, candidates] { SignalIceCandidatesRemoved(candidates); }); |
| } |
| void JsepTransportController::OnTransportCandidatePairChanged_n( |
| const cricket::CandidatePairChangeEvent& event) { |
| invoker_.AsyncInvoke<void>(RTC_FROM_HERE, signaling_thread_, [this, event] { |
| SignalIceCandidatePairChanged(event); |
| }); |
| } |
| |
| void JsepTransportController::OnTransportRoleConflict_n( |
| cricket::IceTransportInternal* transport) { |
| 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. |
| cricket::IceRole reversed_role = (ice_role_ == cricket::ICEROLE_CONTROLLING) |
| ? cricket::ICEROLE_CONTROLLED |
| : cricket::ICEROLE_CONTROLLING; |
| RTC_LOG(LS_INFO) << "Got role conflict; switching to " |
| << (reversed_role == cricket::ICEROLE_CONTROLLING |
| ? "controlling" |
| : "controlled") |
| << " role."; |
| SetIceRole_n(reversed_role); |
| } |
| |
| void JsepTransportController::OnTransportStateChanged_n( |
| cricket::IceTransportInternal* transport) { |
| RTC_DCHECK(network_thread_->IsCurrent()); |
| RTC_LOG(LS_INFO) << transport->transport_name() << " Transport " |
| << transport->component() |
| << " state changed. Check if state is complete."; |
| UpdateAggregateStates_n(); |
| } |
| |
| void JsepTransportController::OnDataChannelTransportNegotiated_n( |
| cricket::JsepTransport* transport, |
| DataChannelTransportInterface* data_channel_transport) { |
| for (auto it : mid_to_transport_) { |
| if (it.second == transport) { |
| config_.transport_observer->OnTransportChanged( |
| it.first, transport->rtp_transport(), transport->RtpDtlsTransport(), |
| data_channel_transport); |
| } |
| } |
| } |
| |
| void JsepTransportController::UpdateAggregateStates_n() { |
| RTC_DCHECK(network_thread_->IsCurrent()); |
| |
| auto dtls_transports = GetDtlsTransports(); |
| cricket::IceConnectionState new_connection_state = |
| cricket::kIceConnectionConnecting; |
| PeerConnectionInterface::IceConnectionState new_ice_connection_state = |
| PeerConnectionInterface::IceConnectionState::kIceConnectionNew; |
| PeerConnectionInterface::PeerConnectionState new_combined_state = |
| PeerConnectionInterface::PeerConnectionState::kNew; |
| cricket::IceGatheringState new_gathering_state = cricket::kIceGatheringNew; |
| bool any_failed = false; |
| bool all_connected = !dtls_transports.empty(); |
| bool all_completed = !dtls_transports.empty(); |
| bool any_gathering = false; |
| bool all_done_gathering = !dtls_transports.empty(); |
| |
| std::map<IceTransportState, int> ice_state_counts; |
| std::map<cricket::DtlsTransportState, int> dtls_state_counts; |
| |
| for (const auto& dtls : dtls_transports) { |
| any_failed = any_failed || dtls->ice_transport()->GetState() == |
| cricket::IceTransportState::STATE_FAILED; |
| all_connected = all_connected && dtls->writable(); |
| all_completed = |
| all_completed && dtls->writable() && |
| dtls->ice_transport()->GetState() == |
| cricket::IceTransportState::STATE_COMPLETED && |
| dtls->ice_transport()->GetIceRole() == cricket::ICEROLE_CONTROLLING && |
| dtls->ice_transport()->gathering_state() == |
| cricket::kIceGatheringComplete; |
| any_gathering = any_gathering || dtls->ice_transport()->gathering_state() != |
| cricket::kIceGatheringNew; |
| all_done_gathering = |
| all_done_gathering && dtls->ice_transport()->gathering_state() == |
| cricket::kIceGatheringComplete; |
| |
| dtls_state_counts[dtls->dtls_state()]++; |
| ice_state_counts[dtls->ice_transport()->GetIceTransportState()]++; |
| } |
| |
| if (any_failed) { |
| new_connection_state = cricket::kIceConnectionFailed; |
| } else if (all_completed) { |
| new_connection_state = cricket::kIceConnectionCompleted; |
| } else if (all_connected) { |
| new_connection_state = cricket::kIceConnectionConnected; |
| } |
| if (ice_connection_state_ != new_connection_state) { |
| ice_connection_state_ = new_connection_state; |
| invoker_.AsyncInvoke<void>(RTC_FROM_HERE, signaling_thread_, |
| [this, new_connection_state] { |
| SignalIceConnectionState(new_connection_state); |
| }); |
| } |
| |
| // Compute the current RTCIceConnectionState as described in |
| // https://www.w3.org/TR/webrtc/#dom-rtciceconnectionstate. |
| // The PeerConnection is responsible for handling the "closed" state. |
| int total_ice_checking = ice_state_counts[IceTransportState::kChecking]; |
| int total_ice_connected = ice_state_counts[IceTransportState::kConnected]; |
| int total_ice_completed = ice_state_counts[IceTransportState::kCompleted]; |
| int total_ice_failed = ice_state_counts[IceTransportState::kFailed]; |
| int total_ice_disconnected = |
| ice_state_counts[IceTransportState::kDisconnected]; |
| int total_ice_closed = ice_state_counts[IceTransportState::kClosed]; |
| int total_ice_new = ice_state_counts[IceTransportState::kNew]; |
| int total_ice = dtls_transports.size(); |
| |
| if (total_ice_failed > 0) { |
| // Any RTCIceTransports are in the "failed" state. |
| new_ice_connection_state = PeerConnectionInterface::kIceConnectionFailed; |
| } else if (total_ice_disconnected > 0) { |
| // None of the previous states apply and any RTCIceTransports are in the |
| // "disconnected" state. |
| new_ice_connection_state = |
| PeerConnectionInterface::kIceConnectionDisconnected; |
| } else if (total_ice_new + total_ice_closed == total_ice) { |
| // None of the previous states apply and all RTCIceTransports are in the |
| // "new" or "closed" state, or there are no transports. |
| new_ice_connection_state = PeerConnectionInterface::kIceConnectionNew; |
| } else if (total_ice_new + total_ice_checking > 0) { |
| // None of the previous states apply and any RTCIceTransports are in the |
| // "new" or "checking" state. |
| new_ice_connection_state = PeerConnectionInterface::kIceConnectionChecking; |
| } else if (total_ice_completed + total_ice_closed == total_ice || |
| all_completed) { |
| // None of the previous states apply and all RTCIceTransports are in the |
| // "completed" or "closed" state. |
| // |
| // TODO(https://bugs.webrtc.org/10356): The all_completed condition is added |
| // to mimic the behavior of the old ICE connection state, and should be |
| // removed once we get end-of-candidates signaling in place. |
| new_ice_connection_state = PeerConnectionInterface::kIceConnectionCompleted; |
| } else if (total_ice_connected + total_ice_completed + total_ice_closed == |
| total_ice) { |
| // None of the previous states apply and all RTCIceTransports are in the |
| // "connected", "completed" or "closed" state. |
| new_ice_connection_state = PeerConnectionInterface::kIceConnectionConnected; |
| } else { |
| RTC_NOTREACHED(); |
| } |
| |
| if (standardized_ice_connection_state_ != new_ice_connection_state) { |
| if (standardized_ice_connection_state_ == |
| PeerConnectionInterface::kIceConnectionChecking && |
| new_ice_connection_state == |
| PeerConnectionInterface::kIceConnectionCompleted) { |
| // Ensure that we never skip over the "connected" state. |
| invoker_.AsyncInvoke<void>(RTC_FROM_HERE, signaling_thread_, [this] { |
| SignalStandardizedIceConnectionState( |
| PeerConnectionInterface::kIceConnectionConnected); |
| }); |
| } |
| standardized_ice_connection_state_ = new_ice_connection_state; |
| invoker_.AsyncInvoke<void>( |
| RTC_FROM_HERE, signaling_thread_, [this, new_ice_connection_state] { |
| SignalStandardizedIceConnectionState(new_ice_connection_state); |
| }); |
| } |
| |
| // Compute the current RTCPeerConnectionState as described in |
| // https://www.w3.org/TR/webrtc/#dom-rtcpeerconnectionstate. |
| // The PeerConnection is responsible for handling the "closed" state. |
| // Note that "connecting" is only a valid state for DTLS transports while |
| // "checking", "completed" and "disconnected" are only valid for ICE |
| // transports. |
| int total_connected = total_ice_connected + |
| dtls_state_counts[cricket::DTLS_TRANSPORT_CONNECTED]; |
| int total_dtls_connecting = |
| dtls_state_counts[cricket::DTLS_TRANSPORT_CONNECTING]; |
| int total_failed = |
| total_ice_failed + dtls_state_counts[cricket::DTLS_TRANSPORT_FAILED]; |
| int total_closed = |
| total_ice_closed + dtls_state_counts[cricket::DTLS_TRANSPORT_CLOSED]; |
| int total_new = |
| total_ice_new + dtls_state_counts[cricket::DTLS_TRANSPORT_NEW]; |
| int total_transports = total_ice * 2; |
| |
| if (total_failed > 0) { |
| // Any of the RTCIceTransports or RTCDtlsTransports are in a "failed" state. |
| new_combined_state = PeerConnectionInterface::PeerConnectionState::kFailed; |
| } else if (total_ice_disconnected > 0) { |
| // None of the previous states apply and any RTCIceTransports or |
| // RTCDtlsTransports are in the "disconnected" state. |
| new_combined_state = |
| PeerConnectionInterface::PeerConnectionState::kDisconnected; |
| } else if (total_new + total_closed == total_transports) { |
| // None of the previous states apply and all RTCIceTransports and |
| // RTCDtlsTransports are in the "new" or "closed" state, or there are no |
| // transports. |
| new_combined_state = PeerConnectionInterface::PeerConnectionState::kNew; |
| } else if (total_new + total_dtls_connecting + total_ice_checking > 0) { |
| // None of the previous states apply and all RTCIceTransports or |
| // RTCDtlsTransports are in the "new", "connecting" or "checking" state. |
| new_combined_state = |
| PeerConnectionInterface::PeerConnectionState::kConnecting; |
| } else if (total_connected + total_ice_completed + total_closed == |
| total_transports) { |
| // None of the previous states apply and all RTCIceTransports and |
| // RTCDtlsTransports are in the "connected", "completed" or "closed" state. |
| new_combined_state = |
| PeerConnectionInterface::PeerConnectionState::kConnected; |
| } else { |
| RTC_NOTREACHED(); |
| } |
| |
| if (combined_connection_state_ != new_combined_state) { |
| combined_connection_state_ = new_combined_state; |
| invoker_.AsyncInvoke<void>(RTC_FROM_HERE, signaling_thread_, |
| [this, new_combined_state] { |
| SignalConnectionState(new_combined_state); |
| }); |
| } |
| |
| if (all_done_gathering) { |
| new_gathering_state = cricket::kIceGatheringComplete; |
| } else if (any_gathering) { |
| new_gathering_state = cricket::kIceGatheringGathering; |
| } |
| if (ice_gathering_state_ != new_gathering_state) { |
| ice_gathering_state_ = new_gathering_state; |
| invoker_.AsyncInvoke<void>(RTC_FROM_HERE, signaling_thread_, |
| [this, new_gathering_state] { |
| SignalIceGatheringState(new_gathering_state); |
| }); |
| } |
| } |
| |
| void JsepTransportController::OnRtcpPacketReceived_n( |
| rtc::CopyOnWriteBuffer* packet, |
| int64_t packet_time_us) { |
| RTC_DCHECK(config_.rtcp_handler); |
| config_.rtcp_handler(*packet, packet_time_us); |
| } |
| |
| void JsepTransportController::OnDtlsHandshakeError( |
| rtc::SSLHandshakeError error) { |
| SignalDtlsHandshakeError(error); |
| } |
| |
| absl::optional<cricket::OpaqueTransportParameters> |
| JsepTransportController::GetTransportParameters(const std::string& mid) { |
| if (!(config_.use_datagram_transport || |
| config_.use_datagram_transport_for_data_channels)) { |
| return absl::nullopt; |
| } |
| |
| cricket::JsepTransport* transport = GetJsepTransportForMid(mid); |
| if (transport) { |
| absl::optional<cricket::OpaqueTransportParameters> params = |
| transport->GetTransportParameters(); |
| if (params) { |
| params->protocol = config_.media_transport_factory->GetTransportName(); |
| } |
| return params; |
| } |
| |
| RTC_DCHECK(!local_desc_ && !remote_desc_) |
| << "JsepTransport should exist for every mid once any description is set"; |
| |
| if (config_.use_datagram_transport_for_data_channels_receive_only) { |
| return absl::nullopt; |
| } |
| |
| // Need to generate a transport for the offer. |
| if (!offer_datagram_transport_) { |
| webrtc::MediaTransportSettings settings; |
| settings.is_caller = true; |
| settings.pre_shared_key = rtc::CreateRandomString(32); |
| settings.event_log = config_.event_log; |
| auto datagram_transport_or_error = |
| config_.media_transport_factory->CreateDatagramTransport( |
| network_thread_, settings); |
| |
| if (datagram_transport_or_error.ok()) { |
| offer_datagram_transport_ = |
| std::move(datagram_transport_or_error.value()); |
| } else { |
| RTC_LOG(LS_INFO) << "Unable to create datagram transport, error=" |
| << datagram_transport_or_error.error().message(); |
| } |
| } |
| |
| // We have prepared a transport for the offer, and can now use its parameters. |
| cricket::OpaqueTransportParameters params; |
| params.parameters = offer_datagram_transport_->GetTransportParameters(); |
| params.protocol = config_.media_transport_factory->GetTransportName(); |
| return params; |
| } |
| |
| } // namespace webrtc |