/* 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 "MediaTransportHandler.h"
#include "MediaTransportHandlerIPC.h"
#include "transport/nricemediastream.h"
#include "transport/nriceresolver.h"
#include "transport/sigslot.h"
#include "transport/transportflow.h"
#include "transport/transportlayerdtls.h"
#include "transport/transportlayerice.h"
#include "transport/transportlayersrtp.h"
// Config stuff
#include "mozilla/IceServerParser.h"
#include "mozilla/Preferences.h"
#include "mozilla/StaticPrefs_network.h"
#include "mozilla/dom/RTCConfigurationBinding.h"
// Logging stuff
#include "common/browser_logging/CSFLog.h"
// For fetching ICE logging
#include "transport/rlogconnector.h"
// DTLS
#include <map>
#include <string>
#include <vector>
#include "mozilla/ProfilerMarkers.h"
#include "mozilla/PublicSSL.h" // For psm::InitializeCipherSuite
#include "mozilla/dom/RTCStatsReportBinding.h"
#include "nsDNSService2.h"
#include "nsFmtString.h"
#include "nsISocketTransportService.h"
#include "nss.h" // For NSS_NoDB_Init
#include "sdp/SdpAttribute.h"
#include "transport/runnable_utils.h"
#define MEDIA_TRANSPORT_HANDLER_PACKET_RECEIVED(aPacket) \
PROFILER_MARKER_TEXT(
"WebRTC Packet Received", MEDIA_RT, {}, \
ProfilerString8View::WrapNullTerminatedString( \
MediaPacket::EnumValueToString((aPacket).type())));
namespace mozilla {
static const char* mthLogTag =
"MediaTransportHandler";
#ifdef LOGTAG
# undef LOGTAG
#endif
#define LOGTAG mthLogTag
class MediaTransportHandlerSTS : public MediaTransportHandler,
public sigslot::has_slots<> {
public:
explicit MediaTransportHandlerSTS();
RefPtr<IceLogPromise> GetIceLog(
const nsCString& aPattern) override;
void ClearIceLog() override;
void EnterPrivateMode() override;
void ExitPrivateMode() override;
void CreateIceCtx(
const std::string& aName) override;
nsresult SetIceConfig(
const nsTArray<dom::RTCIceServer>& aIceServers,
dom::RTCIceTransportPolicy aIcePolicy) override;
// We will probably be able to move the proxy lookup stuff into
// this class once we move mtransport to its own process.
void SetProxyConfig(NrSocketProxyConfig&& aProxyConfig) override;
void EnsureProvisionalTransport(
const std::string& aTransportId,
const std::string& aUfrag,
const std::string& aPwd,
int aComponentCount) override;
void SetTargetForDefaultLocalAddressLookup(
const std::string& aTargetIp,
uint16_t aTargetPort) override;
// We set default-route-only as late as possible because it depends on what
// capture permissions have been granted on the window, which could easily
// change between Init (ie; when the PC is created) and StartIceGathering
// (ie; when we set the local description).
void StartIceGathering(
bool aDefaultRouteOnly,
bool aObfuscateHostAddresses,
// This will go away once mtransport moves to its
// own process, because we won't need to get this
// via IPC anymore
const nsTArray<NrIceStunAddr>& aStunAddrs) override;
void ActivateTransport(
const std::string& aTransportId,
const std::string& aLocalUfrag,
const std::string& aLocalPwd, size_t aComponentCount,
const std::string& aUfrag,
const std::string& aPassword,
const nsTArray<uint8_t>& aKeyDer,
const nsTArray<uint8_t>& aCertDer,
SSLKEAType aAuthType,
bool aDtlsClient,
const DtlsDigestList& aDigests,
bool aPrivacyRequested) override;
void RemoveTransportsExcept(
const std::set<std::string>& aTransportIds) override;
void StartIceChecks(
bool aIsControlling,
const std::vector<std::string>& aIceOptions) override;
void AddIceCandidate(
const std::string& aTransportId,
const std::string& aCandidate,
const std::string& aUfrag,
const std::string& aObfuscatedAddress) override;
void UpdateNetworkState(
bool aOnline) override;
void SendPacket(
const std::string& aTransportId,
MediaPacket&& aPacket) override;
RefPtr<dom::RTCStatsPromise> GetIceStats(
const std::string& aTransportId,
DOMHighResTimeStamp aNow) override;
void Shutdown();
private:
void Destroy() override;
void DestroyFinal();
void Shutdown_s();
RefPtr<TransportFlow> CreateTransportFlow(
const std::string& aTransportId,
bool aIsRtcp,
const RefPtr<DtlsIdentity>& aDtlsIdentity,
bool aDtlsClient,
const DtlsDigestList& aDigests,
bool aPrivacyRequested);
struct Transport {
RefPtr<TransportFlow> mFlow;
RefPtr<TransportFlow> mRtcpFlow;
};
using MediaTransportHandler::OnAlpnNegotiated;
using MediaTransportHandler::OnCandidate;
using MediaTransportHandler::OnCandidateError;
using MediaTransportHandler::OnConnectionStateChange;
using MediaTransportHandler::OnEncryptedSending;
using MediaTransportHandler::OnGatheringStateChange;
using MediaTransportHandler::OnPacketReceived;
using MediaTransportHandler::OnRtcpStateChange;
using MediaTransportHandler::OnStateChange;
void OnGatheringStateChange(
const std::string& aTransportId,
NrIceMediaStream::GatheringState aState);
void OnConnectionStateChange(NrIceMediaStream* aIceStream,
NrIceCtx::ConnectionState aState);
void OnCandidateFound(NrIceMediaStream* aStream,
const std::string& aCandidate,
const std::string& aUfrag,
const std::string& aMDNSAddr,
const std::string& aActualAddr);
void OnCandidateError(NrIceMediaStream* aStream,
const std::string& aAddress,
uint16_t aPort,
const std::string& aUrl,
uint16_t aErrorCode,
const std::string& aErrorText);
void OnStateChange(TransportLayer* aLayer, TransportLayer::State);
void OnRtcpStateChange(TransportLayer* aLayer, TransportLayer::State);
void PacketReceived(TransportLayer* aLayer, MediaPacket& aPacket);
void EncryptedPacketSending(TransportLayer* aLayer, MediaPacket& aPacket);
RefPtr<TransportFlow> GetTransportFlow(
const std::string& aTransportId,
bool aIsRtcp)
const;
void GetIceStats(
const NrIceMediaStream& aStream, DOMHighResTimeStamp aNow,
dom::RTCStatsCollection* aStats,
dom::RTCTransportStats& aTransport)
const;
virtual ~MediaTransportHandlerSTS() =
default;
nsCOMPtr<nsISerialEventTarget> mStsThread;
RefPtr<NrIceCtx> mIceCtx;
RefPtr<NrIceResolver> mDNSResolver;
std::map<std::string, Transport> mTransports;
bool mObfuscateHostAddresses =
false;
bool mTurnDisabled =
false;
uint32_t mMinDtlsVersion =
0;
uint32_t mMaxDtlsVersion =
0;
bool mForceNoHost =
false;
bool mAllowLoopback =
false;
bool mAllowLinkLocal =
false;
Maybe<NrIceCtx::NatSimulatorConfig> mNatConfig;
std::set<std::string> mSignaledAddresses;
// Init can only be done on main, but we want this to be usable on any thread
using InitPromise = MozPromise<
bool, std::string,
false>;
RefPtr<InitPromise> mInitPromise;
};
/* static */
already_AddRefed<MediaTransportHandler> MediaTransportHandler::Create() {
RefPtr<MediaTransportHandler> result;
if (XRE_IsContentProcess() &&
Preferences::GetBool(
"media.peerconnection.mtransport_process") &&
StaticPrefs::network_process_enabled()) {
result = MakeRefPtr<MediaTransportHandlerIPC>();
}
else {
result = MakeRefPtr<MediaTransportHandlerSTS>();
}
result->Initialize();
return result.forget();
}
class STSShutdownHandler : public nsISTSShutdownObserver {
public:
NS_DECL_ISUPPORTS
// Lazy singleton
static RefPtr<STSShutdownHandler>& Instance() {
MOZ_ASSERT(NS_IsMainThread());
static RefPtr<STSShutdownHandler> sHandler =
MakeRefPtr<STSShutdownHandler>();
return sHandler;
}
void Shutdown() {
MOZ_ASSERT(NS_IsMainThread());
for (
const auto& handler : mHandlers) {
handler->Shutdown();
}
mHandlers.clear();
}
STSShutdownHandler() {
CSFLogDebug(LOGTAG,
"%s", __func__);
nsresult res;
nsCOMPtr<nsISocketTransportService> sts =
do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &res);
MOZ_RELEASE_ASSERT(NS_SUCCEEDED(res));
MOZ_RELEASE_ASSERT(sts);
sts->AddShutdownObserver(this);
}
NS_IMETHOD Observe() override {
CSFLogDebug(LOGTAG,
"%s", __func__);
Shutdown();
nsresult res;
nsCOMPtr<nsISocketTransportService> sts =
do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &res);
MOZ_RELEASE_ASSERT(NS_SUCCEEDED(res));
MOZ_RELEASE_ASSERT(sts);
sts->RemoveShutdownObserver(this);
Instance() = nullptr;
return NS_OK;
}
void Register(MediaTransportHandlerSTS* aHandler) {
MOZ_ASSERT(NS_IsMainThread());
mHandlers.insert(aHandler);
}
void Deregister(MediaTransportHandlerSTS* aHandler) {
MOZ_ASSERT(NS_IsMainThread());
mHandlers.erase(aHandler);
}
private:
virtual ~STSShutdownHandler() =
default;
// Raw ptrs, registered on init, deregistered on destruction, all on main
std::set<MediaTransportHandlerSTS*> mHandlers;
};
NS_IMPL_ISUPPORTS(STSShutdownHandler, nsISTSShutdownObserver);
MediaTransportHandlerSTS::MediaTransportHandlerSTS() {
nsresult rv;
mStsThread = do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &rv);
if (!mStsThread) {
MOZ_CRASH();
}
RLogConnector::CreateInstance();
CSFLogDebug(LOGTAG,
"%s done %p", __func__, this);
// We do not set up mDNSService here, because we are not running on main (we
// use PBackground), and the DNS service asserts.
}
static NrIceCtx::Policy toNrIcePolicy(dom::RTCIceTransportPolicy aPolicy) {
switch (aPolicy) {
case dom::RTCIceTransportPolicy::Relay:
return NrIceCtx::ICE_POLICY_RELAY;
case dom::RTCIceTransportPolicy::All:
return NrIceCtx::ICE_POLICY_ALL;
default:
MOZ_CRASH();
}
return NrIceCtx::ICE_POLICY_ALL;
}
static NrIceCtx::GlobalConfig GetGlobalConfig() {
NrIceCtx::GlobalConfig config;
config.mTcpEnabled =
Preferences::GetBool(
"media.peerconnection.ice.tcp",
false);
config.mStunClientMaxTransmits = Preferences::GetInt(
"media.peerconnection.ice.stun_client_maximum_transmits",
config.mStunClientMaxTransmits);
config.mTrickleIceGracePeriod =
Preferences::GetInt(
"media.peerconnection.ice.trickle_grace_period",
config.mTrickleIceGracePeriod);
config.mIceTcpSoSockCount = Preferences::GetInt(
"media.peerconnection.ice.tcp_so_sock_count", config.mIceTcpSoSockCount);
config.mIceTcpListenBacklog =
Preferences::GetInt(
"media.peerconnection.ice.tcp_listen_backlog",
config.mIceTcpListenBacklog);
(
void)Preferences::GetCString(
"media.peerconnection.ice.force_interface",
config.mForceNetInterface);
return config;
}
static Maybe<NrIceCtx::NatSimulatorConfig> GetNatConfig() {
bool block_tcp = Preferences::GetBool(
"media.peerconnection.nat_simulator.block_tcp",
false);
bool block_udp = Preferences::GetBool(
"media.peerconnection.nat_simulator.block_udp",
false);
bool block_tls = Preferences::GetBool(
"media.peerconnection.nat_simulator.block_tls",
false);
int error_code_for_drop = Preferences::GetInt(
"media.peerconnection.nat_simulator.error_code_for_drop",
0);
nsAutoCString mapping_type;
(
void)Preferences::GetCString(
"media.peerconnection.nat_simulator.mapping_type", mapping_type);
nsAutoCString filtering_type;
(
void)Preferences::GetCString(
"media.peerconnection.nat_simulator.filtering_type", filtering_type);
nsAutoCString redirect_address;
(
void)Preferences::GetCString(
"media.peerconnection.nat_simulator.redirect_address", redirect_address);
nsAutoCString redirect_targets;
(
void)Preferences::GetCString(
"media.peerconnection.nat_simulator.redirect_targets", redirect_targets);
int network_delay_ms = Preferences::GetInt(
"media.peerconnection.nat_simulator.network_delay_ms",
0);
if (block_udp || block_tcp || block_tls || !mapping_type.IsEmpty() ||
!filtering_type.IsEmpty() || !redirect_address.IsEmpty()) {
CSFLogDebug(LOGTAG,
"NAT filtering type: %s", filtering_type.get());
CSFLogDebug(LOGTAG,
"NAT mapping type: %s", mapping_type.get());
CSFLogDebug(LOGTAG,
"NAT network delay: %d", network_delay_ms);
NrIceCtx::NatSimulatorConfig natConfig;
natConfig.mBlockUdp = block_udp;
natConfig.mBlockTcp = block_tcp;
natConfig.mBlockTls = block_tls;
natConfig.mErrorCodeForDrop = error_code_for_drop;
natConfig.mFilteringType = std::move(filtering_type);
natConfig.mMappingType = std::move(mapping_type);
natConfig.mNetworkDelayMs = network_delay_ms;
if (redirect_address.Length()) {
CSFLogDebug(LOGTAG,
"Redirect address: %s", redirect_address.get());
CSFLogDebug(LOGTAG,
"Redirect targets: %s", redirect_targets.get());
natConfig.mRedirectAddress = std::move(redirect_address);
std::stringstream str(redirect_targets.get());
std::string target;
while (getline(str, target,
',')) {
CSFLogDebug(LOGTAG,
"Adding target: %s", target.c_str());
natConfig.mRedirectTargets.AppendElement(std::move(target));
}
}
return Some(std::move(natConfig));
}
return Nothing();
}
void MediaTransportHandlerSTS::CreateIceCtx(
const std::string& aName) {
mInitPromise = InvokeAsync(
GetMainThreadSerialEventTarget(), __func__,
[=, this, self = RefPtr<MediaTransportHandlerSTS>(this)]() {
CSFLogDebug(LOGTAG,
"%s starting", __func__);
if (!NSS_IsInitialized()) {
if (NSS_NoDB_Init(nullptr) != SECSuccess) {
MOZ_CRASH();
return InitPromise::CreateAndReject(
"NSS_NoDB_Init failed",
__func__);
}
if (NS_FAILED(mozilla::psm::InitializeCipherSuite())) {
MOZ_CRASH();
return InitPromise::CreateAndReject(
"InitializeCipherSuite failed",
__func__);
}
mozilla::psm::DisableMD5();
}
static bool globalInitDone =
false;
if (!globalInitDone) {
// Ensure the DNS service is initted for the first time on main
DebugOnly<RefPtr<nsIDNSService>> dnsService =
RefPtr<nsIDNSService>(nsDNSService::GetXPCOMSingleton());
MOZ_ASSERT(dnsService.value);
mStsThread->Dispatch(
WrapRunnableNM(&NrIceCtx::InitializeGlobals, GetGlobalConfig()),
NS_DISPATCH_NORMAL);
globalInitDone = true;
}
// Give us a way to globally turn off TURN support
mTurnDisabled =
Preferences::GetBool(
"media.peerconnection.turn.disable",
false);
// We are reading these here, because when we setup the DTLS transport
// we are on the wrong thread to read prefs
mMinDtlsVersion =
Preferences::GetUint(
"media.peerconnection.dtls.version.min");
mMaxDtlsVersion =
Preferences::GetUint(
"media.peerconnection.dtls.version.max");
mForceNoHost =
Preferences::GetBool(
"media.peerconnection.ice.no_host",
false);
mNatConfig = GetNatConfig();
mAllowLoopback =
Preferences::GetBool(
"media.peerconnection.ice.loopback",
false);
mAllowLinkLocal =
Preferences::GetBool(
"media.peerconnection.ice.link_local",
false);
MOZ_RELEASE_ASSERT(STSShutdownHandler::Instance());
STSShutdownHandler::Instance()->
Register(this);
return InvokeAsync(
mStsThread, __func__,
[=, this, self = RefPtr<MediaTransportHandlerSTS>(this)]() {
mIceCtx = NrIceCtx::Create(aName);
if (!mIceCtx) {
return InitPromise::CreateAndReject(
"NrIceCtx::Create failed",
__func__);
}
mIceCtx->SignalConnectionStateChange.connect(
this, &MediaTransportHandlerSTS::OnConnectionStateChange);
mDNSResolver = MakeRefPtr<NrIceResolver>();
nsresult rv;
if (NS_FAILED(rv = mDNSResolver->Init())) {
CSFLogError(LOGTAG,
"%s: Failed to initialize dns resolver",
__FUNCTION__);
return InitPromise::CreateAndReject(
"Failed to initialize dns resolver", __func__);
}
if (NS_FAILED(rv = mIceCtx->SetResolver(
mDNSResolver->AllocateResolver()))) {
CSFLogError(LOGTAG,
"%s: Failed to get dns resolver",
__FUNCTION__);
return InitPromise::CreateAndReject(
"Failed to get dns resolver", __func__);
}
CSFLogDebug(LOGTAG,
"%s done", __func__);
return InitPromise::CreateAndResolve(true, __func__);
});
});
}
using ParsedIceServer = IceServerParser::ParsedIceServer;
nsresult MediaTransportHandlerSTS::SetIceConfig(
const nsTArray<dom::RTCIceServer>& aIceServers,
dom::RTCIceTransportPolicy aIcePolicy) {
auto result = IceServerParser::Parse(aIceServers);
if (result.isErr()) {
// Discard the detailed ErrorResult; callers at this level use nsresult.
result.unwrapErr().SuppressException();
return NS_ERROR_FAILURE;
}
nsTArray<ParsedIceServer> entries = result.unwrap();
MOZ_RELEASE_ASSERT(mInitPromise);
mInitPromise->Then(
mStsThread, __func__,
[this, aIcePolicy, entries = std::move(entries),
self = RefPtr<MediaTransportHandlerSTS>(this)]() {
if (!mIceCtx) {
CSFLogError(LOGTAG,
"%s: mIceCtx is null", __FUNCTION__);
return;
}
NrIceCtx::Config config;
config.mPolicy = toNrIcePolicy(aIcePolicy);
if (config.mPolicy == NrIceCtx::ICE_POLICY_ALL && mForceNoHost) {
config.mPolicy = NrIceCtx::ICE_POLICY_NO_HOST;
}
config.mAllowLoopback = mAllowLoopback;
config.mAllowLinkLocal = mAllowLinkLocal;
config.mNatSimulatorConfig = mNatConfig;
nsresult rv;
if (NS_FAILED(rv = mIceCtx->SetIceServers(entries, mTurnDisabled))) {
CSFLogError(LOGTAG,
"%s: Failed to set ICE servers", __FUNCTION__);
return;
}
if (NS_FAILED(rv = mIceCtx->SetIceConfig(config))) {
CSFLogError(LOGTAG,
"%s: Failed to set config", __FUNCTION__);
}
});
return NS_OK;
}
void MediaTransportHandlerSTS::Shutdown() {
CSFLogDebug(LOGTAG,
"%s", __func__);
MOZ_ASSERT(NS_IsMainThread());
mStsThread->Dispatch(NewNonOwningRunnableMethod(
__func__, this, &MediaTransportHandlerSTS::Shutdown_s));
}
void MediaTransportHandlerSTS::Shutdown_s() {
CSFLogDebug(LOGTAG,
"%s", __func__);
// Clear the transports before destroying the ice ctx so that
// the close_notify alerts have a chance to be sent as the
// TransportFlow destructors execute.
mTransports.clear();
if (mIceCtx) {
NrIceStats stats = mIceCtx->Destroy();
CSFLogDebug(LOGTAG,
"Ice Telemetry: stun (retransmits: %d)"
" turn (401s: %d 403s: %d 438s: %d)",
stats.stun_retransmits, stats.turn_401s, stats.turn_403s,
stats.turn_438s);
}
mIceCtx = nullptr;
mDNSResolver = nullptr;
}
void MediaTransportHandlerSTS::Destroy() {
CSFLogDebug(LOGTAG,
"%s %p", __func__, this);
// Our "destruction tour" starts on main, because we need to deregister.
if (!NS_IsMainThread()) {
GetMainThreadSerialEventTarget()->Dispatch(
NewNonOwningRunnableMethod(
"MediaTransportHandlerSTS::Destroy", this,
&MediaTransportHandlerSTS::Destroy));
return;
}
MOZ_ASSERT(NS_IsMainThread());
if (STSShutdownHandler::Instance()) {
STSShutdownHandler::Instance()->Deregister(this);
Shutdown();
}
// mIceCtx still has a reference to us via sigslot! We must dispach to STS,
// and clean up there.
nsresult rv = mStsThread->Dispatch(
NewNonOwningRunnableMethod(
"MediaTransportHandlerSTS::DestroyFinal", this,
&MediaTransportHandlerSTS::DestroyFinal));
if (NS_WARN_IF(NS_FAILED(rv))) {
CSFLogError(LOGTAG,
"Unable to dispatch to STS: why has the XPCOM shutdown handler "
"not been invoked?");
delete this;
}
}
void MediaTransportHandlerSTS::DestroyFinal() {
delete this; }
void MediaTransportHandlerSTS::SetProxyConfig(
NrSocketProxyConfig&& aProxyConfig) {
MOZ_RELEASE_ASSERT(mInitPromise);
mInitPromise->Then(
mStsThread, __func__,
[this, self = RefPtr<MediaTransportHandlerSTS>(this),
aProxyConfig = std::move(aProxyConfig)]()
mutable {
if (!mIceCtx) {
return;
// Probably due to XPCOM shutdown
}
mIceCtx->SetProxyConfig(std::move(aProxyConfig));
},
[](
const std::string& aError) {});
}
void MediaTransportHandlerSTS::EnsureProvisionalTransport(
const std::string& aTransportId,
const std::string& aUfrag,
const std::string& aPwd,
int aComponentCount) {
MOZ_RELEASE_ASSERT(mInitPromise);
mInitPromise->Then(
mStsThread, __func__,
[=, this, self = RefPtr<MediaTransportHandlerSTS>(this)]() {
if (!mIceCtx) {
return;
// Probably due to XPCOM shutdown
}
RefPtr<NrIceMediaStream> stream(mIceCtx->GetStream(aTransportId));
if (!stream) {
CSFLogDebug(LOGTAG,
"%s: Creating ICE media stream=%s components=%d",
mIceCtx->name().c_str(), aTransportId.c_str(),
aComponentCount);
std::ostringstream os;
os << mIceCtx->name() <<
" transport-id=" << aTransportId;
stream =
mIceCtx->CreateStream(aTransportId, os.str(), aComponentCount);
if (!stream) {
CSFLogError(LOGTAG,
"Failed to create ICE stream.");
return;
}
stream->SignalCandidate.connect(
this, &MediaTransportHandlerSTS::OnCandidateFound);
stream->SignalCandidateError.connect(
this, &MediaTransportHandlerSTS::OnCandidateError);
stream->SignalGatheringStateChange.connect(
this, &MediaTransportHandlerSTS::OnGatheringStateChange);
}
// Begins an ICE restart if this stream has a different ufrag/pwd
stream->SetIceCredentials(aUfrag, aPwd);
// Make sure there's an entry in mTransports
mTransports[aTransportId];
},
[](
const std::string& aError) {});
}
void MediaTransportHandlerSTS::ActivateTransport(
const std::string& aTransportId,
const std::string& aLocalUfrag,
const std::string& aLocalPwd, size_t aComponentCount,
const std::string& aUfrag,
const std::string& aPassword,
const nsTArray<uint8_t>& aKeyDer,
const nsTArray<uint8_t>& aCertDer,
SSLKEAType aAuthType,
bool aDtlsClient,
const DtlsDigestList& aDigests,
bool aPrivacyRequested) {
MOZ_RELEASE_ASSERT(mInitPromise);
mInitPromise->Then(
mStsThread, __func__,
[=, this, keyDer = aKeyDer.Clone(), certDer = aCertDer.Clone(),
self = RefPtr<MediaTransportHandlerSTS>(this)]() {
if (!mIceCtx) {
return;
// Probably due to XPCOM shutdown
}
MOZ_ASSERT(aComponentCount);
RefPtr<DtlsIdentity> dtlsIdentity(
DtlsIdentity::Deserialize(keyDer, certDer, aAuthType));
if (!dtlsIdentity) {
MOZ_ASSERT(
false);
return;
}
RefPtr<NrIceMediaStream> stream(mIceCtx->GetStream(aTransportId));
if (!stream) {
MOZ_ASSERT(
false);
return;
}
CSFLogDebug(LOGTAG,
"%s: Activating ICE media stream=%s components=%u",
mIceCtx->name().c_str(), aTransportId.c_str(),
static_cast<
unsigned>(aComponentCount));
std::vector<std::string> attrs;
attrs.reserve(
2 /* ufrag + pwd */);
attrs.push_back(
"ice-ufrag:" + aUfrag);
attrs.push_back(
"ice-pwd:" + aPassword);
// If we started an ICE restart in EnsureProvisionalTransport, this is
// where we decide whether to commit or rollback.
nsresult rv = stream->ConnectToPeer(aLocalUfrag, aLocalPwd, attrs);
if (NS_FAILED(rv)) {
CSFLogError(LOGTAG,
"Couldn't parse ICE attributes, rv=%u",
static_cast<
unsigned>(rv));
MOZ_ASSERT(
false);
return;
}
Transport transport = mTransports[aTransportId];
if (!transport.mFlow) {
transport.mFlow =
CreateTransportFlow(aTransportId,
false, dtlsIdentity,
aDtlsClient, aDigests, aPrivacyRequested);
if (!transport.mFlow) {
return;
}
TransportLayer* dtls =
transport.mFlow->GetLayer(TransportLayerDtls::ID());
dtls->SignalStateChange.connect(
this, &MediaTransportHandlerSTS::OnStateChange);
if (aComponentCount <
2) {
dtls->SignalStateChange.connect(
this, &MediaTransportHandlerSTS::OnRtcpStateChange);
}
}
if (aComponentCount ==
2) {
if (!transport.mRtcpFlow) {
transport.mRtcpFlow =
CreateTransportFlow(aTransportId, true, dtlsIdentity,
aDtlsClient, aDigests, aPrivacyRequested);
if (!transport.mRtcpFlow) {
return;
}
TransportLayer* dtls =
transport.mRtcpFlow->GetLayer(TransportLayerDtls::ID());
dtls->SignalStateChange.connect(
this, &MediaTransportHandlerSTS::OnRtcpStateChange);
}
}
else {
transport.mRtcpFlow = nullptr;
// components are 1-indexed
stream->DisableComponent(
2);
}
mTransports[aTransportId] = transport;
},
[](
const std::string& aError) {});
}
void MediaTransportHandlerSTS::SetTargetForDefaultLocalAddressLookup(
const std::string& aTargetIp, uint16_t aTargetPort) {
MOZ_RELEASE_ASSERT(mInitPromise);
mInitPromise->Then(
mStsThread, __func__,
[=, this, self = RefPtr<MediaTransportHandlerSTS>(this)]() {
if (!mIceCtx) {
return;
// Probably due to XPCOM shutdown
}
mIceCtx->SetTargetForDefaultLocalAddressLookup(aTargetIp, aTargetPort);
},
[](
const std::string& aError) {});
}
void MediaTransportHandlerSTS::StartIceGathering(
bool aDefaultRouteOnly,
bool aObfuscateHostAddresses,
const nsTArray<NrIceStunAddr>& aStunAddrs) {
MOZ_RELEASE_ASSERT(mInitPromise);
mInitPromise->Then(
mStsThread, __func__,
[=, this, stunAddrs = aStunAddrs.Clone(),
self = RefPtr<MediaTransportHandlerSTS>(this)]() {
if (!mIceCtx) {
return;
// Probably due to XPCOM shutdown
}
mObfuscateHostAddresses = aObfuscateHostAddresses;
// Belt and suspenders - in e10s mode, the call below to SetStunAddrs
// needs to have the proper flags set on ice ctx. For non-e10s,
// setting those flags happens in StartGathering. We could probably
// just set them here, and only do it here.
mIceCtx->SetCtxFlags(aDefaultRouteOnly);
if (stunAddrs.Length()) {
mIceCtx->SetStunAddrs(stunAddrs);
}
// Start gathering, but only if there are streams
if (!mIceCtx->GetStreams().empty()) {
mIceCtx->StartGathering(aDefaultRouteOnly, aObfuscateHostAddresses);
}
},
[](
const std::string& aError) {});
}
void MediaTransportHandlerSTS::StartIceChecks(
bool aIsControlling,
const std::vector<std::string>& aIceOptions) {
MOZ_RELEASE_ASSERT(mInitPromise);
mInitPromise->Then(
mStsThread, __func__,
[=, this, self = RefPtr<MediaTransportHandlerSTS>(this)]() {
if (!mIceCtx) {
return;
// Probably due to XPCOM shutdown
}
nsresult rv = mIceCtx->ParseGlobalAttributes(aIceOptions);
if (NS_FAILED(rv)) {
CSFLogError(LOGTAG,
"%s: couldn't parse global parameters",
__FUNCTION__);
return;
}
rv = mIceCtx->SetControlling(aIsControlling ? NrIceCtx::ICE_CONTROLLING
: NrIceCtx::ICE_CONTROLLED);
if (NS_FAILED(rv)) {
CSFLogError(LOGTAG,
"%s: couldn't set controlling to %d",
__FUNCTION__, aIsControlling);
return;
}
rv = mIceCtx->StartChecks();
if (NS_FAILED(rv)) {
CSFLogError(LOGTAG,
"%s: couldn't start checks", __FUNCTION__);
return;
}
},
[](
const std::string& aError) {});
}
void TokenizeCandidate(
const std::string& aCandidate,
std::vector<std::string>& aTokens) {
aTokens.clear();
std::istringstream iss(aCandidate);
std::string token;
while (std::getline(iss, token,
' ')) {
aTokens.push_back(token);
}
}
void MediaTransportHandlerSTS::AddIceCandidate(
const std::string& aTransportId,
const std::string& aCandidate,
const std::string& aUfrag,
const std::string& aObfuscatedAddress) {
MOZ_RELEASE_ASSERT(mInitPromise);
mInitPromise->Then(
mStsThread, __func__,
[=, this, self = RefPtr<MediaTransportHandlerSTS>(this)]() {
if (!mIceCtx) {
return;
// Probably due to XPCOM shutdown
}
std::vector<std::string> tokens;
TokenizeCandidate(aCandidate, tokens);
RefPtr<NrIceMediaStream> stream(mIceCtx->GetStream(aTransportId));
if (!stream) {
CSFLogError(LOGTAG,
"No ICE stream for candidate with transport id %s: %s",
aTransportId.c_str(), aCandidate.c_str());
return;
}
nsresult rv = stream->ParseTrickleCandidate(aCandidate, aUfrag,
aObfuscatedAddress);
if (NS_SUCCEEDED(rv)) {
// If the address is not obfuscated, we want to track it as
// explicitly signaled so that we know it is fine to reveal
// the address later on.
if (mObfuscateHostAddresses && tokens.size() >
4 &&
aObfuscatedAddress.empty()) {
mSignaledAddresses.insert(tokens[
4]);
}
}
else {
CSFLogError(LOGTAG,
"Couldn't process ICE candidate with transport id %s: "
"%s",
aTransportId.c_str(), aCandidate.c_str());
}
},
[](
const std::string& aError) {});
}
void MediaTransportHandlerSTS::UpdateNetworkState(
bool aOnline) {
MOZ_RELEASE_ASSERT(mInitPromise);
mInitPromise->Then(
mStsThread, __func__,
[=, this, self = RefPtr<MediaTransportHandlerSTS>(this)]() {
if (!mIceCtx) {
return;
// Probably due to XPCOM shutdown
}
mIceCtx->UpdateNetworkState(aOnline);
},
[](
const std::string& aError) {});
}
void MediaTransportHandlerSTS::RemoveTransportsExcept(
const std::set<std::string>& aTransportIds) {
MOZ_RELEASE_ASSERT(mInitPromise);
mInitPromise->Then(
mStsThread, __func__,
[=, this, self = RefPtr<MediaTransportHandlerSTS>(this)]() {
if (!mIceCtx) {
return;
// Probably due to XPCOM shutdown
}
for (
auto it = mTransports.begin(); it != mTransports.end();) {
const std::string transportId(it->first);
if (!aTransportIds.count(transportId)) {
OnStateChange(transportId, TransportLayer::TS_CLOSED, {});
OnRtcpStateChange(transportId, TransportLayer::TS_CLOSED);
// Erase the transport before destroying the ice stream so that
// the close_notify alerts have a chance to be sent as the
// TransportFlow destructors execute.
it = mTransports.erase(it);
// We're already on the STS thread, but the TransportFlow
// destructor executed when mTransports.erase(it) is called
// above dispatches the call to DestroyFinal to the STS thread. If
// we don't also dispatch the call to destroy the NrIceMediaStream
// to the STS thread, it will tear down the NrIceMediaStream
// before the TransportFlow is destroyed. Without a valid
// NrIceMediaStream the close_notify alert cannot be sent.
mStsThread->Dispatch(NS_NewRunnableFunction(
__func__, [iceCtx = RefPtr<NrIceCtx>(mIceCtx), transportId] {
iceCtx->DestroyStream(transportId);
}));
}
else {
MOZ_ASSERT(it->second.mFlow);
++it;
}
}
},
[](
const std::string& aError) {});
}
void MediaTransportHandlerSTS::SendPacket(
const std::string& aTransportId,
MediaPacket&& aPacket) {
MOZ_RELEASE_ASSERT(mInitPromise);
mInitPromise->Then(
mStsThread, __func__,
[this, self = RefPtr<MediaTransportHandlerSTS>(this), aTransportId,
aPacket = std::move(aPacket)]()
mutable {
if (!mIceCtx) {
return;
// Probably due to XPCOM shutdown
}
MOZ_ASSERT(aPacket.type() != MediaPacket::UNCLASSIFIED);
RefPtr<TransportFlow> flow =
GetTransportFlow(aTransportId, aPacket.type() == MediaPacket::RTCP);
if (!flow) {
CSFLogError(LOGTAG,
"%s: No such transport flow (%s) for outgoing packet",
mIceCtx->name().c_str(), aTransportId.c_str());
return;
}
TransportLayer* layer = nullptr;
switch (aPacket.type()) {
case MediaPacket::SCTP:
layer = flow->GetLayer(TransportLayerDtls::ID());
break;
case MediaPacket::RTP:
case MediaPacket::RTCP:
layer = flow->GetLayer(TransportLayerSrtp::ID());
break;
default:
// Maybe it would be useful to allow the injection of other packet
// types for testing?
MOZ_ASSERT(
false);
return;
}
MOZ_ASSERT(layer);
if (
int error = layer->SendPacket(aPacket); error <
0) {
CSFLogError(LOGTAG,
"%s: Transport flow (%s) failed to send packet. error=%d",
mIceCtx->name().c_str(), aTransportId.c_str(), error);
}
},
[](
const std::string& aError) {});
}
TransportLayer::State MediaTransportHandler::GetState(
const std::string& aTransportId,
bool aRtcp)
const {
MutexAutoLock lock(mStateCacheMutex);
const std::map<std::string, TransportLayer::State>* cache = nullptr;
if (aRtcp) {
cache = &mRtcpStateCache;
}
else {
cache = &mStateCache;
}
auto it = cache->find(aTransportId);
if (it != cache->end()) {
return it->second;
}
return TransportLayer::TS_NONE;
}
void MediaTransportHandler::OnCandidate(
const std::string& aTransportId,
CandidateInfo&& aCandidateInfo) {
mCandidateGathered.Notify(aTransportId, std::move(aCandidateInfo));
}
void MediaTransportHandler::OnCandidateError(
IceCandidateErrorInfo&& aErrorInfo) {
mCandidateError.Notify(std::move(aErrorInfo));
}
void MediaTransportHandler::OnAlpnNegotiated(
const std::string& aAlpn) {
const bool privacyRequested = aAlpn ==
"c-webrtc";
mAlpnNegotiated.Notify(aAlpn, privacyRequested);
}
void MediaTransportHandler::OnGatheringStateChange(
const std::string& aTransportId, dom::RTCIceGathererState aState) {
mGatheringStateChange.Notify(aTransportId, aState);
}
void MediaTransportHandler::OnConnectionStateChange(
const std::string& aTransportId, dom::RTCIceTransportState aState) {
mConnectionStateChange.Notify(aTransportId, aState);
}
void MediaTransportHandler::OnPacketReceived(std::string&& aTransportId,
MediaPacket&& aPacket) {
switch (aPacket.type()) {
case MediaPacket::UNCLASSIFIED:
case MediaPacket::DTLS:
case MediaPacket::SRTP:
case MediaPacket::SRTCP:
// Shouldn't happen, and nothing would care if it did
break;
case MediaPacket::RTP:
case MediaPacket::RTCP:
mRtpPacketReceived.Notify(std::forward<std::string>(aTransportId),
std::forward<MediaPacket>(aPacket));
break;
case MediaPacket::SCTP:
mSctpPacketReceived.Notify(std::forward<std::string>(aTransportId),
std::forward<MediaPacket>(aPacket));
break;
}
}
void MediaTransportHandler::OnEncryptedSending(
const std::string& aTransportId,
MediaPacket&& aPacket) {
mEncryptedSending.Notify(aTransportId, std::move(aPacket));
}
void MediaTransportHandler::OnStateChange(
const std::string& aTransportId, TransportLayer::State aState,
nsTArray<nsTArray<uint8_t>>&& aRemoteCerts) {
{
MutexAutoLock lock(mStateCacheMutex);
mStateCache[aTransportId] = aState;
}
mStateChange.Notify(aTransportId, aState, std::move(aRemoteCerts));
}
void MediaTransportHandler::OnRtcpStateChange(
const std::string& aTransportId,
TransportLayer::State aState) {
{
MutexAutoLock lock(mStateCacheMutex);
mRtcpStateCache[aTransportId] = aState;
}
mRtcpStateChange.Notify(aTransportId, aState);
}
static uint16_t ToDtlsWireVersion(uint16_t aProtocolVersion) {
switch (aProtocolVersion) {
case SSL_LIBRARY_VERSION_DTLS_1_0:
return SSL_LIBRARY_VERSION_DTLS_1_0_WIRE;
case SSL_LIBRARY_VERSION_DTLS_1_2:
return SSL_LIBRARY_VERSION_DTLS_1_2_WIRE;
case SSL_LIBRARY_VERSION_DTLS_1_3:
return SSL_LIBRARY_VERSION_DTLS_1_3_WIRE;
default:
return 0;
}
}
RefPtr<dom::RTCStatsPromise> MediaTransportHandlerSTS::GetIceStats(
const std::string& aTransportId, DOMHighResTimeStamp aNow) {
MOZ_RELEASE_ASSERT(mInitPromise);
return mInitPromise->Then(
mStsThread, __func__, [=, this, self = RefPtr(this)]() {
auto stats = MakeUnique<dom::RTCStatsCollection>();
if (mIceCtx) {
dom::RTCIceRole iceRole =
mIceCtx->GetControlling() == NrIceCtx::ICE_CONTROLLING
? dom::RTCIceRole::Controlling
: dom::RTCIceRole::Controlled;
for (
const auto& stream : mIceCtx->GetStreams()) {
if (aTransportId.empty() || aTransportId == stream->GetId()) {
dom::RTCTransportStats transport;
transport.mId.Construct(
NS_ConvertASCIItoUTF16(stream->GetId().c_str()));
transport.mTimestamp.Construct(aNow);
transport.mType.Construct(dom::RTCStatsType::Transport);
transport.mIceRole.Construct(iceRole);
std::string ufrag = stream->GetUfrag();
if (!ufrag.empty()) {
transport.mIceLocalUsernameFragment.Construct(
NS_ConvertASCIItoUTF16(ufrag.c_str()));
}
switch (stream->state()) {
case NrIceMediaStream::ICE_CONNECTING:
transport.mIceState.Construct(
dom::RTCIceTransportState::Checking);
break;
case NrIceMediaStream::ICE_OPEN:
transport.mIceState.Construct(
dom::RTCIceTransportState::Connected);
break;
case NrIceMediaStream::ICE_CLOSED:
transport.mIceState.Construct(
dom::RTCIceTransportState::Closed);
break;
}
// XXX(Bug 1225723) Determine if dtlsState should be `required`.
transport.mDtlsState = dom::RTCDtlsTransportState::
New;
auto transportIt = mTransports.find(stream->GetId());
if (transportIt != mTransports.end() &&
transportIt->second.mFlow) {
if (
auto* dtlsLayer = static_cast<TransportLayerDtls*>(
transportIt->second.mFlow->GetLayer(
TransportLayerDtls::ID()))) {
transport.mDtlsRole.Construct(
dtlsLayer->role() == TransportLayerDtls::CLIENT
? dom::RTCDtlsRole::Client
: dom::RTCDtlsRole::Server);
switch (dtlsLayer->state()) {
case TransportLayer::TS_NONE:
case TransportLayer::TS_INIT:
transport.mDtlsState = dom::RTCDtlsTransportState::
New;
break;
case TransportLayer::TS_CONNECTING:
transport.mDtlsState =
dom::RTCDtlsTransportState::Connecting;
break;
case TransportLayer::TS_OPEN:
transport.mDtlsState =
dom::RTCDtlsTransportState::Connected;
break;
case TransportLayer::TS_CLOSED:
transport.mDtlsState = dom::RTCDtlsTransportState::Closed;
break;
case TransportLayer::TS_ERROR:
transport.mDtlsState = dom::RTCDtlsTransportState::Failed;
break;
}
uint16_t srtpCipher =
0;
if (NS_SUCCEEDED(dtlsLayer->GetSrtpCipher(&srtpCipher))) {
const char* name =
TransportLayerDtls::GetSrtpCipherName(srtpCipher);
if (name) {
transport.mSrtpCipher.Construct(
NS_ConvertASCIItoUTF16(name));
}
}
SSLChannelInfo channelInfo;
if (NS_SUCCEEDED(dtlsLayer->GetChannelInfo(&channelInfo))) {
if (uint16_t v =
ToDtlsWireVersion(channelInfo.protocolVersion)) {
transport.mTlsVersion.Construct(
nsFmtString(u
"{:04X}", v));
}
SSLCipherSuiteInfo info;
if (SSL_GetCipherSuiteInfo(channelInfo.cipherSuite, &info,
sizeof(info)) == SECSuccess &&
info.cipherSuiteName) {
transport.mDtlsCipher.Construct(
NS_ConvertASCIItoUTF16(info.cipherSuiteName));
}
}
}
}
// XXX(Bug 2037532) Fill missing fields on the transport.
GetIceStats(*stream, aNow, stats.get(), transport);
// XXX(Bug 1632090) Instead of extending the array 1-by-1 (which
// might involve multiple reallocations) and potentially crashing
// here, SetCapacity could be called outside the loop once.
if (!stats->mTransportStats.AppendElement(transport, fallible)) {
mozalloc_handle_oom(
0);
}
}
}
}
return dom::RTCStatsPromise::CreateAndResolve(std::move(stats),
__func__);
});
}
RefPtr<MediaTransportHandler::IceLogPromise>
MediaTransportHandlerSTS::GetIceLog(
const nsCString& aPattern) {
return InvokeAsync(
mStsThread, __func__, [=, self = RefPtr<MediaTransportHandlerSTS>(this)] {
dom::Sequence<nsString> converted;
RLogConnector* logs = RLogConnector::GetInstance();
std::deque<std::string> result;
// Might not exist yet.
if (logs) {
logs->Filter(aPattern.get(),
0, &result);
}
/// XXX(Bug 1631386) Check if we should reject the promise instead of
/// crashing in an OOM situation.
if (!converted.SetCapacity(result.size(), fallible)) {
mozalloc_handle_oom(
sizeof(nsString) * result.size());
}
for (
auto& line : result) {
// Cannot fail, SetCapacity was called before.
(
void)converted.AppendElement(NS_ConvertUTF8toUTF16(line.c_str()),
fallible);
}
return IceLogPromise::CreateAndResolve(std::move(converted), __func__);
});
}
void MediaTransportHandlerSTS::ClearIceLog() {
if (!mStsThread->IsOnCurrentThread()) {
mStsThread->Dispatch(WrapRunnable(RefPtr<MediaTransportHandlerSTS>(this),
&MediaTransportHandlerSTS::ClearIceLog),
NS_DISPATCH_NORMAL);
return;
}
RLogConnector* logs = RLogConnector::GetInstance();
if (logs) {
logs->Clear();
}
}
void MediaTransportHandlerSTS::EnterPrivateMode() {
if (!mStsThread->IsOnCurrentThread()) {
mStsThread->Dispatch(
WrapRunnable(RefPtr<MediaTransportHandlerSTS>(this),
&MediaTransportHandlerSTS::EnterPrivateMode),
NS_DISPATCH_NORMAL);
return;
}
RLogConnector::GetInstance()->EnterPrivateMode();
}
void MediaTransportHandlerSTS::ExitPrivateMode() {
if (!mStsThread->IsOnCurrentThread()) {
mStsThread->Dispatch(
WrapRunnable(RefPtr<MediaTransportHandlerSTS>(this),
&MediaTransportHandlerSTS::ExitPrivateMode),
NS_DISPATCH_NORMAL);
return;
}
auto* log = RLogConnector::GetInstance();
MOZ_ASSERT(log);
if (log) {
log->ExitPrivateMode();
}
}
static void ToRTCIceCandidateStats(
const std::vector<NrIceCandidate>& candidates,
dom::RTCStatsType candidateType,
const nsString& transportId,
DOMHighResTimeStamp now, dom::RTCStatsCollection* stats,
bool obfuscateHostAddresses,
const std::set<std::string>& signaledAddresses) {
MOZ_ASSERT(stats);
for (
const auto& candidate : candidates) {
dom::RTCIceCandidateStats cand;
cand.mType.Construct(candidateType);
NS_ConvertASCIItoUTF16 codeword(candidate.codeword.c_str());
cand.mTransportId = transportId;
cand.mId.Construct(codeword);
cand.mTimestamp.Construct(now);
cand.mCandidateType.Construct(dom::RTCIceCandidateType(candidate.type));
cand.mPriority.Construct(candidate.priority);
// https://tools.ietf.org/html/draft-ietf-rtcweb-mdns-ice-candidates-03#section-3.3.1
// This obfuscates the address with the mDNS address if one exists
if (!candidate.mdns_addr.empty()) {
cand.mAddress.Construct(
NS_ConvertASCIItoUTF16(candidate.mdns_addr.c_str()));
}
else if (obfuscateHostAddresses &&
candidate.type == NrIceCandidate::ICE_PEER_REFLEXIVE &&
signaledAddresses.find(candidate.cand_addr.host) ==
signaledAddresses.end()) {
cand.mAddress.Construct(NS_ConvertASCIItoUTF16(
"(redacted)"));
}
else {
cand.mAddress.Construct(
NS_ConvertASCIItoUTF16(candidate.cand_addr.host.c_str()));
}
cand.mPort.Construct(candidate.cand_addr.port);
cand.mProtocol.Construct(
NS_ConvertASCIItoUTF16(candidate.cand_addr.transport.c_str()));
if (candidateType == dom::RTCStatsType::Local_candidate &&
dom::RTCIceCandidateType(candidate.type) ==
dom::RTCIceCandidateType::Relay) {
cand.mRelayProtocol.Construct(
NS_ConvertASCIItoUTF16(candidate.local_addr.transport.c_str()));
}
cand.mUsernameFragment.Construct(
NS_ConvertASCIItoUTF16(candidate.username_fragment.c_str()));
// Foundation is not set for peer-reflexive candidates.
if (candidate.type != NrIceCandidate::ICE_PEER_REFLEXIVE) {
cand.mFoundation.Construct(
NS_ConvertASCIItoUTF16(candidate.foundation.c_str()));
}
if (candidate.tcp_type == NrIceCandidate::ICE_ACTIVE) {
cand.mTcpType.Construct(dom::RTCIceTcpCandidateType::Active);
}
else if (candidate.tcp_type == NrIceCandidate::ICE_PASSIVE) {
cand.mTcpType.Construct(dom::RTCIceTcpCandidateType::Passive);
}
cand.mProxied.Construct(NS_ConvertASCIItoUTF16(
candidate.is_proxied ?
"proxied" :
"non-proxied"));
if (!stats->mIceCandidateStats.AppendElement(cand, fallible)) {
// XXX(Bug 1632090) Instead of extending the array 1-by-1 (which might
// involve multiple reallocations) and potentially crashing here,
// SetCapacity could be called outside the loop once.
mozalloc_handle_oom(
0);
}
if (candidate.trickled) {
if (!stats->mTrickledIceCandidateStats.AppendElement(cand, fallible)) {
mozalloc_handle_oom(
0);
}
}
}
}
void MediaTransportHandlerSTS::GetIceStats(
const NrIceMediaStream& aStream, DOMHighResTimeStamp aNow,
dom::RTCStatsCollection* aStats, dom::RTCTransportStats& aTransport)
const {
MOZ_ASSERT(mStsThread->IsOnCurrentThread());
NS_ConvertASCIItoUTF16 transportId(aStream.GetId().c_str());
std::vector<NrIceCandidatePair> candPairs;
nsresult res = aStream.GetCandidatePairs(&candPairs);
if (NS_FAILED(res)) {
CSFLogError(LOGTAG,
"%s: Error getting candidate pairs for transport id \"%s\
"",
__FUNCTION__, aStream.GetId().c_str());
return;
}
for (
auto& candPair : candPairs) {
NS_ConvertASCIItoUTF16 codeword(candPair.codeword.c_str());
NS_ConvertASCIItoUTF16 localCodeword(candPair.local.codeword.c_str());
NS_ConvertASCIItoUTF16 remoteCodeword(candPair.remote.codeword.c_str());
// Only expose candidate-pair statistics to chrome, until we've thought
// through the implications of exposing it to content.
dom::RTCIceCandidatePairStats s;
s.mId.Construct(codeword);
s.mTransportId = transportId;
s.mTimestamp.Construct(aNow);
s.mType.Construct(dom::RTCStatsType::Candidate_pair);
s.mLocalCandidateId.Construct(localCodeword);
s.mRemoteCandidateId.Construct(remoteCodeword);
s.mNominated.Construct(candPair.nominated);
s.mWritable.Construct(candPair.writable);
s.mReadable.Construct(candPair.readable);
s.mPriority.Construct(candPair.priority);
s.mSelected.Construct(candPair.selected);
s.mBytesSent.Construct(candPair.bytes_sent);
s.mBytesReceived.Construct(candPair.bytes_recvd);
s.mLastPacketSentTimestamp.Construct(candPair.ms_since_last_send);
s.mLastPacketReceivedTimestamp.Construct(candPair.ms_since_last_recv);
s.mState.Construct(dom::RTCStatsIceCandidatePairState(candPair.state));
s.mResponsesReceived.Construct(candPair.responses_recvd);
s.mCurrentRoundTripTime.Construct(candPair.current_rtt_ms /
1000.
0);
s.mTotalRoundTripTime.Construct(candPair.total_rtt_ms /
1000.
0);
s.mComponentId.Construct(candPair.component_id);
if (candPair.selected && candPair.component_id ==
1) {
aTransport.mSelectedCandidatePairId.Construct(codeword);
}
if (!aStats->mIceCandidatePairStats.AppendElement(s, fallible)) {
// XXX(Bug 1632090) Instead of extending the array 1-by-1 (which might
// involve multiple reallocations) and potentially crashing here,
// SetCapacity could be called outside the loop once.
mozalloc_handle_oom(
0);
}
}
std::vector<NrIceCandidate> candidates;
if (NS_SUCCEEDED(aStream.GetLocalCandidates(&candidates))) {
ToRTCIceCandidateStats(candidates, dom::RTCStatsType::Local_candidate,
transportId, aNow, aStats, mObfuscateHostAddresses,
mSignaledAddresses);
// add the local candidates unparsed string to a sequence
for (
const auto& candidate : candidates) {
if (!aStats->mRawLocalCandidates.AppendElement(
NS_ConvertASCIItoUTF16(candidate.label.c_str()), fallible)) {
// XXX(Bug 1632090) Instead of extending the array 1-by-1 (which might
// involve multiple reallocations) and potentially crashing here,
// SetCapacity could be called outside the loop once.
mozalloc_handle_oom(
0);
}
}
}
candidates.clear();
if (NS_SUCCEEDED(aStream.GetRemoteCandidates(&candidates))) {
ToRTCIceCandidateStats(candidates, dom::RTCStatsType::Remote_candidate,
transportId, aNow, aStats, mObfuscateHostAddresses,
mSignaledAddresses);
// add the remote candidates unparsed string to a sequence
for (
const auto& candidate : candidates) {
if (!aStats->mRawRemoteCandidates.AppendElement(
NS_ConvertASCIItoUTF16(candidate.label.c_str()), fallible)) {
// XXX(Bug 1632090) Instead of extending the array 1-by-1 (which might
// involve multiple reallocations) and potentially crashing here,
// SetCapacity could be called outside the loop once.
mozalloc_handle_oom(
0);
}
}
}
}
RefPtr<TransportFlow> MediaTransportHandlerSTS::GetTransportFlow(
const std::string& aTransportId,
bool aIsRtcp)
const {
auto it = mTransports.find(aTransportId);
if (it == mTransports.end()) {
return nullptr;
}
if (aIsRtcp) {
return it->second.mRtcpFlow ? it->second.mRtcpFlow : it->second.mFlow;
;
}
return it->second.mFlow;
}
RefPtr<TransportFlow> MediaTransportHandlerSTS::CreateTransportFlow(
const std::string& aTransportId,
bool aIsRtcp,
const RefPtr<DtlsIdentity>& aDtlsIdentity,
bool aDtlsClient,
const DtlsDigestList& aDigests,
bool aPrivacyRequested) {
nsresult rv;
RefPtr flow = MakeRefPtr<TransportFlow>(aTransportId);
// The media streams are made on STS so we need to defer setup.
auto ice = MakeUnique<TransportLayerIce>();
auto dtls = MakeUnique<TransportLayerDtls>();
auto srtp = MakeUnique<TransportLayerSrtp>(*dtls);
dtls->SetRole(aDtlsClient ? TransportLayerDtls::CLIENT
: TransportLayerDtls::SERVER);
dtls->SetIdentity(aDtlsIdentity);
dtls->SetMinMaxVersion(
static_cast<TransportLayerDtls::Version>(mMinDtlsVersion),
static_cast<TransportLayerDtls::Version>(mMaxDtlsVersion));
for (
const auto& digest : aDigests) {
rv = dtls->SetVerificationDigest(digest);
if (NS_FAILED(rv)) {
CSFLogError(LOGTAG,
"Could not set fingerprint");
return nullptr;
}
}
std::vector<uint16_t> srtpCiphers =
TransportLayerDtls::GetDefaultSrtpCiphers();
rv = dtls->SetSrtpCiphers(srtpCiphers);
if (NS_FAILED(rv)) {
CSFLogError(LOGTAG,
"Couldn't set SRTP ciphers");
return nullptr;
}
// Always permits negotiation of the confidential mode.
// Only allow non-confidential (which is an allowed default),
// if we aren't confidential.
std::set<std::string> alpn = {
"c-webrtc"};
std::string alpnDefault;
if (!aPrivacyRequested) {
alpnDefault =
"webrtc";
alpn.insert(alpnDefault);
}
rv = dtls->SetAlpn(alpn, alpnDefault);
if (NS_FAILED(rv)) {
CSFLogError(LOGTAG,
"Couldn't set ALPN");
return nullptr;
}
ice->SetParameters(mIceCtx->GetStream(aTransportId), aIsRtcp ?
2 :
1);
NS_ENSURE_SUCCESS(ice->Init(), nullptr);
NS_ENSURE_SUCCESS(dtls->Init(), nullptr);
NS_ENSURE_SUCCESS(srtp->Init(), nullptr);
dtls->Chain(ice.get());
srtp->Chain(ice.get());
dtls->SignalPacketReceived.connect(this,
&MediaTransportHandlerSTS::PacketReceived);
srtp->SignalPacketReceived.connect(this,
&MediaTransportHandlerSTS::PacketReceived);
ice->SignalPacketSending.connect(
this, &MediaTransportHandlerSTS::EncryptedPacketSending);
flow->PushLayer(ice.release());
flow->PushLayer(dtls.release());
flow->PushLayer(srtp.release());
return flow;
}
static mozilla::dom::RTCIceGathererState toDomIceGathererState(
NrIceMediaStream::GatheringState aState) {
switch (aState) {
case NrIceMediaStream::ICE_STREAM_GATHER_INIT:
return dom::RTCIceGathererState::
New;
case NrIceMediaStream::ICE_STREAM_GATHER_STARTED:
return dom::RTCIceGathererState::Gathering;
case NrIceMediaStream::ICE_STREAM_GATHER_COMPLETE:
return dom::RTCIceGathererState::Complete;
}
MOZ_CRASH();
}
void MediaTransportHandlerSTS::OnGatheringStateChange(
const std::string& aTransportId, NrIceMediaStream::GatheringState aState) {
OnGatheringStateChange(aTransportId, toDomIceGathererState(aState));
}
static mozilla::dom::RTCIceTransportState toDomIceTransportState(
NrIceCtx::ConnectionState aState) {
switch (aState) {
case NrIceCtx::ICE_CTX_INIT:
return dom::RTCIceTransportState::
New;
case NrIceCtx::ICE_CTX_CHECKING:
return dom::RTCIceTransportState::Checking;
case NrIceCtx::ICE_CTX_CONNECTED:
return dom::RTCIceTransportState::Connected;
case NrIceCtx::ICE_CTX_COMPLETED:
return dom::RTCIceTransportState::Completed;
case NrIceCtx::ICE_CTX_FAILED:
return dom::RTCIceTransportState::Failed;
case NrIceCtx::ICE_CTX_DISCONNECTED:
return dom::RTCIceTransportState::Disconnected;
case NrIceCtx::ICE_CTX_CLOSED:
return dom::RTCIceTransportState::Closed;
}
MOZ_CRASH();
}
void MediaTransportHandlerSTS::OnConnectionStateChange(
NrIceMediaStream* aIceStream, NrIceCtx::ConnectionState aState) {
OnConnectionStateChange(aIceStream->GetId(), toDomIceTransportState(aState));
}
// The stuff below here will eventually go into the MediaTransportChild class
void MediaTransportHandlerSTS::OnCandidateFound(
NrIceMediaStream* aStream,
const std::string& aCandidate,
const std::string& aUfrag,
const std::string& aMDNSAddr,
const std::string& aActualAddr) {
CandidateInfo info;
info.mCandidate = aCandidate;
MOZ_ASSERT(!aUfrag.empty());
info.mUfrag = aUfrag;
NrIceCandidate defaultRtpCandidate;
NrIceCandidate defaultRtcpCandidate;
nsresult rv = aStream->GetDefaultCandidate(
1, &defaultRtpCandidate);
if (NS_SUCCEEDED(rv)) {
if (!defaultRtpCandidate.mdns_addr.empty()) {
info.mDefaultHostRtp =
"0.0.0.0";
info.mDefaultPortRtp =
9;
}
else {
info.mDefaultHostRtp = defaultRtpCandidate.cand_addr.host;
info.mDefaultPortRtp = defaultRtpCandidate.cand_addr.port;
}
}
else {
CSFLogError(LOGTAG,
"%s: GetDefaultCandidates failed for transport id %s, "
"res=%u",
__FUNCTION__, aStream->GetId().c_str(),
static_cast<
unsigned>(rv));
}
// Optional; component won't exist if doing rtcp-mux
if (NS_SUCCEEDED(aStream->GetDefaultCandidate(
2, &defaultRtcpCandidate))) {
if (!defaultRtcpCandidate.mdns_addr.empty()) {
info.mDefaultHostRtcp = defaultRtcpCandidate.mdns_addr;
}
else {
info.mDefaultHostRtcp = defaultRtcpCandidate.cand_addr.host;
}
info.mDefaultPortRtcp = defaultRtcpCandidate.cand_addr.port;
}
info.mMDNSAddress = aMDNSAddr;
info.mActualAddress = aActualAddr;
OnCandidate(aStream->GetId(), std::move(info));
}
void MediaTransportHandlerSTS::OnCandidateError(NrIceMediaStream* aStream,
const std::string& aAddress,
uint16_t aPort,
const std::string& aUrl,
uint16_t aErrorCode,
const std::string& aErrorText) {
IceCandidateErrorInfo info;
info.mAddress = aAddress;
info.mPort = aPort;
info.mUrl = aUrl;
info.mErrorCode = aErrorCode;
info.mErrorText = aErrorText;
OnCandidateError(std::move(info));
}
void MediaTransportHandlerSTS::OnStateChange(TransportLayer* aLayer,
TransportLayer::State aState) {
nsTArray<nsTArray<uint8_t>> remoteCerts;
if (aState == TransportLayer::TS_OPEN) {
MOZ_ASSERT(aLayer->id() == TransportLayerDtls::ID());
TransportLayerDtls* dtlsLayer = static_cast<TransportLayerDtls*>(aLayer);
OnAlpnNegotiated(dtlsLayer->GetNegotiatedAlpn());
remoteCerts = dtlsLayer->GetPeerCertChainDer();
}
// DTLS state indicates the readiness of the transport as a whole, because
// SRTP uses the keys from the DTLS handshake.
MediaTransportHandler::OnStateChange(aLayer->flow_id(), aState,
std::move(remoteCerts));
}
void MediaTransportHandlerSTS::OnRtcpStateChange(TransportLayer* aLayer,
TransportLayer::State aState) {
MediaTransportHandler::OnRtcpStateChange(aLayer->flow_id(), aState);
}
void MediaTransportHandlerSTS::PacketReceived(TransportLayer* aLayer,
MediaPacket& aPacket) {
MEDIA_TRANSPORT_HANDLER_PACKET_RECEIVED(aPacket);
OnPacketReceived(std::string(aLayer->flow_id()), std::move(aPacket));
}
void MediaTransportHandlerSTS::EncryptedPacketSending(TransportLayer* aLayer,
MediaPacket& aPacket) {
OnEncryptedSending(aLayer->flow_id(), std::move(aPacket));
}
}
// namespace mozilla
#undef MEDIA_TRANSPORT_HANDLER_PACKET_RECEIVED