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)

defect

Tracking

()

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.
Flags: needinfo?(martin.thomson)
Group: core-security → media-core-security
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)
(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.
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
Unhiding per comment 1.
Group: media-core-security
Mass change P3->P4 to align with new Mozilla triage process.
Priority: P3 → P4
Severity: normal → S3
You need to log in before you can comment on or make changes to this bug.