blob: d05339e1a118ab9ea06a37cc29fc66d79f545bca [file] [log] [blame]
/*
* 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/jseptransportcontroller.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_ICEGATHERINGSTATE,
MSG_ICECANDIDATESGATHERED,
};
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;
};
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");
}
}
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,
Config config)
: signaling_thread_(signaling_thread),
network_thread_(network_thread),
port_allocator_(port_allocator),
config_(config) {
// The |transport_observer| is assumed to be non-null.
RTC_DCHECK(config_.transport_observer);
}
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();
}
cricket::DtlsTransportInternal* JsepTransportController::GetDtlsTransport(
const std::string& mid) const {
auto jsep_transport = GetJsepTransportForMid(mid);
if (!jsep_transport) {
return nullptr;
}
return jsep_transport->rtp_dtls_transport();
}
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();
}
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();
}
rtc::Optional<rtc::SSLRole> JsepTransportController::GetDtlsRole(
const std::string& mid) const {
if (!network_thread_->IsCurrent()) {
return network_thread_->Invoke<rtc::Optional<rtc::SSLRole>>(
RTC_FROM_HERE, [&] { return GetDtlsRole(mid); });
}
const cricket::JsepTransport* t = GetJsepTransportForMid(mid);
if (!t) {
return rtc::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.ToString();
}
}
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) {
auto 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::SetMetricsObserver(
webrtc::MetricsObserverInterface* metrics_observer) {
if (!network_thread_->IsCurrent()) {
network_thread_->Invoke<void>(
RTC_FROM_HERE, [=] { SetMetricsObserver(metrics_observer); });
return;
}
metrics_observer_ = metrics_observer;
for (auto& dtls : GetDtlsTransports()) {
dtls->ice_transport()->SetMetricsObserver(metrics_observer);
}
}
std::unique_ptr<cricket::DtlsTransportInternal>
JsepTransportController::CreateDtlsTransport(const std::string& transport_name,
bool rtcp) {
RTC_DCHECK(network_thread_->IsCurrent());
int component = rtcp ? cricket::ICE_CANDIDATE_COMPONENT_RTCP
: cricket::ICE_CANDIDATE_COMPONENT_RTP;
std::unique_ptr<cricket::DtlsTransportInternal> dtls;
if (config_.external_transport_factory) {
auto ice = config_.external_transport_factory->CreateIceTransport(
transport_name, component);
dtls = config_.external_transport_factory->CreateDtlsTransport(
std::move(ice), config_.crypto_options);
} else {
auto ice = rtc::MakeUnique<cricket::P2PTransportChannel>(
transport_name, component, port_allocator_);
dtls = rtc::MakeUnique<cricket::DtlsTransport>(std::move(ice),
config_.crypto_options);
}
RTC_DCHECK(dtls);
dtls->SetSslMaxProtocolVersion(config_.ssl_max_version);
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 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()->SignalCandidatesRemoved.connect(
this, &JsepTransportController::OnTransportCandidatesRemoved_n);
dtls->ice_transport()->SignalRoleConflict.connect(
this, &JsepTransportController::OnTransportRoleConflict_n);
dtls->ice_transport()->SignalStateChanged.connect(
this, &JsepTransportController::OnTransportStateChanged_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 =
rtc::MakeUnique<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 =
rtc::MakeUnique<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 = rtc::MakeUnique<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);
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;
}
void JsepTransportController::OnMessage(rtc::Message* pmsg) {
RTC_DCHECK(signaling_thread_->IsCurrent());
switch (pmsg->message_id) {
case MSG_ICECONNECTIONSTATE: {
rtc::TypedMessageData<cricket::IceConnectionState>* data =
static_cast<rtc::TypedMessageData<cricket::IceConnectionState>*>(
pmsg->pdata);
SignalIceConnectionState(data->data());
delete data;
break;
}
case MSG_ICEGATHERINGSTATE: {
rtc::TypedMessageData<cricket::IceGatheringState>* data =
static_cast<rtc::TypedMessageData<cricket::IceGatheringState>*>(
pmsg->pdata);
SignalIceGatheringState(data->data());
delete data;
break;
}
case MSG_ICECANDIDATESGATHERED: {
CandidatesData* data = static_cast<CandidatesData*>(pmsg->pdata);
SignalIceCandidatesGathered(data->transport_name, data->candidates);
delete data;
break;
}
default:
RTC_NOTREACHED();
}
}
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;
if (bundle_group_) {
merged_encrypted_extension_ids =
MergeEncryptedHeaderExtensionIdsForBundle(description);
}
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(content_info);
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::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;
if (bundled_mid() && content_info.name == *bundled_mid()) {
extension_ids = merged_encrypted_extension_ids;
} else {
extension_ids = GetEncryptedHeaderExtensionIds(content_info);
}
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);
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 (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 (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 (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)) {
const std::string* new_bundled_mid = new_bundle_group->FirstContentName();
if (bundled_mid() && new_bundled_mid &&
*bundled_mid() != *new_bundled_mid) {
return RTCError(RTCErrorType::UNSUPPORTED_OPERATION,
"Changing the negotiated BUNDLE-tag is not supported.");
}
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 (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 (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)) {
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->rtp_dtls_transport());
}
void JsepTransportController::RemoveTransportForMid(const std::string& mid) {
bool ret =
config_.transport_observer->OnTransportChanged(mid, 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(
cricket::ContentInfo content_info,
cricket::TransportInfo transport_info,
const std::vector<int>& encrypted_extension_ids,
int rtp_abs_sendtime_extn_id) {
const cricket::MediaContentDescription* content_desc =
static_cast<const cricket::MediaContentDescription*>(
content_info.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);
}
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 =
static_cast<const cricket::MediaContentDescription*>(
content_info.description);
if (!config_.crypto_options.enable_encrypted_rtp_header_extensions) {
return std::vector<int>();
}
std::vector<int> encrypted_header_extension_ids;
for (auto extension : content_desc->rtp_header_extensions()) {
if (!extension.encrypt) {
continue;
}
auto it = std::find(encrypted_header_extension_ids.begin(),
encrypted_header_extension_ids.end(), extension.id);
if (it == encrypted_header_extension_ids.end()) {
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) {
auto it = std::find(merged_ids.begin(), merged_ids.end(), id);
if (it == merged_ids.end()) {
merged_ids.push_back(id);
}
}
}
}
return merged_ids;
}
int JsepTransportController::GetRtpAbsSendTimeHeaderExtensionId(
const cricket::ContentInfo& content_info) {
if (!config_.enable_external_auth) {
return -1;
}
const cricket::MediaContentDescription* content_desc =
static_cast<const cricket::MediaContentDescription*>(
content_info.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();
}
RTCError JsepTransportController::MaybeCreateJsepTransport(
const cricket::ContentInfo& content_info) {
RTC_DCHECK(network_thread_->IsCurrent());
cricket::JsepTransport* transport = GetJsepTransportByName(content_info.name);
if (transport) {
return RTCError::OK();
}
const cricket::MediaContentDescription* content_desc =
static_cast<const cricket::MediaContentDescription*>(
content_info.description);
if (certificate_ && !content_desc->cryptos().empty()) {
return RTCError(RTCErrorType::INVALID_PARAMETER,
"SDES and DTLS-SRTP cannot be enabled at the same time.");
}
std::unique_ptr<cricket::DtlsTransportInternal> rtp_dtls_transport =
CreateDtlsTransport(content_info.name, /*rtcp =*/false);
std::unique_ptr<cricket::DtlsTransportInternal> rtcp_dtls_transport;
if (config_.rtcp_mux_policy !=
PeerConnectionInterface::kRtcpMuxPolicyRequire &&
content_info.type == cricket::MediaProtocolType::kRtp) {
rtcp_dtls_transport =
CreateDtlsTransport(content_info.name, /*rtcp =*/true);
}
std::unique_ptr<RtpTransport> unencrypted_rtp_transport;
std::unique_ptr<SrtpTransport> sdes_transport;
std::unique_ptr<DtlsSrtpTransport> dtls_srtp_transport;
if (config_.disable_encryption) {
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());
} else {
dtls_srtp_transport = CreateDtlsSrtpTransport(
content_info.name, rtp_dtls_transport.get(), rtcp_dtls_transport.get());
}
std::unique_ptr<cricket::JsepTransport> jsep_transport =
rtc::MakeUnique<cricket::JsepTransport>(
content_info.name, certificate_, std::move(unencrypted_rtp_transport),
std::move(sdes_transport), std::move(dtls_srtp_transport),
std::move(rtp_dtls_transport), std::move(rtcp_dtls_transport));
jsep_transport->SignalRtcpMuxActive.connect(
this, &JsepTransportController::UpdateAggregateStates_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());
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::vector<cricket::Candidate> candidates;
candidates.push_back(candidate);
CandidatesData* data =
new CandidatesData(transport->transport_name(), candidates);
signaling_thread_->Post(RTC_FROM_HERE, this, MSG_ICECANDIDATESGATHERED, data);
}
void JsepTransportController::OnTransportCandidatesRemoved_n(
cricket::IceTransportInternal* transport,
const cricket::Candidates& candidates) {
invoker_.AsyncInvoke<void>(
RTC_FROM_HERE, signaling_thread_,
rtc::Bind(&JsepTransportController::OnTransportCandidatesRemoved, this,
candidates));
}
void JsepTransportController::OnTransportCandidatesRemoved(
const cricket::Candidates& candidates) {
RTC_DCHECK(signaling_thread_->IsCurrent());
SignalIceCandidatesRemoved(candidates);
}
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::UpdateAggregateStates_n() {
RTC_DCHECK(network_thread_->IsCurrent());
auto dtls_transports = GetDtlsTransports();
cricket::IceConnectionState new_connection_state =
cricket::kIceConnectionConnecting;
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();
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;
}
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;
signaling_thread_->Post(
RTC_FROM_HERE, this, MSG_ICECONNECTIONSTATE,
new rtc::TypedMessageData<cricket::IceConnectionState>(
new_connection_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;
signaling_thread_->Post(
RTC_FROM_HERE, this, MSG_ICEGATHERINGSTATE,
new rtc::TypedMessageData<cricket::IceGatheringState>(
new_gathering_state));
}
}
void JsepTransportController::OnDtlsHandshakeError(
rtc::SSLHandshakeError error) {
SignalDtlsHandshakeError(error);
}
} // namespace webrtc