Open
Bug 1284104
Opened 9 years ago
Updated 3 years ago
PCMedia doesn't check DTLS state in DtlsConnected_{s,m}
Categories
(Core :: WebRTC: Signaling, defect, P4)
Core
WebRTC: Signaling
Tracking
()
NEW
People
(Reporter: ekr, Assigned: mt)
Details
The code below doesn't check whether DTLS completed successfully, just that the state changed:
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
#include <ostream>
#include <string>
#include <vector>
#include "CSFLog.h"
#include "nspr.h"
#include "nricectx.h"
#include "nricemediastream.h"
#include "MediaPipelineFactory.h"
#include "PeerConnectionImpl.h"
#include "PeerConnectionMedia.h"
#include "AudioConduit.h"
#include "VideoConduit.h"
#include "runnable_utils.h"
#include "transportlayerice.h"
#include "transportlayerdtls.h"
#include "signaling/src/jsep/JsepSession.h"
#include "signaling/src/jsep/JsepTransport.h"
#ifdef USE_FAKE_STREAMS
#include "DOMMediaStream.h"
#include "FakeMediaStreams.h"
#else
#include "MediaSegment.h"
#ifdef MOZILLA_INTERNAL_API
#include "MediaStreamGraph.h"
#endif
#endif
#ifndef USE_FAKE_MEDIA_STREAMS
#include "MediaStreamGraphImpl.h"
#endif
#include "nsNetCID.h"
#include "nsNetUtil.h"
#include "nsIURI.h"
#include "nsIScriptSecurityManager.h"
#include "nsICancelable.h"
#include "nsIDocument.h"
#include "nsILoadInfo.h"
#include "nsIContentPolicy.h"
#include "nsIProxyInfo.h"
#include "nsIProtocolProxyService.h"
#include "nsProxyRelease.h"
#if !defined(MOZILLA_EXTERNAL_LINKAGE)
#include "MediaStreamList.h"
#include "nsIScriptGlobalObject.h"
#include "mozilla/Preferences.h"
#include "mozilla/dom/RTCStatsReportBinding.h"
#include "MediaStreamTrack.h"
#include "VideoStreamTrack.h"
#endif
namespace mozilla {
using namespace dom;
static const char* logTag = "PeerConnectionMedia";
nsresult
PeerConnectionMedia::ReplaceTrack(const std::string& aOldStreamId,
const std::string& aOldTrackId,
MediaStreamTrack& aNewTrack,
const std::string& aNewStreamId,
const std::string& aNewTrackId)
{
RefPtr<LocalSourceStreamInfo> oldInfo(GetLocalStreamById(aOldStreamId));
if (!oldInfo) {
CSFLogError(logTag, "Failed to find stream id %s", aOldStreamId.c_str());
return NS_ERROR_NOT_AVAILABLE;
}
nsresult rv = AddTrack(*aNewTrack.mOwningStream, aNewStreamId,
aNewTrack, aNewTrackId);
NS_ENSURE_SUCCESS(rv, rv);
RefPtr<LocalSourceStreamInfo> newInfo(GetLocalStreamById(aNewStreamId));
if (!newInfo) {
CSFLogError(logTag, "Failed to add track id %s", aNewTrackId.c_str());
MOZ_ASSERT(false);
return NS_ERROR_FAILURE;
}
rv = newInfo->TakePipelineFrom(oldInfo, aOldTrackId, aNewTrack, aNewTrackId);
NS_ENSURE_SUCCESS(rv, rv);
return RemoveLocalTrack(aOldStreamId, aOldTrackId);
}
static void
PipelineReleaseRef_m(RefPtr<MediaPipeline> pipeline)
{}
static void
PipelineDetachTransport_s(RefPtr<MediaPipeline> pipeline,
nsCOMPtr<nsIThread> mainThread)
{
pipeline->DetachTransport_s();
mainThread->Dispatch(
// Make sure we let go of our reference before dispatching
// If the dispatch fails, well, we're hosed anyway.
WrapRunnableNM(PipelineReleaseRef_m, pipeline.forget()),
NS_DISPATCH_NORMAL);
}
void
SourceStreamInfo::EndTrack(MediaStream* stream, dom::MediaStreamTrack* track)
{
if (!stream || !stream->AsSourceStream()) {
return;
}
#if !defined(MOZILLA_EXTERNAL_LINKAGE)
class Message : public ControlMessage {
public:
Message(MediaStream* stream, TrackID track)
: ControlMessage(stream),
track_id_(track) {}
virtual void Run() override {
mStream->AsSourceStream()->EndTrack(track_id_);
}
private:
TrackID track_id_;
};
stream->GraphImpl()->AppendMessage(
MakeUnique<Message>(stream, track->mTrackID));
#endif
}
void
SourceStreamInfo::RemoveTrack(const std::string& trackId)
{
mTracks.erase(trackId);
RefPtr<MediaPipeline> pipeline = GetPipelineByTrackId_m(trackId);
if (pipeline) {
mPipelines.erase(trackId);
pipeline->ShutdownMedia_m();
mParent->GetSTSThread()->Dispatch(
WrapRunnableNM(PipelineDetachTransport_s,
pipeline.forget(),
mParent->GetMainThread()),
NS_DISPATCH_NORMAL);
}
}
void SourceStreamInfo::DetachTransport_s()
{
ASSERT_ON_THREAD(mParent->GetSTSThread());
// walk through all the MediaPipelines and call the shutdown
// transport functions. Must be on the STS thread.
for (auto it = mPipelines.begin(); it != mPipelines.end(); ++it) {
it->second->DetachTransport_s();
}
}
void SourceStreamInfo::DetachMedia_m()
{
ASSERT_ON_THREAD(mParent->GetMainThread());
// walk through all the MediaPipelines and call the shutdown
// media functions. Must be on the main thread.
for (auto it = mPipelines.begin(); it != mPipelines.end(); ++it) {
it->second->ShutdownMedia_m();
}
mMediaStream = nullptr;
}
already_AddRefed<PeerConnectionImpl>
PeerConnectionImpl::Constructor(const dom::GlobalObject& aGlobal, ErrorResult& rv)
{
RefPtr<PeerConnectionImpl> pc = new PeerConnectionImpl(&aGlobal);
CSFLogDebug(logTag, "Created PeerConnection: %p", pc.get());
return pc.forget();
}
PeerConnectionImpl* PeerConnectionImpl::CreatePeerConnection()
{
PeerConnectionImpl *pc = new PeerConnectionImpl();
CSFLogDebug(logTag, "Created PeerConnection: %p", pc);
return pc;
}
NS_IMETHODIMP PeerConnectionMedia::ProtocolProxyQueryHandler::
OnProxyAvailable(nsICancelable *request,
nsIChannel *aChannel,
nsIProxyInfo *proxyinfo,
nsresult result) {
if (!pcm_->mProxyRequest) {
// PeerConnectionMedia is no longer waiting
return NS_OK;
}
CSFLogInfo(logTag, "%s: Proxy Available: %d", __FUNCTION__, (int)result);
if (NS_SUCCEEDED(result) && proxyinfo) {
SetProxyOnPcm(*proxyinfo);
}
pcm_->mProxyResolveCompleted = true;
pcm_->mProxyRequest = nullptr;
pcm_->FlushIceCtxOperationQueueIfReady();
return NS_OK;
}
void
PeerConnectionMedia::ProtocolProxyQueryHandler::SetProxyOnPcm(
nsIProxyInfo& proxyinfo)
{
CSFLogInfo(logTag, "%s: Had proxyinfo", __FUNCTION__);
nsresult rv;
nsCString httpsProxyHost;
int32_t httpsProxyPort;
rv = proxyinfo.GetHost(httpsProxyHost);
if (NS_FAILED(rv)) {
CSFLogError(logTag, "%s: Failed to get proxy server host", __FUNCTION__);
return;
}
rv = proxyinfo.GetPort(&httpsProxyPort);
if (NS_FAILED(rv)) {
CSFLogError(logTag, "%s: Failed to get proxy server port", __FUNCTION__);
return;
}
if (pcm_->mIceCtxHdlr.get()) {
assert(httpsProxyPort >= 0 && httpsProxyPort < (1 << 16));
// Note that this could check if PrivacyRequested() is set on the PC and
// remove "webrtc" from the ALPN list. But that would only work if the PC
// was constructed with a peerIdentity constraint, not when isolated
// streams are added. If we ever need to signal to the proxy that the
// media is isolated, then we would need to restructure this code.
pcm_->mProxyServer.reset(
new NrIceProxyServer(httpsProxyHost.get(),
static_cast<uint16_t>(httpsProxyPort),
"webrtc,c-webrtc"));
} else {
CSFLogError(logTag, "%s: Failed to set proxy server (ICE ctx unavailable)",
__FUNCTION__);
}
}
NS_IMPL_ISUPPORTS(PeerConnectionMedia::ProtocolProxyQueryHandler, nsIProtocolProxyCallback)
PeerConnectionMedia::PeerConnectionMedia(PeerConnectionImpl *parent)
: mParent(parent),
mParentHandle(parent->GetHandle()),
mParentName(parent->GetName()),
mIceCtxHdlr(nullptr),
mDNSResolver(new NrIceResolver()),
mUuidGen(MakeUnique<PCUuidGenerator>()),
mMainThread(mParent->GetMainThread()),
mSTSThread(mParent->GetSTSThread()),
mProxyResolveCompleted(false),
mIceRestartState(ICE_RESTART_NONE) {
}
nsresult
PeerConnectionMedia::InitProxy()
{
#if !defined(MOZILLA_EXTERNAL_LINKAGE)
// Allow mochitests to disable this, since mochitest configures a fake proxy
// that serves up content.
bool disable = Preferences::GetBool("media.peerconnection.disable_http_proxy",
false);
if (disable) {
mProxyResolveCompleted = true;
return NS_OK;
}
#endif
nsresult rv;
nsCOMPtr<nsIProtocolProxyService> pps =
do_GetService(NS_PROTOCOLPROXYSERVICE_CONTRACTID, &rv);
if (NS_FAILED(rv)) {
CSFLogError(logTag, "%s: Failed to get proxy service: %d", __FUNCTION__, (int)rv);
return NS_ERROR_FAILURE;
}
// We use the following URL to find the "default" proxy address for all HTTPS
// connections. We will only attempt one HTTP(S) CONNECT per peer connection.
// "example.com" is guaranteed to be unallocated and should return the best default.
nsCOMPtr<nsIURI> fakeHttpsLocation;
rv = NS_NewURI(getter_AddRefs(fakeHttpsLocation), "https://example.com");
if (NS_FAILED(rv)) {
CSFLogError(logTag, "%s: Failed to set URI: %d", __FUNCTION__, (int)rv);
return NS_ERROR_FAILURE;
}
nsCOMPtr<nsIScriptSecurityManager> secMan(
do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID, &rv));
if (NS_FAILED(rv)) {
CSFLogError(logTag, "%s: Failed to get IOService: %d",
__FUNCTION__, (int)rv);
CSFLogError(logTag, "%s: Failed to get securityManager: %d", __FUNCTION__, (int)rv);
return NS_ERROR_FAILURE;
}
nsCOMPtr<nsIPrincipal> systemPrincipal;
rv = secMan->GetSystemPrincipal(getter_AddRefs(systemPrincipal));
if (NS_FAILED(rv)) {
CSFLogError(logTag, "%s: Failed to get systemPrincipal: %d", __FUNCTION__, (int)rv);
return NS_ERROR_FAILURE;
}
nsCOMPtr<nsIChannel> channel;
rv = NS_NewChannel(getter_AddRefs(channel),
fakeHttpsLocation,
systemPrincipal,
nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
nsIContentPolicy::TYPE_OTHER);
if (NS_FAILED(rv)) {
CSFLogError(logTag, "%s: Failed to get channel from URI: %d",
__FUNCTION__, (int)rv);
return NS_ERROR_FAILURE;
}
RefPtr<ProtocolProxyQueryHandler> handler = new ProtocolProxyQueryHandler(this);
rv = pps->AsyncResolve(channel,
nsIProtocolProxyService::RESOLVE_PREFER_HTTPS_PROXY |
nsIProtocolProxyService::RESOLVE_ALWAYS_TUNNEL,
handler, getter_AddRefs(mProxyRequest));
if (NS_FAILED(rv)) {
CSFLogError(logTag, "%s: Failed to resolve protocol proxy: %d", __FUNCTION__, (int)rv);
return NS_ERROR_FAILURE;
}
return NS_OK;
}
nsresult PeerConnectionMedia::Init(const std::vector<NrIceStunServer>& stun_servers,
const std::vector<NrIceTurnServer>& turn_servers,
NrIceCtx::Policy policy)
{
nsresult rv = InitProxy();
NS_ENSURE_SUCCESS(rv, rv);
#if !defined(MOZILLA_EXTERNAL_LINKAGE)
bool ice_tcp = Preferences::GetBool("media.peerconnection.ice.tcp", false);
#else
bool ice_tcp = false;
#endif
bool default_address_only = GetPrefDefaultAddressOnly();
// TODO(ekr@rtfm.com): need some way to set not offerer later
// Looks like a bug in the NrIceCtx API.
mIceCtxHdlr = NrIceCtxHandler::Create("PC:" + mParentName,
true, // Offerer
mParent->GetAllowIceLoopback(),
ice_tcp,
mParent->GetAllowIceLinkLocal(),
default_address_only,
policy);
if(!mIceCtxHdlr) {
CSFLogError(logTag, "%s: Failed to create Ice Context", __FUNCTION__);
return NS_ERROR_FAILURE;
}
if (NS_FAILED(rv = mIceCtxHdlr->ctx()->SetStunServers(stun_servers))) {
CSFLogError(logTag, "%s: Failed to set stun servers", __FUNCTION__);
return rv;
}
// Give us a way to globally turn off TURN support
#if !defined(MOZILLA_EXTERNAL_LINKAGE)
bool disabled = Preferences::GetBool("media.peerconnection.turn.disable", false);
#else
bool disabled = false;
#endif
if (!disabled) {
if (NS_FAILED(rv = mIceCtxHdlr->ctx()->SetTurnServers(turn_servers))) {
CSFLogError(logTag, "%s: Failed to set turn servers", __FUNCTION__);
return rv;
}
} else if (turn_servers.size() != 0) {
CSFLogError(logTag, "%s: Setting turn servers disabled", __FUNCTION__);
}
if (NS_FAILED(rv = mDNSResolver->Init())) {
CSFLogError(logTag, "%s: Failed to initialize dns resolver", __FUNCTION__);
return rv;
}
if (NS_FAILED(rv =
mIceCtxHdlr->ctx()->SetResolver(mDNSResolver->AllocateResolver()))) {
CSFLogError(logTag, "%s: Failed to get dns resolver", __FUNCTION__);
return rv;
}
ConnectSignals(mIceCtxHdlr->ctx().get());
return NS_OK;
}
void
PeerConnectionMedia::EnsureTransports(const JsepSession& aSession)
{
auto transports = aSession.GetTransports();
for (size_t i = 0; i < transports.size(); ++i) {
RefPtr<JsepTransport> transport = transports[i];
RUN_ON_THREAD(
GetSTSThread(),
WrapRunnable(RefPtr<PeerConnectionMedia>(this),
&PeerConnectionMedia::EnsureTransport_s,
i,
transport->mComponents),
NS_DISPATCH_NORMAL);
}
GatherIfReady();
}
void
PeerConnectionMedia::EnsureTransport_s(size_t aLevel, size_t aComponentCount)
{
RefPtr<NrIceMediaStream> stream(mIceCtxHdlr->ctx()->GetStream(aLevel));
if (!stream) {
CSFLogDebug(logTag, "%s: Creating ICE media stream=%u components=%u",
mParentHandle.c_str(),
static_cast<unsigned>(aLevel),
static_cast<unsigned>(aComponentCount));
std::ostringstream os;
os << mParentName << " aLevel=" << aLevel;
RefPtr<NrIceMediaStream> stream =
mIceCtxHdlr->CreateStream(os.str().c_str(),
aComponentCount);
if (!stream) {
CSFLogError(logTag, "Failed to create ICE stream.");
return;
}
stream->SetLevel(aLevel);
stream->SignalReady.connect(this, &PeerConnectionMedia::IceStreamReady_s);
stream->SignalCandidate.connect(this,
&PeerConnectionMedia::OnCandidateFound_s);
mIceCtxHdlr->ctx()->SetStream(aLevel, stream);
}
}
void
PeerConnectionMedia::ActivateOrRemoveTransports(const JsepSession& aSession)
{
auto transports = aSession.GetTransports();
for (size_t i = 0; i < transports.size(); ++i) {
RefPtr<JsepTransport> transport = transports[i];
std::string ufrag;
std::string pwd;
std::vector<std::string> candidates;
if (transport->mComponents) {
MOZ_ASSERT(transport->mIce);
CSFLogDebug(logTag, "Transport %u is active", static_cast<unsigned>(i));
ufrag = transport->mIce->GetUfrag();
pwd = transport->mIce->GetPassword();
candidates = transport->mIce->GetCandidates();
} else {
CSFLogDebug(logTag, "Transport %u is disabled", static_cast<unsigned>(i));
// Make sure the MediaPipelineFactory doesn't try to use these.
RemoveTransportFlow(i, false);
RemoveTransportFlow(i, true);
}
RUN_ON_THREAD(
GetSTSThread(),
WrapRunnable(RefPtr<PeerConnectionMedia>(this),
&PeerConnectionMedia::ActivateOrRemoveTransport_s,
i,
transport->mComponents,
ufrag,
pwd,
candidates),
NS_DISPATCH_NORMAL);
}
// We can have more streams than m-lines due to rollback.
RUN_ON_THREAD(
GetSTSThread(),
WrapRunnable(RefPtr<PeerConnectionMedia>(this),
&PeerConnectionMedia::RemoveTransportsAtOrAfter_s,
transports.size()),
NS_DISPATCH_NORMAL);
}
void
PeerConnectionMedia::ActivateOrRemoveTransport_s(
size_t aMLine,
size_t aComponentCount,
const std::string& aUfrag,
const std::string& aPassword,
const std::vector<std::string>& aCandidateList) {
if (!aComponentCount) {
CSFLogDebug(logTag, "%s: Removing ICE media stream=%u",
mParentHandle.c_str(),
static_cast<unsigned>(aMLine));
mIceCtxHdlr->ctx()->SetStream(aMLine, nullptr);
return;
}
RefPtr<NrIceMediaStream> stream(mIceCtxHdlr->ctx()->GetStream(aMLine));
if (!stream) {
MOZ_ASSERT(false);
return;
}
if (!stream->HasParsedAttributes()) {
CSFLogDebug(logTag, "%s: Activating ICE media stream=%u components=%u",
mParentHandle.c_str(),
static_cast<unsigned>(aMLine),
static_cast<unsigned>(aComponentCount));
std::vector<std::string> attrs;
for (auto i = aCandidateList.begin(); i != aCandidateList.end(); ++i) {
attrs.push_back("candidate:" + *i);
}
attrs.push_back("ice-ufrag:" + aUfrag);
attrs.push_back("ice-pwd:" + aPassword);
nsresult rv = stream->ParseAttributes(attrs);
if (NS_FAILED(rv)) {
CSFLogError(logTag, "Couldn't parse ICE attributes, rv=%u",
static_cast<unsigned>(rv));
}
for (size_t c = aComponentCount; c < stream->components(); ++c) {
// components are 1-indexed
stream->DisableComponent(c + 1);
}
}
}
void
PeerConnectionMedia::RemoveTransportsAtOrAfter_s(size_t aMLine)
{
for (size_t i = aMLine; i < mIceCtxHdlr->ctx()->GetStreamCount(); ++i) {
mIceCtxHdlr->ctx()->SetStream(i, nullptr);
}
}
nsresult PeerConnectionMedia::UpdateMediaPipelines(
const JsepSession& session) {
auto trackPairs = session.GetNegotiatedTrackPairs();
MediaPipelineFactory factory(this);
nsresult rv;
for (auto i = trackPairs.begin(); i != trackPairs.end(); ++i) {
JsepTrackPair pair = *i;
if (pair.mReceiving) {
rv = factory.CreateOrUpdateMediaPipeline(pair, *pair.mReceiving);
if (NS_FAILED(rv)) {
return rv;
}
}
if (pair.mSending) {
rv = factory.CreateOrUpdateMediaPipeline(pair, *pair.mSending);
if (NS_FAILED(rv)) {
return rv;
}
}
}
for (auto& stream : mRemoteSourceStreams) {
stream->StartReceiving();
}
return NS_OK;
}
void
PeerConnectionMedia::StartIceChecks(const JsepSession& aSession)
{
nsCOMPtr<nsIRunnable> runnable(
WrapRunnable(
RefPtr<PeerConnectionMedia>(this),
&PeerConnectionMedia::StartIceChecks_s,
aSession.IsIceControlling(),
aSession.RemoteIsIceLite(),
// Copy, just in case API changes to return a ref
std::vector<std::string>(aSession.GetIceOptions())));
PerformOrEnqueueIceCtxOperation(runnable);
}
void
PeerConnectionMedia::StartIceChecks_s(
bool aIsControlling,
bool aIsIceLite,
const std::vector<std::string>& aIceOptionsList) {
CSFLogDebug(logTag, "Starting ICE Checking");
std::vector<std::string> attributes;
if (aIsIceLite) {
attributes.push_back("ice-lite");
}
if (!aIceOptionsList.empty()) {
attributes.push_back("ice-options:");
for (auto i = aIceOptionsList.begin(); i != aIceOptionsList.end(); ++i) {
attributes.back() += *i + ' ';
}
}
nsresult rv = mIceCtxHdlr->ctx()->ParseGlobalAttributes(attributes);
if (NS_FAILED(rv)) {
CSFLogError(logTag, "%s: couldn't parse global parameters", __FUNCTION__ );
}
mIceCtxHdlr->ctx()->SetControlling(aIsControlling ?
NrIceCtx::ICE_CONTROLLING :
NrIceCtx::ICE_CONTROLLED);
mIceCtxHdlr->ctx()->StartChecks();
}
bool
PeerConnectionMedia::IsIceRestarting() const
{
ASSERT_ON_THREAD(mMainThread);
return (mIceRestartState != ICE_RESTART_NONE);
}
PeerConnectionMedia::IceRestartState
PeerConnectionMedia::GetIceRestartState() const
{
ASSERT_ON_THREAD(mMainThread);
return mIceRestartState;
}
void
PeerConnectionMedia::BeginIceRestart(const std::string& ufrag,
const std::string& pwd)
{
ASSERT_ON_THREAD(mMainThread);
if (IsIceRestarting()) {
return;
}
bool default_address_only = GetPrefDefaultAddressOnly();
RefPtr<NrIceCtx> new_ctx = mIceCtxHdlr->CreateCtx(ufrag,
pwd,
default_address_only);
RUN_ON_THREAD(GetSTSThread(),
WrapRunnable(
RefPtr<PeerConnectionMedia>(this),
&PeerConnectionMedia::BeginIceRestart_s,
new_ctx),
NS_DISPATCH_NORMAL);
mIceRestartState = ICE_RESTART_PROVISIONAL;
}
void
PeerConnectionMedia::BeginIceRestart_s(RefPtr<NrIceCtx> new_ctx)
{
ASSERT_ON_THREAD(mSTSThread);
// hold the original context so we can disconnect signals if needed
RefPtr<NrIceCtx> originalCtx = mIceCtxHdlr->ctx();
if (mIceCtxHdlr->BeginIceRestart(new_ctx)) {
ConnectSignals(mIceCtxHdlr->ctx().get(), originalCtx.get());
}
}
void
PeerConnectionMedia::CommitIceRestart()
{
ASSERT_ON_THREAD(mMainThread);
if (mIceRestartState != ICE_RESTART_PROVISIONAL) {
return;
}
mIceRestartState = ICE_RESTART_COMMITTED;
}
void
PeerConnectionMedia::FinalizeIceRestart()
{
ASSERT_ON_THREAD(mMainThread);
if (!IsIceRestarting()) {
return;
}
RUN_ON_THREAD(GetSTSThread(),
WrapRunnable(
RefPtr<PeerConnectionMedia>(this),
&PeerConnectionMedia::FinalizeIceRestart_s),
NS_DISPATCH_NORMAL);
mIceRestartState = ICE_RESTART_NONE;
}
void
PeerConnectionMedia::FinalizeIceRestart_s()
{
ASSERT_ON_THREAD(mSTSThread);
// reset old streams since we don't need them anymore
for (auto i = mTransportFlows.begin();
i != mTransportFlows.end();
++i) {
RefPtr<TransportFlow> aFlow = i->second;
if (!aFlow) continue;
TransportLayerIce* ice =
static_cast<TransportLayerIce*>(aFlow->GetLayer(TransportLayerIce::ID()));
ice->ResetOldStream();
}
mIceCtxHdlr->FinalizeIceRestart();
}
void
PeerConnectionMedia::RollbackIceRestart()
{
ASSERT_ON_THREAD(mMainThread);
if (mIceRestartState != ICE_RESTART_PROVISIONAL) {
return;
}
RUN_ON_THREAD(GetSTSThread(),
WrapRunnable(
RefPtr<PeerConnectionMedia>(this),
&PeerConnectionMedia::RollbackIceRestart_s),
NS_DISPATCH_NORMAL);
mIceRestartState = ICE_RESTART_NONE;
}
void
PeerConnectionMedia::RollbackIceRestart_s()
{
ASSERT_ON_THREAD(mSTSThread);
// hold the restart context so we can disconnect signals
RefPtr<NrIceCtx> restartCtx = mIceCtxHdlr->ctx();
// restore old streams since we're rolling back
for (auto i = mTransportFlows.begin();
i != mTransportFlows.end();
++i) {
RefPtr<TransportFlow> aFlow = i->second;
if (!aFlow) continue;
TransportLayerIce* ice =
static_cast<TransportLayerIce*>(aFlow->GetLayer(TransportLayerIce::ID()));
ice->RestoreOldStream();
}
mIceCtxHdlr->RollbackIceRestart();
ConnectSignals(mIceCtxHdlr->ctx().get(), restartCtx.get());
}
bool
PeerConnectionMedia::GetPrefDefaultAddressOnly() const
{
ASSERT_ON_THREAD(mMainThread); // will crash on STS thread
#if !defined(MOZILLA_EXTERNAL_LINKAGE)
bool default_address_only = Preferences::GetBool(
"media.peerconnection.ice.default_address_only", false);
#else
bool default_address_only = false;
#endif
return default_address_only;
}
void
PeerConnectionMedia::ConnectSignals(NrIceCtx *aCtx, NrIceCtx *aOldCtx)
{
aCtx->SignalGatheringStateChange.connect(
this,
&PeerConnectionMedia::IceGatheringStateChange_s);
aCtx->SignalConnectionStateChange.connect(
this,
&PeerConnectionMedia::IceConnectionStateChange_s);
if (aOldCtx) {
MOZ_ASSERT(aCtx != aOldCtx);
aOldCtx->SignalGatheringStateChange.disconnect(this);
aOldCtx->SignalConnectionStateChange.disconnect(this);
// if the old and new connection state and/or gathering state is
// different fire the state update. Note: we don't fire the update
// if the state is *INIT since updates for the INIT state aren't
// sent during the normal flow. (mjf)
if (aOldCtx->connection_state() != aCtx->connection_state() &&
aCtx->connection_state() != NrIceCtx::ICE_CTX_INIT) {
aCtx->SignalConnectionStateChange(aCtx, aCtx->connection_state());
}
if (aOldCtx->gathering_state() != aCtx->gathering_state() &&
aCtx->gathering_state() != NrIceCtx::ICE_CTX_GATHER_INIT) {
aCtx->SignalGatheringStateChange(aCtx, aCtx->gathering_state());
}
}
}
void
PeerConnectionMedia::AddIceCandidate(const std::string& candidate,
const std::string& mid,
uint32_t aMLine) {
RUN_ON_THREAD(GetSTSThread(),
WrapRunnable(
RefPtr<PeerConnectionMedia>(this),
&PeerConnectionMedia::AddIceCandidate_s,
std::string(candidate), // Make copies.
std::string(mid),
aMLine),
NS_DISPATCH_NORMAL);
}
void
PeerConnectionMedia::AddIceCandidate_s(const std::string& aCandidate,
const std::string& aMid,
uint32_t aMLine) {
RefPtr<NrIceMediaStream> stream(mIceCtxHdlr->ctx()->GetStream(aMLine));
if (!stream) {
CSFLogError(logTag, "No ICE stream for candidate at level %u: %s",
static_cast<unsigned>(aMLine), aCandidate.c_str());
return;
}
nsresult rv = stream->ParseTrickleCandidate(aCandidate);
if (NS_FAILED(rv)) {
CSFLogError(logTag, "Couldn't process ICE candidate at level %u",
static_cast<unsigned>(aMLine));
return;
}
}
void
PeerConnectionMedia::FlushIceCtxOperationQueueIfReady()
{
ASSERT_ON_THREAD(mMainThread);
if (IsIceCtxReady()) {
for (auto i = mQueuedIceCtxOperations.begin();
i != mQueuedIceCtxOperations.end();
++i) {
GetSTSThread()->Dispatch(*i, NS_DISPATCH_NORMAL);
}
mQueuedIceCtxOperations.clear();
}
}
void
PeerConnectionMedia::PerformOrEnqueueIceCtxOperation(nsIRunnable* runnable)
{
ASSERT_ON_THREAD(mMainThread);
if (IsIceCtxReady()) {
GetSTSThread()->Dispatch(runnable, NS_DISPATCH_NORMAL);
} else {
mQueuedIceCtxOperations.push_back(runnable);
}
}
void
PeerConnectionMedia::GatherIfReady() {
ASSERT_ON_THREAD(mMainThread);
nsCOMPtr<nsIRunnable> runnable(WrapRunnable(
RefPtr<PeerConnectionMedia>(this),
&PeerConnectionMedia::EnsureIceGathering_s));
PerformOrEnqueueIceCtxOperation(runnable);
}
void
PeerConnectionMedia::EnsureIceGathering_s() {
if (mProxyServer) {
mIceCtxHdlr->ctx()->SetProxyServer(*mProxyServer);
}
// Start gathering, but only if there are streams
for (size_t i = 0; i < mIceCtxHdlr->ctx()->GetStreamCount(); ++i) {
if (mIceCtxHdlr->ctx()->GetStream(i)) {
mIceCtxHdlr->ctx()->StartGathering();
return;
}
}
// If there are no streams, we're probably in a situation where we've rolled
// back while still waiting for our proxy configuration to come back. Make
// sure content knows that the rollback has stuck wrt gathering.
IceGatheringStateChange_s(mIceCtxHdlr->ctx().get(),
NrIceCtx::ICE_CTX_GATHER_COMPLETE);
}
nsresult
PeerConnectionMedia::AddTrack(DOMMediaStream& aMediaStream,
const std::string& streamId,
MediaStreamTrack& aTrack,
const std::string& trackId)
{
ASSERT_ON_THREAD(mMainThread);
CSFLogDebug(logTag, "%s: MediaStream: %p", __FUNCTION__, &aMediaStream);
RefPtr<LocalSourceStreamInfo> localSourceStream =
GetLocalStreamById(streamId);
if (!localSourceStream) {
localSourceStream = new LocalSourceStreamInfo(&aMediaStream, this, streamId);
mLocalSourceStreams.AppendElement(localSourceStream);
}
localSourceStream->AddTrack(trackId, &aTrack);
return NS_OK;
}
nsresult
PeerConnectionMedia::RemoveLocalTrack(const std::string& streamId,
const std::string& trackId)
{
ASSERT_ON_THREAD(mMainThread);
CSFLogDebug(logTag, "%s: stream: %s track: %s", __FUNCTION__,
streamId.c_str(), trackId.c_str());
RefPtr<LocalSourceStreamInfo> localSourceStream =
GetLocalStreamById(streamId);
if (!localSourceStream) {
return NS_ERROR_ILLEGAL_VALUE;
}
localSourceStream->RemoveTrack(trackId);
if (!localSourceStream->GetTrackCount()) {
mLocalSourceStreams.RemoveElement(localSourceStream);
}
return NS_OK;
}
nsresult
PeerConnectionMedia::RemoveRemoteTrack(const std::string& streamId,
const std::string& trackId)
{
ASSERT_ON_THREAD(mMainThread);
CSFLogDebug(logTag, "%s: stream: %s track: %s", __FUNCTION__,
streamId.c_str(), trackId.c_str());
RefPtr<RemoteSourceStreamInfo> remoteSourceStream =
GetRemoteStreamById(streamId);
if (!remoteSourceStream) {
return NS_ERROR_ILLEGAL_VALUE;
}
remoteSourceStream->RemoveTrack(trackId);
if (!remoteSourceStream->GetTrackCount()) {
mRemoteSourceStreams.RemoveElement(remoteSourceStream);
}
return NS_OK;
}
void
PeerConnectionMedia::SelfDestruct()
{
ASSERT_ON_THREAD(mMainThread);
CSFLogDebug(logTag, "%s: ", __FUNCTION__);
// Shut down the media
for (uint32_t i=0; i < mLocalSourceStreams.Length(); ++i) {
mLocalSourceStreams[i]->DetachMedia_m();
}
for (uint32_t i=0; i < mRemoteSourceStreams.Length(); ++i) {
mRemoteSourceStreams[i]->DetachMedia_m();
}
if (mProxyRequest) {
mProxyRequest->Cancel(NS_ERROR_ABORT);
mProxyRequest = nullptr;
}
// Shutdown the transport (async)
RUN_ON_THREAD(mSTSThread, WrapRunnable(
this, &PeerConnectionMedia::ShutdownMediaTransport_s),
NS_DISPATCH_NORMAL);
CSFLogDebug(logTag, "%s: Media shut down", __FUNCTION__);
}
void
PeerConnectionMedia::SelfDestruct_m()
{
CSFLogDebug(logTag, "%s: ", __FUNCTION__);
ASSERT_ON_THREAD(mMainThread);
mLocalSourceStreams.Clear();
mRemoteSourceStreams.Clear();
mMainThread = nullptr;
// Final self-destruct.
this->Release();
}
void
PeerConnectionMedia::ShutdownMediaTransport_s()
{
ASSERT_ON_THREAD(mSTSThread);
CSFLogDebug(logTag, "%s: ", __FUNCTION__);
// Here we access m{Local|Remote}SourceStreams off the main thread.
// That's OK because by here PeerConnectionImpl has forgotten about us,
// so there is no chance of getting a call in here from outside.
// The dispatches from SelfDestruct() and to SelfDestruct_m() provide
// memory barriers that protect us from badness.
for (uint32_t i=0; i < mLocalSourceStreams.Length(); ++i) {
mLocalSourceStreams[i]->DetachTransport_s();
}
for (uint32_t i=0; i < mRemoteSourceStreams.Length(); ++i) {
mRemoteSourceStreams[i]->DetachTransport_s();
}
disconnect_all();
mTransportFlows.clear();
mIceCtxHdlr = nullptr;
mMainThread->Dispatch(WrapRunnable(this, &PeerConnectionMedia::SelfDestruct_m),
NS_DISPATCH_NORMAL);
}
LocalSourceStreamInfo*
PeerConnectionMedia::GetLocalStreamByIndex(int aIndex)
{
ASSERT_ON_THREAD(mMainThread);
if(aIndex < 0 || aIndex >= (int) mLocalSourceStreams.Length()) {
return nullptr;
}
MOZ_ASSERT(mLocalSourceStreams[aIndex]);
return mLocalSourceStreams[aIndex];
}
LocalSourceStreamInfo*
PeerConnectionMedia::GetLocalStreamById(const std::string& id)
{
ASSERT_ON_THREAD(mMainThread);
for (size_t i = 0; i < mLocalSourceStreams.Length(); ++i) {
if (id == mLocalSourceStreams[i]->GetId()) {
return mLocalSourceStreams[i];
}
}
return nullptr;
}
LocalSourceStreamInfo*
PeerConnectionMedia::GetLocalStreamByTrackId(const std::string& id)
{
ASSERT_ON_THREAD(mMainThread);
for (RefPtr<LocalSourceStreamInfo>& info : mLocalSourceStreams) {
if (info->HasTrack(id)) {
return info;
}
}
return nullptr;
}
RemoteSourceStreamInfo*
PeerConnectionMedia::GetRemoteStreamByIndex(size_t aIndex)
{
ASSERT_ON_THREAD(mMainThread);
MOZ_ASSERT(mRemoteSourceStreams.SafeElementAt(aIndex));
return mRemoteSourceStreams.SafeElementAt(aIndex);
}
RemoteSourceStreamInfo*
PeerConnectionMedia::GetRemoteStreamById(const std::string& id)
{
ASSERT_ON_THREAD(mMainThread);
for (size_t i = 0; i < mRemoteSourceStreams.Length(); ++i) {
if (id == mRemoteSourceStreams[i]->GetId()) {
return mRemoteSourceStreams[i];
}
}
return nullptr;
}
RemoteSourceStreamInfo*
PeerConnectionMedia::GetRemoteStreamByTrackId(const std::string& id)
{
ASSERT_ON_THREAD(mMainThread);
for (RefPtr<RemoteSourceStreamInfo>& info : mRemoteSourceStreams) {
if (info->HasTrack(id)) {
return info;
}
}
return nullptr;
}
nsresult
PeerConnectionMedia::AddRemoteStream(RefPtr<RemoteSourceStreamInfo> aInfo)
{
ASSERT_ON_THREAD(mMainThread);
mRemoteSourceStreams.AppendElement(aInfo);
return NS_OK;
}
void
PeerConnectionMedia::IceGatheringStateChange_s(NrIceCtx* ctx,
NrIceCtx::GatheringState state)
{
ASSERT_ON_THREAD(mSTSThread);
if (state == NrIceCtx::ICE_CTX_GATHER_COMPLETE) {
// Fire off EndOfLocalCandidates for each stream
for (size_t i = 0; ; ++i) {
RefPtr<NrIceMediaStream> stream(ctx->GetStream(i));
if (!stream) {
break;
}
NrIceCandidate candidate;
NrIceCandidate rtcpCandidate;
GetDefaultCandidates(*stream, &candidate, &rtcpCandidate);
EndOfLocalCandidates(candidate.cand_addr.host,
candidate.cand_addr.port,
rtcpCandidate.cand_addr.host,
rtcpCandidate.cand_addr.port,
i);
}
}
// ShutdownMediaTransport_s has not run yet because it unhooks this function
// from its signal, which means that SelfDestruct_m has not been dispatched
// yet either, so this PCMedia will still be around when this dispatch reaches
// main.
GetMainThread()->Dispatch(
WrapRunnable(this,
&PeerConnectionMedia::IceGatheringStateChange_m,
ctx,
state),
NS_DISPATCH_NORMAL);
}
void
PeerConnectionMedia::IceConnectionStateChange_s(NrIceCtx* ctx,
NrIceCtx::ConnectionState state)
{
ASSERT_ON_THREAD(mSTSThread);
// ShutdownMediaTransport_s has not run yet because it unhooks this function
// from its signal, which means that SelfDestruct_m has not been dispatched
// yet either, so this PCMedia will still be around when this dispatch reaches
// main.
GetMainThread()->Dispatch(
WrapRunnable(this,
&PeerConnectionMedia::IceConnectionStateChange_m,
ctx,
state),
NS_DISPATCH_NORMAL);
}
void
PeerConnectionMedia::OnCandidateFound_s(NrIceMediaStream *aStream,
const std::string &aCandidateLine)
{
ASSERT_ON_THREAD(mSTSThread);
MOZ_ASSERT(aStream);
MOZ_RELEASE_ASSERT(mIceCtxHdlr);
CSFLogDebug(logTag, "%s: %s", __FUNCTION__, aStream->name().c_str());
NrIceCandidate candidate;
NrIceCandidate rtcpCandidate;
GetDefaultCandidates(*aStream, &candidate, &rtcpCandidate);
// ShutdownMediaTransport_s has not run yet because it unhooks this function
// from its signal, which means that SelfDestruct_m has not been dispatched
// yet either, so this PCMedia will still be around when this dispatch reaches
// main.
GetMainThread()->Dispatch(
WrapRunnable(this,
&PeerConnectionMedia::OnCandidateFound_m,
aCandidateLine,
candidate.cand_addr.host,
candidate.cand_addr.port,
rtcpCandidate.cand_addr.host,
rtcpCandidate.cand_addr.port,
aStream->GetLevel()),
NS_DISPATCH_NORMAL);
}
void
PeerConnectionMedia::EndOfLocalCandidates(const std::string& aDefaultAddr,
uint16_t aDefaultPort,
const std::string& aDefaultRtcpAddr,
uint16_t aDefaultRtcpPort,
uint16_t aMLine)
{
GetMainThread()->Dispatch(
WrapRunnable(this,
&PeerConnectionMedia::EndOfLocalCandidates_m,
aDefaultAddr,
aDefaultPort,
aDefaultRtcpAddr,
aDefaultRtcpPort,
aMLine),
NS_DISPATCH_NORMAL);
}
void
PeerConnectionMedia::GetDefaultCandidates(const NrIceMediaStream& aStream,
NrIceCandidate* aCandidate,
NrIceCandidate* aRtcpCandidate)
{
nsresult res = aStream.GetDefaultCandidate(1, aCandidate);
// Optional; component won't exist if doing rtcp-mux
if (NS_FAILED(aStream.GetDefaultCandidate(2, aRtcpCandidate))) {
aRtcpCandidate->cand_addr.host.clear();
aRtcpCandidate->cand_addr.port = 0;
}
if (NS_FAILED(res)) {
aCandidate->cand_addr.host.clear();
aCandidate->cand_addr.port = 0;
CSFLogError(logTag, "%s: GetDefaultCandidates failed for level %u, "
"res=%u",
__FUNCTION__,
static_cast<unsigned>(aStream.GetLevel()),
static_cast<unsigned>(res));
}
}
void
PeerConnectionMedia::IceGatheringStateChange_m(NrIceCtx* ctx,
NrIceCtx::GatheringState state)
{
ASSERT_ON_THREAD(mMainThread);
SignalIceGatheringStateChange(ctx, state);
}
void
PeerConnectionMedia::IceConnectionStateChange_m(NrIceCtx* ctx,
NrIceCtx::ConnectionState state)
{
ASSERT_ON_THREAD(mMainThread);
SignalIceConnectionStateChange(ctx, state);
}
void
PeerConnectionMedia::IceStreamReady_s(NrIceMediaStream *aStream)
{
MOZ_ASSERT(aStream);
CSFLogDebug(logTag, "%s: %s", __FUNCTION__, aStream->name().c_str());
}
void
PeerConnectionMedia::OnCandidateFound_m(const std::string& aCandidateLine,
const std::string& aDefaultAddr,
uint16_t aDefaultPort,
const std::string& aDefaultRtcpAddr,
uint16_t aDefaultRtcpPort,
uint16_t aMLine)
{
ASSERT_ON_THREAD(mMainThread);
if (!aDefaultAddr.empty()) {
SignalUpdateDefaultCandidate(aDefaultAddr,
aDefaultPort,
aDefaultRtcpAddr,
aDefaultRtcpPort,
aMLine);
}
SignalCandidate(aCandidateLine, aMLine);
}
void
PeerConnectionMedia::EndOfLocalCandidates_m(const std::string& aDefaultAddr,
uint16_t aDefaultPort,
const std::string& aDefaultRtcpAddr,
uint16_t aDefaultRtcpPort,
uint16_t aMLine) {
ASSERT_ON_THREAD(mMainThread);
if (!aDefaultAddr.empty()) {
SignalUpdateDefaultCandidate(aDefaultAddr,
aDefaultPort,
aDefaultRtcpAddr,
aDefaultRtcpPort,
aMLine);
}
SignalEndOfLocalCandidates(aMLine);
}
void
PeerConnectionMedia::DtlsConnected_s(TransportLayer *layer,
TransportLayer::State state)
{
MOZ_ASSERT(layer->id() == "dtls");
TransportLayerDtls* dtlsLayer = static_cast<TransportLayerDtls*>(layer);
dtlsLayer->SignalStateChange.disconnect(this);
bool privacyRequested = (dtlsLayer->GetNegotiatedAlpn() == "c-webrtc");
GetMainThread()->Dispatch(
WrapRunnableNM(&PeerConnectionMedia::DtlsConnected_m,
mParentHandle, privacyRequested),
NS_DISPATCH_NORMAL);
}
void
PeerConnectionMedia::DtlsConnected_m(const std::string& aParentHandle,
bool aPrivacyRequested)
{
PeerConnectionWrapper pcWrapper(aParentHandle);
PeerConnectionImpl* pc = pcWrapper.impl();
if (pc) {
pc->SetDtlsConnected(aPrivacyRequested);
}
}
https://dxr.mozilla.org/mozilla-central/source/media/webrtc/signaling/src/peerconnection/PeerConnectionMedia.cpp?q=%2Bfunction%3A%22mozilla%3A%3APeerConnectionMedia%3A%3ADtlsConnected_s%28mozilla%3A%3ATransportLayer+%2A%2C+TransportLayer%3A%3AState%29%22&redirect_type=single#1322
I don't know if this is a security issue because MediaPipeline does check:
https://dxr.mozilla.org/mozilla-central/source/media/webrtc/signaling/src/mediapipeline/MediaPipeline.cpp#640
But it doesn't seem like it's a good thing.
Reporter | ||
Updated•9 years ago
|
Flags: needinfo?(martin.thomson)
Updated•9 years ago
|
Group: core-security → media-core-security
Assignee | ||
Comment 1•9 years ago
|
||
Did you have to paste the entire file?
This isn't an issue as far as I can tell. It only sets the "privacy" flag based on ALPN. When I wrote the code, I considered adding a check, but it wouldn't help to switch to confidential mode on error because that change cannot be reverted and we might retry the connection (ICE restart, for instance).
I believe that it's safe because we can't send media to a peer if the connection fails.
Flags: needinfo?(martin.thomson)
Reporter | ||
Comment 2•9 years ago
|
||
(In reply to Martin Thomson [:mt:] from comment #1)
> Did you have to paste the entire file?
No, I didn't, but apparently dxr decided to help.
> This isn't an issue as far as I can tell. It only sets the "privacy" flag
> based on ALPN. When I wrote the code, I considered adding a check, but it
> wouldn't help to switch to confidential mode on error because that change
> cannot be reverted and we might retry the connection (ICE restart, for
> instance).
Well, so maybe we shouldn't be setting the flag if we switch to error?
> I believe that it's safe because we can't send media to a peer if the
> connection fails.
Comment 3•9 years ago
|
||
low priority based on mt's comments. Let's finish the discussion to verify if there's a sec issue (or any issue) here, then process/re-prio or close. Assigning to Martin to ensure there's a driver for resolving a (possible) sec bug.
Assignee: nobody → martin.thomson
Rank: 35
Priority: -- → P3
Comment 5•8 years ago
|
||
Mass change P3->P4 to align with new Mozilla triage process.
Priority: P3 → P4
Updated•3 years ago
|
Severity: normal → S3
You need to log in
before you can comment on or make changes to this bug.
Description
•