JsepTransportController process the entire SDP and handle the RTCP-mux, SRTP setup, BUNDLE related logic internally. This will replace the current TransportController. JsepTransport2 is used by the JsepTransportController which processes the transport part of SDP and owns the DtlsTransport created internally. JsepTransport2 will replace JsepTransport and be renamed eventually. Bug: webrtc:8587 Change-Id: Ib02dfa52fe9b7a5b8b132afcc8e4363eb8bd9cf4 Reviewed-on: https://webrtc-review.googlesource.com/48841 Commit-Queue: Zhi Huang <zhihuang@webrtc.org> Reviewed-by: Taylor Brandstetter <deadbeef@webrtc.org> Reviewed-by: Peter Thatcher <pthatcher@webrtc.org> Cr-Commit-Position: refs/heads/master@{#22164}
429 lines
14 KiB
C++
429 lines
14 KiB
C++
/*
|
|
* Copyright 2004 The WebRTC Project Authors. All rights reserved.
|
|
*
|
|
* Use of this source code is governed by a BSD-style license
|
|
* that can be found in the LICENSE file in the root of the source
|
|
* tree. An additional intellectual property rights grant can be found
|
|
* in the file PATENTS. All contributing project authors may
|
|
* be found in the AUTHORS file in the root of the source tree.
|
|
*/
|
|
|
|
#include "pc/jseptransport.h"
|
|
|
|
#include <memory>
|
|
#include <utility> // for std::pair
|
|
|
|
#include "api/candidate.h"
|
|
#include "p2p/base/p2pconstants.h"
|
|
#include "p2p/base/p2ptransportchannel.h"
|
|
#include "p2p/base/port.h"
|
|
#include "rtc_base/bind.h"
|
|
#include "rtc_base/checks.h"
|
|
#include "rtc_base/logging.h"
|
|
|
|
using webrtc::SdpType;
|
|
|
|
namespace cricket {
|
|
|
|
static bool BadTransportDescription(const std::string& desc,
|
|
std::string* err_desc) {
|
|
if (err_desc) {
|
|
*err_desc = desc;
|
|
}
|
|
RTC_LOG(LS_ERROR) << desc;
|
|
return false;
|
|
}
|
|
|
|
static bool VerifyIceParams(const TransportDescription& desc) {
|
|
// For legacy protocols.
|
|
if (desc.ice_ufrag.empty() && desc.ice_pwd.empty())
|
|
return true;
|
|
|
|
if (desc.ice_ufrag.length() < ICE_UFRAG_MIN_LENGTH ||
|
|
desc.ice_ufrag.length() > ICE_UFRAG_MAX_LENGTH) {
|
|
return false;
|
|
}
|
|
if (desc.ice_pwd.length() < ICE_PWD_MIN_LENGTH ||
|
|
desc.ice_pwd.length() > ICE_PWD_MAX_LENGTH) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
JsepTransport::JsepTransport(
|
|
const std::string& mid,
|
|
const rtc::scoped_refptr<rtc::RTCCertificate>& certificate)
|
|
: mid_(mid), certificate_(certificate) {}
|
|
|
|
JsepTransport::~JsepTransport() = default;
|
|
|
|
bool JsepTransport::AddChannel(DtlsTransportInternal* dtls, int component) {
|
|
if (channels_.find(component) != channels_.end()) {
|
|
RTC_LOG(LS_ERROR) << "Adding channel for component " << component
|
|
<< " twice.";
|
|
return false;
|
|
}
|
|
channels_[component] = dtls;
|
|
// Something's wrong if a channel is being added after a description is set.
|
|
// This may currently occur if rtcp-mux is negotiated, then a new m= section
|
|
// is added in a later offer/answer. But this is suboptimal and should be
|
|
// changed; we shouldn't support going from muxed to non-muxed.
|
|
// TODO(deadbeef): Once this is fixed, make the warning an error, and remove
|
|
// the calls to "ApplyXTransportDescription" below.
|
|
if (local_description_set_ || remote_description_set_) {
|
|
RTC_LOG(LS_WARNING) << "Adding new transport channel after "
|
|
"transport description already applied.";
|
|
}
|
|
bool ret = true;
|
|
std::string err;
|
|
if (local_description_set_) {
|
|
ret &= ApplyLocalTransportDescription(channels_[component], &err);
|
|
}
|
|
if (remote_description_set_) {
|
|
ret &= ApplyRemoteTransportDescription(channels_[component], &err);
|
|
}
|
|
if (local_description_set_ && remote_description_set_) {
|
|
ret &= ApplyNegotiatedTransportDescription(channels_[component], &err);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
bool JsepTransport::RemoveChannel(int component) {
|
|
auto it = channels_.find(component);
|
|
if (it == channels_.end()) {
|
|
RTC_LOG(LS_ERROR) << "Trying to remove channel for component " << component
|
|
<< ", which doesn't exist.";
|
|
return false;
|
|
}
|
|
channels_.erase(component);
|
|
return true;
|
|
}
|
|
|
|
bool JsepTransport::HasChannels() const {
|
|
return !channels_.empty();
|
|
}
|
|
|
|
void JsepTransport::SetLocalCertificate(
|
|
const rtc::scoped_refptr<rtc::RTCCertificate>& certificate) {
|
|
certificate_ = certificate;
|
|
}
|
|
|
|
bool JsepTransport::GetLocalCertificate(
|
|
rtc::scoped_refptr<rtc::RTCCertificate>* certificate) const {
|
|
if (!certificate_) {
|
|
return false;
|
|
}
|
|
|
|
*certificate = certificate_;
|
|
return true;
|
|
}
|
|
|
|
bool JsepTransport::SetLocalTransportDescription(
|
|
const TransportDescription& description,
|
|
SdpType type,
|
|
std::string* error_desc) {
|
|
bool ret = true;
|
|
|
|
if (!VerifyIceParams(description)) {
|
|
return BadTransportDescription("Invalid ice-ufrag or ice-pwd length",
|
|
error_desc);
|
|
}
|
|
|
|
bool ice_restarting =
|
|
local_description_set_ &&
|
|
IceCredentialsChanged(local_description_->ice_ufrag,
|
|
local_description_->ice_pwd, description.ice_ufrag,
|
|
description.ice_pwd);
|
|
local_description_.reset(new TransportDescription(description));
|
|
|
|
rtc::SSLFingerprint* local_fp =
|
|
local_description_->identity_fingerprint.get();
|
|
|
|
if (!local_fp) {
|
|
certificate_ = nullptr;
|
|
} else if (!VerifyCertificateFingerprint(certificate_.get(), local_fp,
|
|
error_desc)) {
|
|
return false;
|
|
}
|
|
|
|
for (const auto& kv : channels_) {
|
|
ret &= ApplyLocalTransportDescription(kv.second, error_desc);
|
|
}
|
|
if (!ret) {
|
|
return false;
|
|
}
|
|
|
|
// If PRANSWER/ANSWER is set, we should decide transport protocol type.
|
|
if (type == SdpType::kPrAnswer || type == SdpType::kAnswer) {
|
|
ret &= NegotiateTransportDescription(type, error_desc);
|
|
}
|
|
if (!ret) {
|
|
return false;
|
|
}
|
|
|
|
if (needs_ice_restart_ && ice_restarting) {
|
|
needs_ice_restart_ = false;
|
|
RTC_LOG(LS_VERBOSE) << "needs-ice-restart flag cleared for transport "
|
|
<< mid();
|
|
}
|
|
|
|
local_description_set_ = true;
|
|
return true;
|
|
}
|
|
|
|
bool JsepTransport::SetRemoteTransportDescription(
|
|
const TransportDescription& description,
|
|
SdpType type,
|
|
std::string* error_desc) {
|
|
bool ret = true;
|
|
|
|
if (!VerifyIceParams(description)) {
|
|
return BadTransportDescription("Invalid ice-ufrag or ice-pwd length",
|
|
error_desc);
|
|
}
|
|
|
|
remote_description_.reset(new TransportDescription(description));
|
|
for (const auto& kv : channels_) {
|
|
ret &= ApplyRemoteTransportDescription(kv.second, error_desc);
|
|
}
|
|
|
|
// If PRANSWER/ANSWER is set, we should decide transport protocol type.
|
|
if (type == SdpType::kPrAnswer || type == SdpType::kAnswer) {
|
|
ret = NegotiateTransportDescription(SdpType::kOffer, error_desc);
|
|
}
|
|
if (ret) {
|
|
remote_description_set_ = true;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
void JsepTransport::SetNeedsIceRestartFlag() {
|
|
if (!needs_ice_restart_) {
|
|
needs_ice_restart_ = true;
|
|
RTC_LOG(LS_VERBOSE) << "needs-ice-restart flag set for transport " << mid();
|
|
}
|
|
}
|
|
|
|
bool JsepTransport::NeedsIceRestart() const {
|
|
return needs_ice_restart_;
|
|
}
|
|
|
|
rtc::Optional<rtc::SSLRole> JsepTransport::GetSslRole() const {
|
|
return ssl_role_;
|
|
}
|
|
|
|
bool JsepTransport::GetStats(TransportStats* stats) {
|
|
stats->transport_name = mid();
|
|
stats->channel_stats.clear();
|
|
for (auto& kv : channels_) {
|
|
DtlsTransportInternal* dtls_transport = kv.second;
|
|
TransportChannelStats substats;
|
|
substats.component = kv.first;
|
|
dtls_transport->GetSrtpCryptoSuite(&substats.srtp_crypto_suite);
|
|
dtls_transport->GetSslCipherSuite(&substats.ssl_cipher_suite);
|
|
substats.dtls_state = dtls_transport->dtls_state();
|
|
if (!dtls_transport->ice_transport()->GetStats(
|
|
&substats.connection_infos, &substats.candidate_stats_list)) {
|
|
return false;
|
|
}
|
|
stats->channel_stats.push_back(substats);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool JsepTransport::VerifyCertificateFingerprint(
|
|
const rtc::RTCCertificate* certificate,
|
|
const rtc::SSLFingerprint* fingerprint,
|
|
std::string* error_desc) const {
|
|
if (!fingerprint) {
|
|
return BadTransportDescription("No fingerprint.", error_desc);
|
|
}
|
|
if (!certificate) {
|
|
return BadTransportDescription(
|
|
"Fingerprint provided but no identity available.", error_desc);
|
|
}
|
|
std::unique_ptr<rtc::SSLFingerprint> fp_tmp(rtc::SSLFingerprint::Create(
|
|
fingerprint->algorithm, certificate->identity()));
|
|
RTC_DCHECK(fp_tmp.get() != NULL);
|
|
if (*fp_tmp == *fingerprint) {
|
|
return true;
|
|
}
|
|
std::ostringstream desc;
|
|
desc << "Local fingerprint does not match identity. Expected: ";
|
|
desc << fp_tmp->ToString();
|
|
desc << " Got: " << fingerprint->ToString();
|
|
return BadTransportDescription(desc.str(), error_desc);
|
|
}
|
|
|
|
bool JsepTransport::ApplyLocalTransportDescription(
|
|
DtlsTransportInternal* dtls_transport,
|
|
std::string* error_desc) {
|
|
dtls_transport->ice_transport()->SetIceParameters(
|
|
local_description_->GetIceParameters());
|
|
return true;
|
|
}
|
|
|
|
bool JsepTransport::ApplyRemoteTransportDescription(
|
|
DtlsTransportInternal* dtls_transport,
|
|
std::string* error_desc) {
|
|
dtls_transport->ice_transport()->SetRemoteIceParameters(
|
|
remote_description_->GetIceParameters());
|
|
dtls_transport->ice_transport()->SetRemoteIceMode(
|
|
remote_description_->ice_mode);
|
|
return true;
|
|
}
|
|
|
|
bool JsepTransport::ApplyNegotiatedTransportDescription(
|
|
DtlsTransportInternal* dtls_transport,
|
|
std::string* error_desc) {
|
|
// Set SSL role. Role must be set before fingerprint is applied, which
|
|
// initiates DTLS setup.
|
|
if (ssl_role_ && !dtls_transport->SetDtlsRole(*ssl_role_)) {
|
|
return BadTransportDescription("Failed to set SSL role for the channel.",
|
|
error_desc);
|
|
}
|
|
// Apply remote fingerprint.
|
|
if (!dtls_transport->SetRemoteFingerprint(
|
|
remote_fingerprint_->algorithm,
|
|
reinterpret_cast<const uint8_t*>(remote_fingerprint_->digest.data()),
|
|
remote_fingerprint_->digest.size())) {
|
|
return BadTransportDescription("Failed to apply remote fingerprint.",
|
|
error_desc);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool JsepTransport::NegotiateTransportDescription(
|
|
SdpType local_description_type,
|
|
std::string* error_desc) {
|
|
if (!local_description_ || !remote_description_) {
|
|
const std::string msg =
|
|
"Applying an answer transport description "
|
|
"without applying any offer.";
|
|
return BadTransportDescription(msg, error_desc);
|
|
}
|
|
rtc::SSLFingerprint* local_fp =
|
|
local_description_->identity_fingerprint.get();
|
|
rtc::SSLFingerprint* remote_fp =
|
|
remote_description_->identity_fingerprint.get();
|
|
if (remote_fp && local_fp) {
|
|
remote_fingerprint_.reset(new rtc::SSLFingerprint(*remote_fp));
|
|
if (!NegotiateRole(local_description_type, error_desc)) {
|
|
return false;
|
|
}
|
|
} else if (local_fp && (local_description_type == SdpType::kAnswer)) {
|
|
return BadTransportDescription(
|
|
"Local fingerprint supplied when caller didn't offer DTLS.",
|
|
error_desc);
|
|
} else {
|
|
// We are not doing DTLS
|
|
remote_fingerprint_.reset(new rtc::SSLFingerprint("", nullptr, 0));
|
|
}
|
|
// Now that we have negotiated everything, push it downward.
|
|
// Note that we cache the result so that if we have race conditions
|
|
// between future SetRemote/SetLocal invocations and new channel
|
|
// creation, we have the negotiation state saved until a new
|
|
// negotiation happens.
|
|
for (const auto& kv : channels_) {
|
|
if (!ApplyNegotiatedTransportDescription(kv.second, error_desc)) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool JsepTransport::NegotiateRole(SdpType local_description_type,
|
|
std::string* error_desc) {
|
|
if (!local_description_ || !remote_description_) {
|
|
const std::string msg =
|
|
"Local and Remote description must be set before "
|
|
"transport descriptions are negotiated";
|
|
return BadTransportDescription(msg, error_desc);
|
|
}
|
|
|
|
// From RFC 4145, section-4.1, The following are the values that the
|
|
// 'setup' attribute can take in an offer/answer exchange:
|
|
// Offer Answer
|
|
// ________________
|
|
// active passive / holdconn
|
|
// passive active / holdconn
|
|
// actpass active / passive / holdconn
|
|
// holdconn holdconn
|
|
//
|
|
// Set the role that is most conformant with RFC 5763, Section 5, bullet 1
|
|
// The endpoint MUST use the setup attribute defined in [RFC4145].
|
|
// The endpoint that is the offerer MUST use the setup attribute
|
|
// value of setup:actpass and be prepared to receive a client_hello
|
|
// before it receives the answer. The answerer MUST use either a
|
|
// setup attribute value of setup:active or setup:passive. Note that
|
|
// if the answerer uses setup:passive, then the DTLS handshake will
|
|
// not begin until the answerer is received, which adds additional
|
|
// latency. setup:active allows the answer and the DTLS handshake to
|
|
// occur in parallel. Thus, setup:active is RECOMMENDED. Whichever
|
|
// party is active MUST initiate a DTLS handshake by sending a
|
|
// ClientHello over each flow (host/port quartet).
|
|
// IOW - actpass and passive modes should be treated as server and
|
|
// active as client.
|
|
ConnectionRole local_connection_role = local_description_->connection_role;
|
|
ConnectionRole remote_connection_role = remote_description_->connection_role;
|
|
|
|
bool is_remote_server = false;
|
|
if (local_description_type == SdpType::kOffer) {
|
|
if (local_connection_role != CONNECTIONROLE_ACTPASS) {
|
|
return BadTransportDescription(
|
|
"Offerer must use actpass value for setup attribute.", error_desc);
|
|
}
|
|
|
|
if (remote_connection_role == CONNECTIONROLE_ACTIVE ||
|
|
remote_connection_role == CONNECTIONROLE_PASSIVE ||
|
|
remote_connection_role == CONNECTIONROLE_NONE) {
|
|
is_remote_server = (remote_connection_role == CONNECTIONROLE_PASSIVE);
|
|
} else {
|
|
const std::string msg =
|
|
"Answerer must use either active or passive value "
|
|
"for setup attribute.";
|
|
return BadTransportDescription(msg, error_desc);
|
|
}
|
|
// If remote is NONE or ACTIVE it will act as client.
|
|
} else {
|
|
if (remote_connection_role != CONNECTIONROLE_ACTPASS &&
|
|
remote_connection_role != CONNECTIONROLE_NONE) {
|
|
// Accept a remote role attribute that's not "actpass", but matches the
|
|
// current negotiated role. This is allowed by dtls-sdp, though our
|
|
// implementation will never generate such an offer as it's not
|
|
// recommended.
|
|
//
|
|
// See https://datatracker.ietf.org/doc/html/draft-ietf-mmusic-dtls-sdp,
|
|
// section 5.5.
|
|
if (!ssl_role_ ||
|
|
(*ssl_role_ == rtc::SSL_CLIENT &&
|
|
remote_connection_role == CONNECTIONROLE_ACTIVE) ||
|
|
(*ssl_role_ == rtc::SSL_SERVER &&
|
|
remote_connection_role == CONNECTIONROLE_PASSIVE)) {
|
|
return BadTransportDescription(
|
|
"Offerer must use actpass value or current negotiated role for "
|
|
"setup attribute.",
|
|
error_desc);
|
|
}
|
|
}
|
|
|
|
if (local_connection_role == CONNECTIONROLE_ACTIVE ||
|
|
local_connection_role == CONNECTIONROLE_PASSIVE) {
|
|
is_remote_server = (local_connection_role == CONNECTIONROLE_ACTIVE);
|
|
} else {
|
|
const std::string msg =
|
|
"Answerer must use either active or passive value "
|
|
"for setup attribute.";
|
|
return BadTransportDescription(msg, error_desc);
|
|
}
|
|
|
|
// If local is passive, local will act as server.
|
|
}
|
|
|
|
ssl_role_.emplace(is_remote_server ? rtc::SSL_CLIENT : rtc::SSL_SERVER);
|
|
return true;
|
|
}
|
|
|
|
} // namespace cricket
|