diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
commit | 36d22d82aa202bb199967e9512281e9a53db42c9 (patch) | |
tree | 105e8c98ddea1c1e4784a60a5a6410fa416be2de /dom/media/webrtc/transport/nricectx.cpp | |
parent | Initial commit. (diff) | |
download | firefox-esr-upstream.tar.xz firefox-esr-upstream.zip |
Adding upstream version 115.7.0esr.upstream/115.7.0esrupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'dom/media/webrtc/transport/nricectx.cpp')
-rw-r--r-- | dom/media/webrtc/transport/nricectx.cpp | 1106 |
1 files changed, 1106 insertions, 0 deletions
diff --git a/dom/media/webrtc/transport/nricectx.cpp b/dom/media/webrtc/transport/nricectx.cpp new file mode 100644 index 0000000000..697eecdb07 --- /dev/null +++ b/dom/media/webrtc/transport/nricectx.cpp @@ -0,0 +1,1106 @@ +/* -*- mode: c++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* 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/. */ + +// Original author: ekr@rtfm.com + +// Some of this code is cut-and-pasted from nICEr. Copyright is: + +/* +Copyright (c) 2007, Adobe Systems, Incorporated +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + +* Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +* Neither the name of Adobe Systems, Network Resonance nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include <string> +#include <vector> + +#include "nr_socket_proxy_config.h" +#include "nsXULAppAPI.h" + +#include "logging.h" +#include "pk11pub.h" +#include "plbase64.h" + +#include "nsCOMPtr.h" +#include "nsError.h" +#include "nsNetCID.h" +#include "nsServiceManagerUtils.h" +#include "ScopedNSSTypes.h" +#include "runnable_utils.h" +#include "nsIUUIDGenerator.h" + +// nICEr includes +extern "C" { +#include "nr_api.h" +#include "registry.h" +#include "async_timer.h" +#include "r_crc32.h" +#include "r_memory.h" +#include "ice_reg.h" +#include "transport_addr.h" +#include "nr_crypto.h" +#include "nr_socket.h" +#include "nr_socket_local.h" +#include "stun_reg.h" +#include "stun_util.h" +#include "ice_codeword.h" +#include "ice_ctx.h" +#include "ice_candidate.h" +} + +// Local includes +#include "nricectx.h" +#include "nricemediastream.h" +#include "nr_socket_prsock.h" +#include "nrinterfaceprioritizer.h" +#include "rlogconnector.h" +#include "test_nr_socket.h" + +namespace mozilla { + +using std::shared_ptr; + +TimeStamp nr_socket_short_term_violation_time() { + return NrSocketBase::short_term_violation_time(); +} + +TimeStamp nr_socket_long_term_violation_time() { + return NrSocketBase::long_term_violation_time(); +} + +MOZ_MTLOG_MODULE("mtransport") + +const char kNrIceTransportUdp[] = "udp"; +const char kNrIceTransportTcp[] = "tcp"; +const char kNrIceTransportTls[] = "tls"; + +static bool initialized = false; + +static int noop(void** obj) { return 0; } + +static nr_socket_factory_vtbl ctx_socket_factory_vtbl = {nr_socket_local_create, + noop}; + +// Implement NSPR-based crypto algorithms +static int nr_crypto_nss_random_bytes(UCHAR* buf, size_t len) { + UniquePK11SlotInfo slot(PK11_GetInternalSlot()); + if (!slot) return R_INTERNAL; + + SECStatus rv = PK11_GenerateRandomOnSlot(slot.get(), buf, len); + if (rv != SECSuccess) return R_INTERNAL; + + return 0; +} + +static int nr_crypto_nss_hmac(UCHAR* key, size_t keyl, UCHAR* buf, size_t bufl, + UCHAR* result) { + CK_MECHANISM_TYPE mech = CKM_SHA_1_HMAC; + PK11SlotInfo* slot = nullptr; + MOZ_ASSERT(keyl > 0); + SECItem keyi = {siBuffer, key, static_cast<unsigned int>(keyl)}; + PK11SymKey* skey = nullptr; + PK11Context* hmac_ctx = nullptr; + SECStatus status; + unsigned int hmac_len; + SECItem param = {siBuffer, nullptr, 0}; + int err = R_INTERNAL; + + slot = PK11_GetInternalKeySlot(); + if (!slot) goto abort; + + skey = PK11_ImportSymKey(slot, mech, PK11_OriginUnwrap, CKA_SIGN, &keyi, + nullptr); + if (!skey) goto abort; + + hmac_ctx = PK11_CreateContextBySymKey(mech, CKA_SIGN, skey, ¶m); + if (!hmac_ctx) goto abort; + + status = PK11_DigestBegin(hmac_ctx); + if (status != SECSuccess) goto abort; + + status = PK11_DigestOp(hmac_ctx, buf, bufl); + if (status != SECSuccess) goto abort; + + status = PK11_DigestFinal(hmac_ctx, result, &hmac_len, 20); + if (status != SECSuccess) goto abort; + + MOZ_ASSERT(hmac_len == 20); + + err = 0; + +abort: + if (hmac_ctx) PK11_DestroyContext(hmac_ctx, PR_TRUE); + if (skey) PK11_FreeSymKey(skey); + if (slot) PK11_FreeSlot(slot); + + return err; +} + +static int nr_crypto_nss_md5(UCHAR* buf, size_t bufl, UCHAR* result) { + int err = R_INTERNAL; + SECStatus rv; + + const SECHashObject* ho = HASH_GetHashObject(HASH_AlgMD5); + MOZ_ASSERT(ho); + if (!ho) goto abort; + + MOZ_ASSERT(ho->length == 16); + + rv = HASH_HashBuf(ho->type, result, buf, bufl); + if (rv != SECSuccess) goto abort; + + err = 0; +abort: + return err; +} + +static nr_ice_crypto_vtbl nr_ice_crypto_nss_vtbl = { + nr_crypto_nss_random_bytes, nr_crypto_nss_hmac, nr_crypto_nss_md5}; + +nsresult NrIceStunServer::ToNicerStunStruct(nr_ice_stun_server* server) const { + int r; + + memset(server, 0, sizeof(nr_ice_stun_server)); + uint8_t protocol; + if (transport_ == kNrIceTransportUdp) { + protocol = IPPROTO_UDP; + } else if (transport_ == kNrIceTransportTcp) { + protocol = IPPROTO_TCP; + } else if (transport_ == kNrIceTransportTls) { + protocol = IPPROTO_TCP; + } else { + MOZ_MTLOG(ML_ERROR, "Unsupported STUN server transport: " << transport_); + return NS_ERROR_FAILURE; + } + + if (has_addr_) { + if (transport_ == kNrIceTransportTls) { + // Refuse to try TLS without an FQDN + return NS_ERROR_INVALID_ARG; + } + r = nr_praddr_to_transport_addr(&addr_, &server->addr, protocol, 0); + if (r) { + return NS_ERROR_FAILURE; + } + } else { + MOZ_ASSERT(sizeof(server->addr.fqdn) > host_.size()); + // Dummy information to keep nICEr happy + if (use_ipv6_if_fqdn_) { + nr_str_port_to_transport_addr("::", port_, protocol, &server->addr); + } else { + nr_str_port_to_transport_addr("0.0.0.0", port_, protocol, &server->addr); + } + PL_strncpyz(server->addr.fqdn, host_.c_str(), sizeof(server->addr.fqdn)); + if (transport_ == kNrIceTransportTls) { + server->addr.tls = 1; + } + } + + nr_transport_addr_fmt_addr_string(&server->addr); + + return NS_OK; +} + +nsresult NrIceTurnServer::ToNicerTurnStruct(nr_ice_turn_server* server) const { + memset(server, 0, sizeof(nr_ice_turn_server)); + + nsresult rv = ToNicerStunStruct(&server->turn_server); + if (NS_FAILED(rv)) return rv; + + if (!(server->username = r_strdup(username_.c_str()))) + return NS_ERROR_OUT_OF_MEMORY; + + // TODO(ekr@rtfm.com): handle non-ASCII passwords somehow? + // STUN requires they be SASLpreped, but we don't know if + // they are at this point. + + // C++03 23.2.4, Paragraph 1 stipulates that the elements + // in std::vector must be contiguous, and can therefore be + // used as input to functions expecting C arrays. + const UCHAR* data = password_.empty() ? nullptr : &password_[0]; + int r = r_data_create(&server->password, data, password_.size()); + if (r) { + RFREE(server->username); + return NS_ERROR_OUT_OF_MEMORY; + } + + return NS_OK; +} + +NrIceCtx::NrIceCtx(const std::string& name) + : connection_state_(ICE_CTX_INIT), + gathering_state_(ICE_CTX_GATHER_INIT), + name_(name), + ice_controlling_set_(false), + streams_(), + ctx_(nullptr), + peer_(nullptr), + ice_handler_vtbl_(nullptr), + ice_handler_(nullptr), + trickle_(true), + config_(), + nat_(nullptr), + proxy_config_(nullptr) {} + +/* static */ +RefPtr<NrIceCtx> NrIceCtx::Create(const std::string& aName) { + RefPtr<NrIceCtx> ctx = new NrIceCtx(aName); + + if (!ctx->Initialize()) { + return nullptr; + } + + return ctx; +} + +nsresult NrIceCtx::SetIceConfig(const Config& aConfig) { + config_ = aConfig; + switch (config_.mPolicy) { + case ICE_POLICY_RELAY: + MOZ_MTLOG(ML_DEBUG, "SetIceConfig: relay only"); + nr_ice_ctx_remove_flags(ctx_, NR_ICE_CTX_FLAGS_DISABLE_HOST_CANDIDATES); + nr_ice_ctx_add_flags(ctx_, NR_ICE_CTX_FLAGS_RELAY_ONLY); + break; + case ICE_POLICY_NO_HOST: + MOZ_MTLOG(ML_DEBUG, "SetIceConfig: no host"); + nr_ice_ctx_add_flags(ctx_, NR_ICE_CTX_FLAGS_DISABLE_HOST_CANDIDATES); + nr_ice_ctx_remove_flags(ctx_, NR_ICE_CTX_FLAGS_RELAY_ONLY); + break; + case ICE_POLICY_ALL: + MOZ_MTLOG(ML_DEBUG, "SetIceConfig: all"); + nr_ice_ctx_remove_flags(ctx_, NR_ICE_CTX_FLAGS_DISABLE_HOST_CANDIDATES); + nr_ice_ctx_remove_flags(ctx_, NR_ICE_CTX_FLAGS_RELAY_ONLY); + break; + } + + // TODO: Support re-configuring the test NAT someday? + if (!nat_ && config_.mNatSimulatorConfig.isSome()) { + TestNat* test_nat = new TestNat; + test_nat->filtering_type_ = TestNat::ToNatBehavior( + config_.mNatSimulatorConfig->mFilteringType.get()); + test_nat->mapping_type_ = + TestNat::ToNatBehavior(config_.mNatSimulatorConfig->mMappingType.get()); + test_nat->block_udp_ = config_.mNatSimulatorConfig->mBlockUdp; + test_nat->block_tcp_ = config_.mNatSimulatorConfig->mBlockTcp; + test_nat->block_tls_ = config_.mNatSimulatorConfig->mBlockTls; + test_nat->error_code_for_drop_ = + config_.mNatSimulatorConfig->mErrorCodeForDrop; + if (config_.mNatSimulatorConfig->mRedirectAddress.Length()) { + test_nat + ->stun_redirect_map_[config_.mNatSimulatorConfig->mRedirectAddress] = + config_.mNatSimulatorConfig->mRedirectTargets; + } + test_nat->enabled_ = true; + SetNat(test_nat); + } + + return NS_OK; +} + +RefPtr<NrIceMediaStream> NrIceCtx::CreateStream(const std::string& id, + const std::string& name, + int components) { + if (streams_.count(id)) { + MOZ_ASSERT(false); + return nullptr; + } + + RefPtr<NrIceMediaStream> stream = + new NrIceMediaStream(this, id, name, components); + streams_[id] = stream; + return stream; +} + +void NrIceCtx::DestroyStream(const std::string& id) { + auto it = streams_.find(id); + if (it != streams_.end()) { + auto preexisting_stream = it->second; + streams_.erase(it); + preexisting_stream->Close(); + } + + if (streams_.empty()) { + SetGatheringState(ICE_CTX_GATHER_INIT); + } +} + +// Handler callbacks +int NrIceCtx::select_pair(void* obj, nr_ice_media_stream* stream, + int component_id, nr_ice_cand_pair** potentials, + int potential_ct) { + MOZ_MTLOG(ML_DEBUG, "select pair called: potential_ct = " << potential_ct); + MOZ_ASSERT(stream->local_stream); + MOZ_ASSERT(!stream->local_stream->obsolete); + + return 0; +} + +int NrIceCtx::stream_ready(void* obj, nr_ice_media_stream* stream) { + MOZ_MTLOG(ML_DEBUG, "stream_ready called"); + MOZ_ASSERT(!stream->local_stream); + MOZ_ASSERT(!stream->obsolete); + + // Get the ICE ctx. + NrIceCtx* ctx = static_cast<NrIceCtx*>(obj); + + RefPtr<NrIceMediaStream> s = ctx->FindStream(stream); + + // Streams which do not exist should never be ready. + MOZ_ASSERT(s); + + s->Ready(); + + return 0; +} + +int NrIceCtx::stream_failed(void* obj, nr_ice_media_stream* stream) { + MOZ_MTLOG(ML_DEBUG, "stream_failed called"); + MOZ_ASSERT(!stream->local_stream); + MOZ_ASSERT(!stream->obsolete); + + // Get the ICE ctx + NrIceCtx* ctx = static_cast<NrIceCtx*>(obj); + RefPtr<NrIceMediaStream> s = ctx->FindStream(stream); + + // Streams which do not exist should never fail. + MOZ_ASSERT(s); + + ctx->SetConnectionState(ICE_CTX_FAILED); + s->Failed(); + return 0; +} + +int NrIceCtx::ice_checking(void* obj, nr_ice_peer_ctx* pctx) { + MOZ_MTLOG(ML_DEBUG, "ice_checking called"); + + // Get the ICE ctx + NrIceCtx* ctx = static_cast<NrIceCtx*>(obj); + + ctx->SetConnectionState(ICE_CTX_CHECKING); + + return 0; +} + +int NrIceCtx::ice_connected(void* obj, nr_ice_peer_ctx* pctx) { + MOZ_MTLOG(ML_DEBUG, "ice_connected called"); + + // Get the ICE ctx + NrIceCtx* ctx = static_cast<NrIceCtx*>(obj); + + // This is called even on failed contexts. + if (ctx->connection_state() != ICE_CTX_FAILED) { + ctx->SetConnectionState(ICE_CTX_CONNECTED); + } + + return 0; +} + +int NrIceCtx::ice_disconnected(void* obj, nr_ice_peer_ctx* pctx) { + MOZ_MTLOG(ML_DEBUG, "ice_disconnected called"); + + // Get the ICE ctx + NrIceCtx* ctx = static_cast<NrIceCtx*>(obj); + + ctx->SetConnectionState(ICE_CTX_DISCONNECTED); + + return 0; +} + +int NrIceCtx::msg_recvd(void* obj, nr_ice_peer_ctx* pctx, + nr_ice_media_stream* stream, int component_id, + UCHAR* msg, int len) { + // Get the ICE ctx + NrIceCtx* ctx = static_cast<NrIceCtx*>(obj); + RefPtr<NrIceMediaStream> s = ctx->FindStream(stream); + + // Streams which do not exist should never have packets. + MOZ_ASSERT(s); + + s->SignalPacketReceived(s, component_id, msg, len); + + return 0; +} + +void NrIceCtx::trickle_cb(void* arg, nr_ice_ctx* ice_ctx, + nr_ice_media_stream* stream, int component_id, + nr_ice_candidate* candidate) { + if (stream->obsolete) { + // Stream was probably just marked obsolete, resulting in this callback + return; + } + // Get the ICE ctx + NrIceCtx* ctx = static_cast<NrIceCtx*>(arg); + RefPtr<NrIceMediaStream> s = ctx->FindStream(stream); + + if (!s) { + // This stream has been removed because it is inactive + return; + } + + if (!candidate) { + s->SignalCandidate(s, "", stream->ufrag, "", ""); + return; + } + + std::string actual_addr; + std::string mdns_addr; + ctx->GenerateObfuscatedAddress(candidate, &mdns_addr, &actual_addr); + + // Format the candidate. + char candidate_str[NR_ICE_MAX_ATTRIBUTE_SIZE]; + int r = nr_ice_format_candidate_attribute( + candidate, candidate_str, sizeof(candidate_str), + (ctx->ctx()->flags & NR_ICE_CTX_FLAGS_OBFUSCATE_HOST_ADDRESSES) ? 1 : 0); + MOZ_ASSERT(!r); + if (r) return; + + MOZ_MTLOG(ML_INFO, "NrIceCtx(" << ctx->name_ << "): trickling candidate " + << candidate_str); + + s->SignalCandidate(s, candidate_str, stream->ufrag, mdns_addr, actual_addr); +} + +void NrIceCtx::InitializeGlobals(const GlobalConfig& aConfig) { + RLogConnector::CreateInstance(); + // Initialize the crypto callbacks and logging stuff + if (!initialized) { + NR_reg_init(NR_REG_MODE_LOCAL); + nr_crypto_vtbl = &nr_ice_crypto_nss_vtbl; + initialized = true; + + // Set the priorites for candidate type preferences. + // These numbers come from RFC 5245 S. 4.1.2.2 + NR_reg_set_uchar((char*)NR_ICE_REG_PREF_TYPE_SRV_RFLX, 100); + NR_reg_set_uchar((char*)NR_ICE_REG_PREF_TYPE_PEER_RFLX, 110); + NR_reg_set_uchar((char*)NR_ICE_REG_PREF_TYPE_HOST, 126); + NR_reg_set_uchar((char*)NR_ICE_REG_PREF_TYPE_RELAYED, 5); + NR_reg_set_uchar((char*)NR_ICE_REG_PREF_TYPE_SRV_RFLX_TCP, 99); + NR_reg_set_uchar((char*)NR_ICE_REG_PREF_TYPE_PEER_RFLX_TCP, 109); + NR_reg_set_uchar((char*)NR_ICE_REG_PREF_TYPE_HOST_TCP, 125); + NR_reg_set_uchar((char*)NR_ICE_REG_PREF_TYPE_RELAYED_TCP, 0); + NR_reg_set_uint4((char*)"stun.client.maximum_transmits", + aConfig.mStunClientMaxTransmits); + NR_reg_set_uint4((char*)NR_ICE_REG_TRICKLE_GRACE_PERIOD, + aConfig.mTrickleIceGracePeriod); + NR_reg_set_int4((char*)NR_ICE_REG_ICE_TCP_SO_SOCK_COUNT, + aConfig.mIceTcpSoSockCount); + NR_reg_set_int4((char*)NR_ICE_REG_ICE_TCP_LISTEN_BACKLOG, + aConfig.mIceTcpListenBacklog); + + NR_reg_set_char((char*)NR_ICE_REG_ICE_TCP_DISABLE, !aConfig.mTcpEnabled); + + if (aConfig.mAllowLoopback) { + NR_reg_set_char((char*)NR_STUN_REG_PREF_ALLOW_LOOPBACK_ADDRS, 1); + } + + if (aConfig.mAllowLinkLocal) { + NR_reg_set_char((char*)NR_STUN_REG_PREF_ALLOW_LINK_LOCAL_ADDRS, 1); + } + if (!aConfig.mForceNetInterface.Length()) { + NR_reg_set_string((char*)NR_ICE_REG_PREF_FORCE_INTERFACE_NAME, + const_cast<char*>(aConfig.mForceNetInterface.get())); + } + + // For now, always use nr_resolver for UDP. + NR_reg_set_char((char*)NR_ICE_REG_USE_NR_RESOLVER_FOR_UDP, 1); + + // Use nr_resolver for TCP only when not in e10s mode (for unit-tests) + if (XRE_IsParentProcess()) { + NR_reg_set_char((char*)NR_ICE_REG_USE_NR_RESOLVER_FOR_TCP, 1); + } + } +} + +void NrIceCtx::SetTargetForDefaultLocalAddressLookup( + const std::string& target_ip, uint16_t target_port) { + nr_ice_set_target_for_default_local_address_lookup(ctx_, target_ip.c_str(), + target_port); +} + +#define MAXADDRS 100 // mirrors setting in ice_ctx.c + +/* static */ +nsTArray<NrIceStunAddr> NrIceCtx::GetStunAddrs() { + nsTArray<NrIceStunAddr> addrs; + + nr_local_addr local_addrs[MAXADDRS]; + int addr_ct = 0; + + // most likely running on parent process and need crypto vtbl + // initialized on Windows (Linux and OSX don't seem to care) + if (!initialized) { + nr_crypto_vtbl = &nr_ice_crypto_nss_vtbl; + } + + MOZ_MTLOG(ML_INFO, "NrIceCtx static call to find local stun addresses"); + if (nr_stun_find_local_addresses(local_addrs, MAXADDRS, &addr_ct)) { + MOZ_MTLOG(ML_INFO, "Error finding local stun addresses"); + } else { + for (int i = 0; i < addr_ct; ++i) { + NrIceStunAddr addr(&local_addrs[i]); + addrs.AppendElement(addr); + } + } + + return addrs; +} + +void NrIceCtx::SetStunAddrs(const nsTArray<NrIceStunAddr>& addrs) { + nr_local_addr* local_addrs; + local_addrs = new nr_local_addr[addrs.Length()]; + + for (size_t i = 0; i < addrs.Length(); ++i) { + nr_local_addr_copy(&local_addrs[i], + const_cast<nr_local_addr*>(&addrs[i].localAddr())); + } + nr_ice_set_local_addresses(ctx_, local_addrs, addrs.Length()); + + delete[] local_addrs; +} + +bool NrIceCtx::Initialize() { + // Create the ICE context + int r; + + UINT4 flags = NR_ICE_CTX_FLAGS_AGGRESSIVE_NOMINATION; + r = nr_ice_ctx_create(const_cast<char*>(name_.c_str()), flags, &ctx_); + + if (r) { + MOZ_MTLOG(ML_ERROR, "Couldn't create ICE ctx for '" << name_ << "'"); + return false; + } + + // override default factory to capture optional proxy config when creating + // sockets. + nr_socket_factory* factory; + r = nr_socket_factory_create_int(this, &ctx_socket_factory_vtbl, &factory); + + if (r) { + MOZ_MTLOG(LogLevel::Error, "Couldn't create ctx socket factory."); + return false; + } + nr_ice_ctx_set_socket_factory(ctx_, factory); + + nr_interface_prioritizer* prioritizer = CreateInterfacePrioritizer(); + if (!prioritizer) { + MOZ_MTLOG(LogLevel::Error, "Couldn't create interface prioritizer."); + return false; + } + + r = nr_ice_ctx_set_interface_prioritizer(ctx_, prioritizer); + if (r) { + MOZ_MTLOG(LogLevel::Error, "Couldn't set interface prioritizer."); + return false; + } + + if (generating_trickle()) { + r = nr_ice_ctx_set_trickle_cb(ctx_, &NrIceCtx::trickle_cb, this); + if (r) { + MOZ_MTLOG(ML_ERROR, "Couldn't set trickle cb for '" << name_ << "'"); + return false; + } + } + + // Create the handler objects + ice_handler_vtbl_ = new nr_ice_handler_vtbl(); + ice_handler_vtbl_->select_pair = &NrIceCtx::select_pair; + ice_handler_vtbl_->stream_ready = &NrIceCtx::stream_ready; + ice_handler_vtbl_->stream_failed = &NrIceCtx::stream_failed; + ice_handler_vtbl_->ice_connected = &NrIceCtx::ice_connected; + ice_handler_vtbl_->msg_recvd = &NrIceCtx::msg_recvd; + ice_handler_vtbl_->ice_checking = &NrIceCtx::ice_checking; + ice_handler_vtbl_->ice_disconnected = &NrIceCtx::ice_disconnected; + + ice_handler_ = new nr_ice_handler(); + ice_handler_->vtbl = ice_handler_vtbl_; + ice_handler_->obj = this; + + // Create the peer ctx. Because we do not support parallel forking, we + // only have one peer ctx. + std::string peer_name = name_ + ":default"; + r = nr_ice_peer_ctx_create(ctx_, ice_handler_, + const_cast<char*>(peer_name.c_str()), &peer_); + if (r) { + MOZ_MTLOG(ML_ERROR, "Couldn't create ICE peer ctx for '" << name_ << "'"); + return false; + } + + nsresult rv; + sts_target_ = do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &rv); + + if (!NS_SUCCEEDED(rv)) return false; + + return true; +} + +int NrIceCtx::SetNat(const RefPtr<TestNat>& aNat) { + nat_ = aNat; + nr_socket_factory* fac; + int r = nat_->create_socket_factory(&fac); + if (r) { + return r; + } + nr_ice_ctx_set_socket_factory(ctx_, fac); + return 0; +} + +// ONLY USE THIS FOR TESTING. Will cause totally unpredictable and possibly very +// bad effects if ICE is still live. +void NrIceCtx::internal_DeinitializeGlobal() { + NR_reg_del((char*)"stun"); + NR_reg_del((char*)"ice"); + RLogConnector::DestroyInstance(); + nr_crypto_vtbl = nullptr; + initialized = false; +} + +void NrIceCtx::internal_SetTimerAccelarator(int divider) { + ctx_->test_timer_divider = divider; +} + +void NrIceCtx::AccumulateStats(const NrIceStats& stats) { + nr_accumulate_count(&(ctx_->stats.stun_retransmits), stats.stun_retransmits); + nr_accumulate_count(&(ctx_->stats.turn_401s), stats.turn_401s); + nr_accumulate_count(&(ctx_->stats.turn_403s), stats.turn_403s); + nr_accumulate_count(&(ctx_->stats.turn_438s), stats.turn_438s); +} + +NrIceStats NrIceCtx::Destroy() { + // designed to be called more than once so if stats are desired, this can be + // called just prior to the destructor + MOZ_MTLOG(ML_NOTICE, "NrIceCtx(" << name_ << "): " << __func__); + + for (auto& idAndStream : streams_) { + idAndStream.second->Close(); + } + + NrIceStats stats; + if (ctx_) { + stats.stun_retransmits = ctx_->stats.stun_retransmits; + stats.turn_401s = ctx_->stats.turn_401s; + stats.turn_403s = ctx_->stats.turn_403s; + stats.turn_438s = ctx_->stats.turn_438s; + } + + if (peer_) { + nr_ice_peer_ctx_destroy(&peer_); + } + if (ctx_) { + nr_ice_ctx_destroy(&ctx_); + } + + delete ice_handler_vtbl_; + delete ice_handler_; + + ice_handler_vtbl_ = nullptr; + ice_handler_ = nullptr; + proxy_config_ = nullptr; + streams_.clear(); + + return stats; +} + +NrIceCtx::~NrIceCtx() = default; + +void NrIceCtx::destroy_peer_ctx() { nr_ice_peer_ctx_destroy(&peer_); } + +nsresult NrIceCtx::SetControlling(Controlling controlling) { + if (!ice_controlling_set_) { + peer_->controlling = (controlling == ICE_CONTROLLING) ? 1 : 0; + ice_controlling_set_ = true; + + MOZ_MTLOG(ML_DEBUG, + "ICE ctx " << name_ << " setting controlling to" << controlling); + } + return NS_OK; +} + +NrIceCtx::Controlling NrIceCtx::GetControlling() { + return (peer_->controlling) ? ICE_CONTROLLING : ICE_CONTROLLED; +} + +nsresult NrIceCtx::SetStunServers( + const std::vector<NrIceStunServer>& stun_servers) { + MOZ_MTLOG(ML_NOTICE, "NrIceCtx(" << name_ << "): " << __func__); + // We assume nr_ice_stun_server is memmoveable. That's true right now. + std::vector<nr_ice_stun_server> servers; + + for (size_t i = 0; i < stun_servers.size(); ++i) { + nr_ice_stun_server server; + nsresult rv = stun_servers[i].ToNicerStunStruct(&server); + if (NS_WARN_IF(NS_FAILED(rv))) { + MOZ_MTLOG(ML_ERROR, "Couldn't convert STUN server for '" << name_ << "'"); + } else { + servers.push_back(server); + } + } + + int r = nr_ice_ctx_set_stun_servers(ctx_, servers.data(), + static_cast<int>(servers.size())); + if (r) { + MOZ_MTLOG(ML_ERROR, "Couldn't set STUN servers for '" << name_ << "'"); + return NS_ERROR_FAILURE; + } + + return NS_OK; +} + +// TODO(ekr@rtfm.com): This is just SetStunServers with s/Stun/Turn +// Could we do a template or something? +nsresult NrIceCtx::SetTurnServers( + const std::vector<NrIceTurnServer>& turn_servers) { + MOZ_MTLOG(ML_NOTICE, "NrIceCtx(" << name_ << "): " << __func__); + // We assume nr_ice_turn_server is memmoveable. That's true right now. + std::vector<nr_ice_turn_server> servers; + + for (size_t i = 0; i < turn_servers.size(); ++i) { + nr_ice_turn_server server; + nsresult rv = turn_servers[i].ToNicerTurnStruct(&server); + if (NS_WARN_IF(NS_FAILED(rv))) { + MOZ_MTLOG(ML_ERROR, "Couldn't convert TURN server for '" << name_ << "'"); + } else { + servers.push_back(server); + } + } + + int r = nr_ice_ctx_set_turn_servers(ctx_, servers.data(), + static_cast<int>(servers.size())); + if (r) { + MOZ_MTLOG(ML_ERROR, "Couldn't set TURN servers for '" << name_ << "'"); + // TODO(ekr@rtfm.com): This leaks the username/password. Need to free that. + return NS_ERROR_FAILURE; + } + + return NS_OK; +} + +nsresult NrIceCtx::SetResolver(nr_resolver* resolver) { + int r = nr_ice_ctx_set_resolver(ctx_, resolver); + + if (r) { + MOZ_MTLOG(ML_ERROR, "Couldn't set resolver for '" << name_ << "'"); + return NS_ERROR_FAILURE; + } + + return NS_OK; +} + +nsresult NrIceCtx::SetProxyConfig(NrSocketProxyConfig&& config) { + proxy_config_.reset(new NrSocketProxyConfig(std::move(config))); + if (nat_) { + nat_->set_proxy_config(proxy_config_); + } + + if (proxy_config_->GetForceProxy()) { + nr_ice_ctx_add_flags(ctx_, NR_ICE_CTX_FLAGS_ONLY_PROXY); + } else { + nr_ice_ctx_remove_flags(ctx_, NR_ICE_CTX_FLAGS_ONLY_PROXY); + } + + return NS_OK; +} + +void NrIceCtx::SetCtxFlags(bool default_route_only) { + ASSERT_ON_THREAD(sts_target_); + + if (default_route_only) { + nr_ice_ctx_add_flags(ctx_, NR_ICE_CTX_FLAGS_ONLY_DEFAULT_ADDRS); + } else { + nr_ice_ctx_remove_flags(ctx_, NR_ICE_CTX_FLAGS_ONLY_DEFAULT_ADDRS); + } +} + +nsresult NrIceCtx::StartGathering(bool default_route_only, + bool obfuscate_host_addresses) { + ASSERT_ON_THREAD(sts_target_); + MOZ_MTLOG(ML_NOTICE, "NrIceCtx(" << name_ << "): " << __func__); + + if (obfuscate_host_addresses) { + nr_ice_ctx_add_flags(ctx_, NR_ICE_CTX_FLAGS_OBFUSCATE_HOST_ADDRESSES); + } + + SetCtxFlags(default_route_only); + + // This might start gathering for the first time, or again after + // renegotiation, or might do nothing at all if gathering has already + // finished. + int r = nr_ice_gather(ctx_, &NrIceCtx::gather_cb, this); + + if (!r) { + SetGatheringState(ICE_CTX_GATHER_COMPLETE); + } else if (r == R_WOULDBLOCK) { + SetGatheringState(ICE_CTX_GATHER_STARTED); + } else { + SetGatheringState(ICE_CTX_GATHER_COMPLETE); + MOZ_MTLOG(ML_ERROR, "ICE FAILED: Couldn't gather ICE candidates for '" + << name_ << "', error=" << r); + SetConnectionState(ICE_CTX_FAILED); + return NS_ERROR_FAILURE; + } + + return NS_OK; +} + +RefPtr<NrIceMediaStream> NrIceCtx::FindStream(nr_ice_media_stream* stream) { + for (auto& idAndStream : streams_) { + if (idAndStream.second->HasStream(stream)) { + return idAndStream.second; + } + } + + return nullptr; +} + +std::vector<std::string> NrIceCtx::GetGlobalAttributes() { + char** attrs = nullptr; + int attrct; + int r; + std::vector<std::string> ret; + + r = nr_ice_get_global_attributes(ctx_, &attrs, &attrct); + if (r) { + MOZ_MTLOG(ML_ERROR, + "Couldn't get ufrag and password for '" << name_ << "'"); + return ret; + } + + for (int i = 0; i < attrct; i++) { + ret.push_back(std::string(attrs[i])); + RFREE(attrs[i]); + } + RFREE(attrs); + + return ret; +} + +nsresult NrIceCtx::ParseGlobalAttributes(std::vector<std::string> attrs) { + std::vector<char*> attrs_in; + attrs_in.reserve(attrs.size()); + for (auto& attr : attrs) { + attrs_in.push_back(const_cast<char*>(attr.c_str())); + } + + int r = nr_ice_peer_ctx_parse_global_attributes( + peer_, attrs_in.empty() ? nullptr : &attrs_in[0], attrs_in.size()); + if (r) { + MOZ_MTLOG(ML_ERROR, + "Couldn't parse global attributes for " << name_ << "'"); + return NS_ERROR_FAILURE; + } + + return NS_OK; +} + +bool NrIceCtx::HasStreamsToConnect() const { + for (auto& idAndStream : streams_) { + if (idAndStream.second->state() != NrIceMediaStream::ICE_CLOSED) { + return true; + } + } + return false; +} + +nsresult NrIceCtx::StartChecks() { + int r; + MOZ_MTLOG(ML_NOTICE, "NrIceCtx(" << name_ << "): " << __func__); + + if (!HasStreamsToConnect()) { + MOZ_MTLOG(ML_NOTICE, "In StartChecks, nothing to do on " << name_); + return NS_OK; + } + + r = nr_ice_peer_ctx_pair_candidates(peer_); + if (r) { + MOZ_MTLOG(ML_ERROR, "ICE FAILED: Couldn't pair candidates on " << name_); + SetConnectionState(ICE_CTX_FAILED); + return NS_ERROR_FAILURE; + } + + r = nr_ice_peer_ctx_start_checks2(peer_, 1); + if (r) { + if (r == R_NOT_FOUND) { + MOZ_MTLOG(ML_INFO, "Couldn't start peer checks on " + << name_ << ", assuming trickle ICE"); + } else { + MOZ_MTLOG(ML_ERROR, + "ICE FAILED: Couldn't start peer checks on " << name_); + SetConnectionState(ICE_CTX_FAILED); + return NS_ERROR_FAILURE; + } + } + + return NS_OK; +} + +void NrIceCtx::gather_cb(NR_SOCKET s, int h, void* arg) { + NrIceCtx* ctx = static_cast<NrIceCtx*>(arg); + + ctx->SetGatheringState(ICE_CTX_GATHER_COMPLETE); +} + +void NrIceCtx::UpdateNetworkState(bool online) { + MOZ_MTLOG(ML_NOTICE, "NrIceCtx(" << name_ << "): updating network state to " + << (online ? "online" : "offline")); + if (connection_state_ == ICE_CTX_CLOSED) { + return; + } + + if (online) { + nr_ice_peer_ctx_refresh_consent_all_streams(peer_); + } else { + nr_ice_peer_ctx_disconnect_all_streams(peer_); + } +} + +void NrIceCtx::SetConnectionState(ConnectionState state) { + if (state == connection_state_) return; + + MOZ_MTLOG(ML_INFO, "NrIceCtx(" << name_ << "): state " << connection_state_ + << "->" << state); + connection_state_ = state; + + if (connection_state_ == ICE_CTX_FAILED) { + MOZ_MTLOG(ML_INFO, + "NrIceCtx(" << name_ << "): dumping r_log ringbuffer... "); + std::deque<std::string> logs; + RLogConnector::GetInstance()->GetAny(0, &logs); + for (auto& log : logs) { + MOZ_MTLOG(ML_INFO, log); + } + } + + SignalConnectionStateChange(this, state); +} + +void NrIceCtx::SetGatheringState(GatheringState state) { + if (state == gathering_state_) return; + + MOZ_MTLOG(ML_DEBUG, "NrIceCtx(" << name_ << "): gathering state " + << gathering_state_ << "->" << state); + gathering_state_ = state; + + SignalGatheringStateChange(this, state); +} + +void NrIceCtx::GenerateObfuscatedAddress(nr_ice_candidate* candidate, + std::string* mdns_address, + std::string* actual_address) { + if (candidate->type == HOST && + (ctx_->flags & NR_ICE_CTX_FLAGS_OBFUSCATE_HOST_ADDRESSES)) { + char addr[64]; + if (nr_transport_addr_get_addrstring(&candidate->addr, addr, + sizeof(addr))) { + return; + } + + *actual_address = addr; + + const auto& iter = obfuscated_host_addresses_.find(*actual_address); + if (iter != obfuscated_host_addresses_.end()) { + *mdns_address = iter->second; + } else { + nsresult rv; + nsCOMPtr<nsIUUIDGenerator> uuidgen = + do_GetService("@mozilla.org/uuid-generator;1", &rv); + // If this fails, we'll return a zero UUID rather than something + // unexpected. + nsID id = {}; + id.Clear(); + if (NS_SUCCEEDED(rv)) { + rv = uuidgen->GenerateUUIDInPlace(&id); + if (NS_FAILED(rv)) { + id.Clear(); + } + } + + char chars[NSID_LENGTH]; + id.ToProvidedString(chars); + // The string will look like {64888863-a253-424a-9b30-1ed285d20142}, + // we want to trim off the braces. + const char* ptr_to_id = chars; + ++ptr_to_id; + chars[NSID_LENGTH - 2] = 0; + + std::ostringstream o; + o << ptr_to_id << ".local"; + *mdns_address = o.str(); + + obfuscated_host_addresses_[*actual_address] = *mdns_address; + } + candidate->mdns_addr = r_strdup(mdns_address->c_str()); + } +} + +} // namespace mozilla + +// Reimplement nr_ice_compute_codeword to avoid copyright issues +void nr_ice_compute_codeword(char* buf, int len, char* codeword) { + UINT4 c; + + r_crc32(buf, len, &c); + + PL_Base64Encode(reinterpret_cast<char*>(&c), 3, codeword); + codeword[4] = 0; +} + +int nr_socket_local_create(void* obj, nr_transport_addr* addr, + nr_socket** sockp) { + using namespace mozilla; + + RefPtr<NrSocketBase> sock; + int r, _status; + shared_ptr<NrSocketProxyConfig> config = nullptr; + + if (obj) { + config = static_cast<NrIceCtx*>(obj)->GetProxyConfig(); + } + + r = NrSocketBase::CreateSocket(addr, &sock, config); + if (r) { + ABORT(r); + } + + r = nr_socket_create_int(static_cast<void*>(sock), sock->vtbl(), sockp); + if (r) ABORT(r); + + _status = 0; + + { + // We will release this reference in destroy(), not exactly the normal + // ownership model, but it is what it is. + NrSocketBase* dummy = sock.forget().take(); + (void)dummy; + } + +abort: + return _status; +} |