/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set sw=2 ts=8 et 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/. */ #include "nsDNSService2.h" #include "nsIDNSRecord.h" #include "nsIDNSListener.h" #include "nsIDNSByTypeRecord.h" #include "nsICancelable.h" #include "nsIPrefBranch.h" #include "nsIOService.h" #include "nsIXPConnect.h" #include "nsProxyRelease.h" #include "nsReadableUtils.h" #include "nsString.h" #include "nsCRT.h" #include "nsNetCID.h" #include "nsError.h" #include "nsDNSPrefetch.h" #include "nsThreadUtils.h" #include "nsIProtocolProxyService.h" #include "nsIObliviousHttp.h" #include "prsystem.h" #include "prnetdb.h" #include "prmon.h" #include "prio.h" #include "nsCharSeparatedTokenizer.h" #include "nsNetAddr.h" #include "nsProxyRelease.h" #include "nsQueryObject.h" #include "nsIObserverService.h" #include "nsINetworkLinkService.h" #include "DNSAdditionalInfo.h" #include "TRRService.h" #include "mozilla/Attributes.h" #include "mozilla/ClearOnShutdown.h" #include "mozilla/net/NeckoCommon.h" #include "mozilla/net/ChildDNSService.h" #include "mozilla/net/DNSListenerProxy.h" #include "mozilla/Services.h" #include "mozilla/StaticPrefs_network.h" #include "mozilla/StaticPtr.h" #include "mozilla/SyncRunnable.h" #include "mozilla/TextUtils.h" #include "mozilla/Utf8.h" using namespace mozilla; using namespace mozilla::net; static const char kPrefDnsCacheEntries[] = "network.dnsCacheEntries"; static const char kPrefDnsCacheExpiration[] = "network.dnsCacheExpiration"; static const char kPrefDnsCacheGrace[] = "network.dnsCacheExpirationGracePeriod"; static const char kPrefIPv4OnlyDomains[] = "network.dns.ipv4OnlyDomains"; static const char kPrefBlockDotOnion[] = "network.dns.blockDotOnion"; static const char kPrefDnsLocalDomains[] = "network.dns.localDomains"; static const char kPrefDnsForceResolve[] = "network.dns.forceResolve"; static const char kPrefDnsOfflineLocalhost[] = "network.dns.offline-localhost"; static const char kPrefDnsNotifyResolution[] = "network.dns.notifyResolution"; //----------------------------------------------------------------------------- class nsDNSRecord : public nsIDNSAddrRecord { public: NS_DECL_THREADSAFE_ISUPPORTS NS_DECL_NSIDNSRECORD NS_DECL_NSIDNSADDRRECORD explicit nsDNSRecord(nsHostRecord* hostRecord) { mHostRecord = do_QueryObject(hostRecord); } private: virtual ~nsDNSRecord() = default; RefPtr mHostRecord; // Since mIter is holding a weak reference to the NetAddr array we must // make sure it is not released. So we also keep a RefPtr to the AddrInfo // which is immutable. RefPtr mAddrInfo; nsTArray::const_iterator mIter; const NetAddr* iter() { if (!mIter.GetArray()) { return nullptr; } if (mIter.GetArray()->end() == mIter) { return nullptr; } return &*mIter; } int mIterGenCnt = -1; // the generation count of // mHostRecord->addr_info when we // start iterating bool mDone = false; }; NS_IMPL_ISUPPORTS(nsDNSRecord, nsIDNSRecord, nsIDNSAddrRecord) NS_IMETHODIMP nsDNSRecord::GetCanonicalName(nsACString& result) { // this method should only be called if we have a CNAME NS_ENSURE_TRUE(mHostRecord->flags & nsHostResolver::RES_CANON_NAME, NS_ERROR_NOT_AVAILABLE); MutexAutoLock lock(mHostRecord->addr_info_lock); // if the record is for an IP address literal, then the canonical // host name is the IP address literal. if (!mHostRecord->addr_info) { result = mHostRecord->host; return NS_OK; } if (mHostRecord->addr_info->CanonicalHostname().IsEmpty()) { result = mHostRecord->addr_info->Hostname(); } else { result = mHostRecord->addr_info->CanonicalHostname(); } return NS_OK; } NS_IMETHODIMP nsDNSRecord::IsTRR(bool* retval) { MutexAutoLock lock(mHostRecord->addr_info_lock); if (mHostRecord->addr_info) { *retval = mHostRecord->addr_info->IsTRR(); } else { *retval = false; } return NS_OK; } NS_IMETHODIMP nsDNSRecord::ResolvedInSocketProcess(bool* retval) { *retval = false; return NS_OK; } NS_IMETHODIMP nsDNSRecord::GetTrrFetchDuration(double* aTime) { MutexAutoLock lock(mHostRecord->addr_info_lock); if (mHostRecord->addr_info && mHostRecord->addr_info->IsTRR()) { *aTime = mHostRecord->addr_info->GetTrrFetchDuration(); } else { *aTime = 0; } return NS_OK; } NS_IMETHODIMP nsDNSRecord::GetTrrFetchDurationNetworkOnly(double* aTime) { MutexAutoLock lock(mHostRecord->addr_info_lock); if (mHostRecord->addr_info && mHostRecord->addr_info->IsTRR()) { *aTime = mHostRecord->addr_info->GetTrrFetchDurationNetworkOnly(); } else { *aTime = 0; } return NS_OK; } NS_IMETHODIMP nsDNSRecord::GetNextAddr(uint16_t port, NetAddr* addr) { if (mDone) { return NS_ERROR_NOT_AVAILABLE; } mHostRecord->addr_info_lock.Lock(); if (mHostRecord->addr_info) { if (mIterGenCnt != mHostRecord->addr_info_gencnt) { // mHostRecord->addr_info has changed, restart the iteration. mIter = nsTArray::const_iterator(); mIterGenCnt = mHostRecord->addr_info_gencnt; // Make sure to hold a RefPtr to the AddrInfo so we can iterate through // the NetAddr array. mAddrInfo = mHostRecord->addr_info; } bool startedFresh = !iter(); do { if (!iter()) { mIter = mAddrInfo->Addresses().begin(); } else { mIter++; } } while (iter() && mHostRecord->Blocklisted(iter())); if (!iter() && startedFresh) { // If everything was blocklisted we want to reset the blocklist (and // likely relearn it) and return the first address. That is better // than nothing. mHostRecord->ResetBlocklist(); mIter = mAddrInfo->Addresses().begin(); } if (iter()) { *addr = *mIter; } mHostRecord->addr_info_lock.Unlock(); if (!iter()) { mDone = true; mIter = nsTArray::const_iterator(); mAddrInfo = nullptr; mIterGenCnt = -1; return NS_ERROR_NOT_AVAILABLE; } } else { mHostRecord->addr_info_lock.Unlock(); if (!mHostRecord->addr) { // Both mHostRecord->addr_info and mHostRecord->addr are null. // This can happen if mHostRecord->addr_info expired and the // attempt to reresolve it failed. return NS_ERROR_NOT_AVAILABLE; } memcpy(addr, mHostRecord->addr.get(), sizeof(NetAddr)); mDone = true; } // set given port port = htons(port); if (addr->raw.family == AF_INET) { addr->inet.port = port; } else if (addr->raw.family == AF_INET6) { addr->inet6.port = port; } return NS_OK; } NS_IMETHODIMP nsDNSRecord::GetAddresses(nsTArray& aAddressArray) { if (mDone) { return NS_ERROR_NOT_AVAILABLE; } mHostRecord->addr_info_lock.Lock(); if (mHostRecord->addr_info) { for (const auto& address : mHostRecord->addr_info->Addresses()) { if (mHostRecord->Blocklisted(&address)) { continue; } NetAddr* addr = aAddressArray.AppendElement(address); if (addr->raw.family == AF_INET) { addr->inet.port = 0; } else if (addr->raw.family == AF_INET6) { addr->inet6.port = 0; } } mHostRecord->addr_info_lock.Unlock(); } else { mHostRecord->addr_info_lock.Unlock(); if (!mHostRecord->addr) { return NS_ERROR_NOT_AVAILABLE; } NetAddr* addr = aAddressArray.AppendElement(NetAddr()); memcpy(addr, mHostRecord->addr.get(), sizeof(NetAddr)); if (addr->raw.family == AF_INET) { addr->inet.port = 0; } else if (addr->raw.family == AF_INET6) { addr->inet6.port = 0; } } return NS_OK; } NS_IMETHODIMP nsDNSRecord::GetScriptableNextAddr(uint16_t port, nsINetAddr** result) { NetAddr addr; nsresult rv = GetNextAddr(port, &addr); if (NS_FAILED(rv)) { return rv; } RefPtr netaddr = new nsNetAddr(&addr); netaddr.forget(result); return NS_OK; } NS_IMETHODIMP nsDNSRecord::GetNextAddrAsString(nsACString& result) { NetAddr addr; nsresult rv = GetNextAddr(0, &addr); if (NS_FAILED(rv)) { return rv; } char buf[kIPv6CStrBufSize]; if (addr.ToStringBuffer(buf, sizeof(buf))) { result.Assign(buf); return NS_OK; } NS_ERROR("NetAddrToString failed unexpectedly"); return NS_ERROR_FAILURE; // conversion failed for some reason } NS_IMETHODIMP nsDNSRecord::HasMore(bool* result) { if (mDone) { *result = false; return NS_OK; } nsTArray::const_iterator iterCopy = mIter; int iterGenCntCopy = mIterGenCnt; NetAddr addr; *result = NS_SUCCEEDED(GetNextAddr(0, &addr)); mIter = iterCopy; mIterGenCnt = iterGenCntCopy; mDone = false; return NS_OK; } NS_IMETHODIMP nsDNSRecord::Rewind() { mIter = nsTArray::const_iterator(); mIterGenCnt = -1; mDone = false; return NS_OK; } NS_IMETHODIMP nsDNSRecord::ReportUnusable(uint16_t aPort) { // right now we don't use the port in the blocklist MutexAutoLock lock(mHostRecord->addr_info_lock); // Check that we are using a real addr_info (as opposed to a single // constant address), and that the generation count is valid. Otherwise, // ignore the report. if (mHostRecord->addr_info && mIterGenCnt == mHostRecord->addr_info_gencnt && iter()) { mHostRecord->ReportUnusable(iter()); } return NS_OK; } NS_IMETHODIMP nsDNSRecord::GetEffectiveTRRMode(nsIRequest::TRRMode* aMode) { *aMode = mHostRecord->EffectiveTRRMode(); return NS_OK; } NS_IMETHODIMP nsDNSRecord::GetTrrSkipReason( nsITRRSkipReason::value* aTrrSkipReason) { *aTrrSkipReason = mHostRecord->TrrSkipReason(); return NS_OK; } NS_IMETHODIMP nsDNSRecord::GetTtl(uint32_t* aTtl) { return mHostRecord->GetTtl(aTtl); } class nsDNSByTypeRecord : public nsIDNSByTypeRecord, public nsIDNSTXTRecord, public nsIDNSHTTPSSVCRecord { public: NS_DECL_THREADSAFE_ISUPPORTS NS_DECL_NSIDNSRECORD NS_DECL_NSIDNSBYTYPERECORD NS_DECL_NSIDNSTXTRECORD NS_DECL_NSIDNSHTTPSSVCRECORD explicit nsDNSByTypeRecord(nsHostRecord* hostRecord) { mHostRecord = do_QueryObject(hostRecord); } private: virtual ~nsDNSByTypeRecord() = default; RefPtr mHostRecord; }; NS_IMPL_ISUPPORTS(nsDNSByTypeRecord, nsIDNSRecord, nsIDNSByTypeRecord, nsIDNSTXTRecord, nsIDNSHTTPSSVCRecord) NS_IMETHODIMP nsDNSByTypeRecord::GetType(uint32_t* aType) { *aType = mHostRecord->GetType(); return NS_OK; } NS_IMETHODIMP nsDNSByTypeRecord::GetRecords(CopyableTArray& aRecords) { // deep copy return mHostRecord->GetRecords(aRecords); } NS_IMETHODIMP nsDNSByTypeRecord::GetRecordsAsOneString(nsACString& aRecords) { // deep copy return mHostRecord->GetRecordsAsOneString(aRecords); } NS_IMETHODIMP nsDNSByTypeRecord::GetRecords(nsTArray>& aRecords) { return mHostRecord->GetRecords(aRecords); } NS_IMETHODIMP nsDNSByTypeRecord::GetServiceModeRecord(bool aNoHttp2, bool aNoHttp3, nsISVCBRecord** aRecord) { return mHostRecord->GetServiceModeRecord(aNoHttp2, aNoHttp3, aRecord); } NS_IMETHODIMP nsDNSByTypeRecord::GetAllRecordsWithEchConfig( bool aNoHttp2, bool aNoHttp3, bool* aAllRecordsHaveEchConfig, bool* aAllRecordsInH3ExcludedList, nsTArray>& aResult) { return mHostRecord->GetAllRecordsWithEchConfig( aNoHttp2, aNoHttp3, aAllRecordsHaveEchConfig, aAllRecordsInH3ExcludedList, aResult); } NS_IMETHODIMP nsDNSByTypeRecord::GetHasIPAddresses(bool* aResult) { return mHostRecord->GetHasIPAddresses(aResult); } NS_IMETHODIMP nsDNSByTypeRecord::GetAllRecordsExcluded(bool* aResult) { return mHostRecord->GetAllRecordsExcluded(aResult); } NS_IMETHODIMP nsDNSByTypeRecord::GetResults(mozilla::net::TypeRecordResultType* aResults) { *aResults = mHostRecord->GetResults(); return NS_OK; } NS_IMETHODIMP nsDNSByTypeRecord::GetTtl(uint32_t* aTtl) { return mHostRecord->GetTtl(aTtl); } //----------------------------------------------------------------------------- class nsDNSAsyncRequest final : public nsResolveHostCallback, public nsICancelable { public: NS_DECL_THREADSAFE_ISUPPORTS NS_DECL_NSICANCELABLE nsDNSAsyncRequest(nsHostResolver* res, const nsACString& host, const nsACString& trrServer, uint16_t type, const OriginAttributes& attrs, nsIDNSListener* listener, nsIDNSService::DNSFlags flags, uint16_t af) : mResolver(res), mHost(host), mTrrServer(trrServer), mType(type), mOriginAttributes(attrs), mListener(listener), mFlags(flags), mAF(af) {} void OnResolveHostComplete(nsHostResolver*, nsHostRecord*, nsresult) override; // Returns TRUE if the DNS listener arg is the same as the member listener // Used in Cancellations to remove DNS requests associated with a // particular hostname and nsIDNSListener bool EqualsAsyncListener(nsIDNSListener* aListener) override; size_t SizeOfIncludingThis(mozilla::MallocSizeOf) const override; RefPtr mResolver; nsCString mHost; // hostname we're resolving nsCString mTrrServer; // A trr server to be used. uint16_t mType = 0; const OriginAttributes mOriginAttributes; // The originAttributes for this resolving nsCOMPtr mListener; nsIDNSService::DNSFlags mFlags = nsIDNSService::RESOLVE_DEFAULT_FLAGS; uint16_t mAF = 0; private: virtual ~nsDNSAsyncRequest() = default; }; NS_IMPL_ISUPPORTS(nsDNSAsyncRequest, nsICancelable) void nsDNSAsyncRequest::OnResolveHostComplete(nsHostResolver* resolver, nsHostRecord* hostRecord, nsresult status) { // need to have an owning ref when we issue the callback to enable // the caller to be able to addref/release multiple times without // destroying the record prematurely. nsCOMPtr rec; if (NS_SUCCEEDED(status) || mFlags & nsIDNSService::RESOLVE_WANT_RECORD_ON_ERROR) { MOZ_ASSERT(hostRecord, "no host record"); if (hostRecord->type != nsDNSService::RESOLVE_TYPE_DEFAULT) { rec = new nsDNSByTypeRecord(hostRecord); } else { rec = new nsDNSRecord(hostRecord); } } mListener->OnLookupComplete(this, rec, status); mListener = nullptr; } bool nsDNSAsyncRequest::EqualsAsyncListener(nsIDNSListener* aListener) { uintptr_t originalListenerAddr = reinterpret_cast(mListener.get()); RefPtr wrapper = do_QueryObject(mListener); if (wrapper) { originalListenerAddr = wrapper->GetOriginalListenerAddress(); } uintptr_t listenerAddr = reinterpret_cast(aListener); return (listenerAddr == originalListenerAddr); } size_t nsDNSAsyncRequest::SizeOfIncludingThis(MallocSizeOf mallocSizeOf) const { size_t n = mallocSizeOf(this); // The following fields aren't measured. // - mHost, because it's a non-owning pointer // - mResolver, because it's a non-owning pointer // - mListener, because it's a non-owning pointer return n; } NS_IMETHODIMP nsDNSAsyncRequest::Cancel(nsresult reason) { NS_ENSURE_ARG(NS_FAILED(reason)); MOZ_DIAGNOSTIC_ASSERT(mResolver, "mResolver should not be null"); mResolver->DetachCallback(mHost, mTrrServer, mType, mOriginAttributes, mFlags, mAF, this, reason); return NS_OK; } //----------------------------------------------------------------------------- class nsDNSSyncRequest : public nsResolveHostCallback { NS_DECL_THREADSAFE_ISUPPORTS public: explicit nsDNSSyncRequest(PRMonitor* mon) : mMonitor(mon) {} void OnResolveHostComplete(nsHostResolver*, nsHostRecord*, nsresult) override; bool EqualsAsyncListener(nsIDNSListener* aListener) override; size_t SizeOfIncludingThis(mozilla::MallocSizeOf) const override; bool mDone = false; nsresult mStatus = NS_OK; RefPtr mHostRecord; private: virtual ~nsDNSSyncRequest() = default; PRMonitor* mMonitor = nullptr; }; NS_IMPL_ISUPPORTS0(nsDNSSyncRequest) void nsDNSSyncRequest::OnResolveHostComplete(nsHostResolver* resolver, nsHostRecord* hostRecord, nsresult status) { // store results, and wake up nsDNSService::Resolve to process results. PR_EnterMonitor(mMonitor); mDone = true; mStatus = status; mHostRecord = hostRecord; PR_Notify(mMonitor); PR_ExitMonitor(mMonitor); } bool nsDNSSyncRequest::EqualsAsyncListener(nsIDNSListener* aListener) { // Sync request: no listener to compare return false; } size_t nsDNSSyncRequest::SizeOfIncludingThis(MallocSizeOf mallocSizeOf) const { size_t n = mallocSizeOf(this); // The following fields aren't measured. // - mHostRecord, because it's a non-owning pointer // Measurement of the following members may be added later if DMD finds it // is worthwhile: // - mMonitor return n; } class NotifyDNSResolution : public Runnable { public: explicit NotifyDNSResolution(const nsACString& aHostname) : mozilla::Runnable("NotifyDNSResolution"), mHostname(aHostname) {} NS_IMETHOD Run() override { MOZ_ASSERT(NS_IsMainThread()); nsCOMPtr obs = services::GetObserverService(); if (obs) { obs->NotifyObservers(nullptr, "dns-resolution-request", NS_ConvertUTF8toUTF16(mHostname).get()); } return NS_OK; } private: nsCString mHostname; }; //----------------------------------------------------------------------------- static StaticRefPtr gDNSServiceWrapper; NS_IMPL_ISUPPORTS(DNSServiceWrapper, nsIDNSService, nsPIDNSService) // static already_AddRefed DNSServiceWrapper::GetSingleton() { if (!gDNSServiceWrapper) { gDNSServiceWrapper = new DNSServiceWrapper(); gDNSServiceWrapper->mDNSServiceInUse = ChildDNSService::GetSingleton(); if (gDNSServiceWrapper->mDNSServiceInUse) { ClearOnShutdown(&gDNSServiceWrapper); nsDNSPrefetch::Initialize(gDNSServiceWrapper); } else { gDNSServiceWrapper = nullptr; } } return do_AddRef(gDNSServiceWrapper); } // static void DNSServiceWrapper::SwitchToBackupDNSService() { if (!gDNSServiceWrapper) { return; } gDNSServiceWrapper->mBackupDNSService = nsDNSService::GetSingleton(); MutexAutoLock lock(gDNSServiceWrapper->mLock); gDNSServiceWrapper->mBackupDNSService.swap( gDNSServiceWrapper->mDNSServiceInUse); } nsIDNSService* DNSServiceWrapper::DNSService() { MOZ_ASSERT(XRE_IsParentProcess()); MutexAutoLock lock(mLock); return mDNSServiceInUse.get(); } nsPIDNSService* DNSServiceWrapper::PIDNSService() { MOZ_ASSERT(XRE_IsParentProcess()); nsCOMPtr service = do_QueryInterface(DNSService()); return service.get(); } //----------------------------------------------------------------------------- NS_IMPL_ISUPPORTS_INHERITED(nsDNSService, DNSServiceBase, nsIDNSService, nsPIDNSService, nsIMemoryReporter) /****************************************************************************** * nsDNSService impl: * singleton instance ctor/dtor methods ******************************************************************************/ static StaticRefPtr gDNSService; static Atomic gInited(false); already_AddRefed GetOrInitDNSService() { if (gInited) { return nsDNSService::GetXPCOMSingleton(); } nsCOMPtr dns = nullptr; auto initTask = [&dns]() { dns = do_GetService(NS_DNSSERVICE_CID); }; if (!NS_IsMainThread()) { // Forward to the main thread synchronously. RefPtr mainThread = do_GetMainThread(); if (!mainThread) { return nullptr; } SyncRunnable::DispatchToThread( mainThread, NS_NewRunnableFunction("GetOrInitDNSService", initTask)); } else { initTask(); } return dns.forget(); } already_AddRefed nsDNSService::GetXPCOMSingleton() { auto getDNSHelper = []() -> already_AddRefed { if (nsIOService::UseSocketProcess()) { if (XRE_IsSocketProcess()) { return GetSingleton(); } if (XRE_IsParentProcess()) { return DNSServiceWrapper::GetSingleton(); } if (XRE_IsContentProcess()) { return ChildDNSService::GetSingleton(); } return nullptr; } if (XRE_IsParentProcess()) { return GetSingleton(); } if (XRE_IsContentProcess() || XRE_IsSocketProcess()) { return ChildDNSService::GetSingleton(); } return nullptr; }; if (gInited) { return getDNSHelper(); } nsCOMPtr dns = getDNSHelper(); if (dns) { gInited = true; } return dns.forget(); } already_AddRefed nsDNSService::GetSingleton() { MOZ_ASSERT_IF(nsIOService::UseSocketProcess(), XRE_IsSocketProcess()); MOZ_ASSERT_IF(!nsIOService::UseSocketProcess(), XRE_IsParentProcess()); if (!gDNSService) { if (!NS_IsMainThread()) { return nullptr; } gDNSService = new nsDNSService(); if (NS_SUCCEEDED(gDNSService->Init())) { ClearOnShutdown(&gDNSService); } else { gDNSService = nullptr; } } return do_AddRef(gDNSService); } void nsDNSService::ReadPrefs(const char* name) { DNSServiceBase::ReadPrefs(name); bool tmpbool; uint32_t tmpint; mResolverPrefsUpdated = false; // resolver-specific prefs first if (!name || !strcmp(name, kPrefDnsCacheEntries)) { if (NS_SUCCEEDED(Preferences::GetUint(kPrefDnsCacheEntries, &tmpint))) { if (!name || (tmpint != mResCacheEntries)) { mResCacheEntries = tmpint; mResolverPrefsUpdated = true; } } } if (!name || !strcmp(name, kPrefDnsCacheExpiration)) { if (NS_SUCCEEDED(Preferences::GetUint(kPrefDnsCacheExpiration, &tmpint))) { if (!name || (tmpint != mResCacheExpiration)) { mResCacheExpiration = tmpint; mResolverPrefsUpdated = true; } } } if (!name || !strcmp(name, kPrefDnsCacheGrace)) { if (NS_SUCCEEDED(Preferences::GetUint(kPrefDnsCacheGrace, &tmpint))) { if (!name || (tmpint != mResCacheGrace)) { mResCacheGrace = tmpint; mResolverPrefsUpdated = true; } } } // DNSservice prefs if (!name || !strcmp(name, kPrefDnsOfflineLocalhost)) { if (NS_SUCCEEDED( Preferences::GetBool(kPrefDnsOfflineLocalhost, &tmpbool))) { mOfflineLocalhost = tmpbool; } } if (!name || !strcmp(name, kPrefBlockDotOnion)) { if (NS_SUCCEEDED(Preferences::GetBool(kPrefBlockDotOnion, &tmpbool))) { mBlockDotOnion = tmpbool; } } if (!name || !strcmp(name, kPrefDnsNotifyResolution)) { if (NS_SUCCEEDED( Preferences::GetBool(kPrefDnsNotifyResolution, &tmpbool))) { mNotifyResolution = tmpbool; } } if (!name || !strcmp(name, kPrefIPv4OnlyDomains)) { Preferences::GetCString(kPrefIPv4OnlyDomains, mIPv4OnlyDomains); } if (!name || !strcmp(name, kPrefDnsLocalDomains)) { nsCString localDomains; Preferences::GetCString(kPrefDnsLocalDomains, localDomains); MutexAutoLock lock(mLock); mLocalDomains.Clear(); for (const auto& token : nsCCharSeparatedTokenizerTemplate( localDomains, ',') .ToRange()) { mLocalDomains.Insert(token); } } if (!name || !strcmp(name, kPrefDnsForceResolve)) { Preferences::GetCString(kPrefDnsForceResolve, mForceResolve); mForceResolveOn = !mForceResolve.IsEmpty(); } } NS_IMETHODIMP nsDNSService::Init() { MOZ_ASSERT(!mResolver); MOZ_ASSERT(NS_IsMainThread()); ReadPrefs(nullptr); nsCOMPtr observerService = mozilla::services::GetObserverService(); if (observerService) { observerService->AddObserver(this, "last-pb-context-exited", false); observerService->AddObserver(this, NS_NETWORK_LINK_TOPIC, false); observerService->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false); } RefPtr res; nsresult rv = nsHostResolver::Create(mResCacheEntries, mResCacheExpiration, mResCacheGrace, getter_AddRefs(res)); if (NS_SUCCEEDED(rv)) { // now, set all of our member variables while holding the lock MutexAutoLock lock(mLock); mResolver = res; } nsCOMPtr prefs = do_GetService(NS_PREFSERVICE_CONTRACTID); if (prefs) { // register as prefs observer prefs->AddObserver(kPrefDnsCacheEntries, this, false); prefs->AddObserver(kPrefDnsCacheExpiration, this, false); prefs->AddObserver(kPrefDnsCacheGrace, this, false); prefs->AddObserver(kPrefIPv4OnlyDomains, this, false); prefs->AddObserver(kPrefDnsLocalDomains, this, false); prefs->AddObserver(kPrefDnsForceResolve, this, false); prefs->AddObserver(kPrefDnsOfflineLocalhost, this, false); prefs->AddObserver(kPrefBlockDotOnion, this, false); prefs->AddObserver(kPrefDnsNotifyResolution, this, false); AddPrefObserver(prefs); } nsDNSPrefetch::Initialize(this); RegisterWeakMemoryReporter(this); nsCOMPtr ohttpService( do_GetService("@mozilla.org/network/oblivious-http-service;1")); mTrrService = new TRRService(); if (NS_FAILED(mTrrService->Init())) { mTrrService = nullptr; } nsCOMPtr idn = do_GetService(NS_IDNSERVICE_CONTRACTID); mIDN = idn; return NS_OK; } NS_IMETHODIMP nsDNSService::Shutdown() { UnregisterWeakMemoryReporter(this); RefPtr res; { MutexAutoLock lock(mLock); res = std::move(mResolver); } if (res) { // Shutdown outside lock. res->Shutdown(); } nsCOMPtr observerService = mozilla::services::GetObserverService(); if (observerService) { observerService->RemoveObserver(this, NS_NETWORK_LINK_TOPIC); observerService->RemoveObserver(this, "last-pb-context-exited"); observerService->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID); } return NS_OK; } bool nsDNSService::GetOffline() const { bool offline = false; nsCOMPtr io = do_GetService(NS_IOSERVICE_CONTRACTID); if (io) { io->GetOffline(&offline); } return offline; } NS_IMETHODIMP nsDNSService::GetPrefetchEnabled(bool* outVal) { MutexAutoLock lock(mLock); *outVal = !mDisablePrefetch; return NS_OK; } NS_IMETHODIMP nsDNSService::SetPrefetchEnabled(bool inVal) { MutexAutoLock lock(mLock); mDisablePrefetch = !inVal; return NS_OK; } already_AddRefed nsDNSService::GetResolverLocked() { MutexAutoLock lock(mLock); return do_AddRef(mResolver); } nsresult nsDNSService::PreprocessHostname(bool aLocalDomain, const nsACString& aInput, nsIIDNService* aIDN, nsACString& aACE) { // Enforce RFC 7686 if (mBlockDotOnion && StringEndsWith(aInput, ".onion"_ns)) { return NS_ERROR_UNKNOWN_HOST; } if (aLocalDomain) { aACE.AssignLiteral("localhost"); return NS_OK; } if (mTrrService && mTrrService->MaybeBootstrap(aInput, aACE)) { return NS_OK; } if (mForceResolveOn) { MutexAutoLock lock(mLock); if (!aInput.LowerCaseEqualsASCII("localhost") && !aInput.LowerCaseEqualsASCII("127.0.0.1")) { aACE.Assign(mForceResolve); return NS_OK; } } if (!aIDN || IsAscii(aInput)) { aACE = aInput; return NS_OK; } if (!(IsUtf8(aInput) && NS_SUCCEEDED(aIDN->ConvertUTF8toACE(aInput, aACE)))) { return NS_ERROR_FAILURE; } return NS_OK; } nsresult nsDNSService::AsyncResolveInternal( const nsACString& aHostname, uint16_t type, nsIDNSService::DNSFlags flags, nsIDNSAdditionalInfo* aInfo, nsIDNSListener* aListener, nsIEventTarget* target_, const OriginAttributes& aOriginAttributes, nsICancelable** result) { // grab reference to global host resolver and IDN service. beware // simultaneous shutdown!! RefPtr res; nsCOMPtr idn; nsCOMPtr target = target_; nsCOMPtr listener = aListener; bool localDomain = false; { MutexAutoLock lock(mLock); if (mDisablePrefetch && (flags & RESOLVE_SPECULATE)) { return NS_ERROR_DNS_LOOKUP_QUEUE_FULL; } res = mResolver; idn = mIDN; localDomain = mLocalDomains.Contains(aHostname); } if (mNotifyResolution) { NS_DispatchToMainThread(new NotifyDNSResolution(aHostname)); } if (!res) { return NS_ERROR_OFFLINE; } if ((type != RESOLVE_TYPE_DEFAULT) && (type != RESOLVE_TYPE_TXT) && (type != RESOLVE_TYPE_HTTPSSVC)) { return NS_ERROR_INVALID_ARG; } if (DNSForbiddenByActiveProxy(aHostname, flags)) { // nsHostResolver returns NS_ERROR_UNKNOWN_HOST for lots of reasons. // We use a different error code to differentiate this failure and to make // it clear(er) where this error comes from. return NS_ERROR_UNKNOWN_PROXY_HOST; } nsCString hostname; nsresult rv = PreprocessHostname(localDomain, aHostname, idn, hostname); if (NS_FAILED(rv)) { return rv; } if (GetOffline() && (!mOfflineLocalhost || !hostname.LowerCaseEqualsASCII("localhost"))) { flags |= RESOLVE_OFFLINE; } // make sure JS callers get notification on the main thread nsCOMPtr wrappedListener = do_QueryInterface(listener); if (wrappedListener && !target) { target = GetMainThreadSerialEventTarget(); } if (target) { listener = new DNSListenerProxy(listener, target); } uint16_t af = (type != RESOLVE_TYPE_DEFAULT) ? 0 : GetAFForLookup(hostname, flags); MOZ_ASSERT(listener); RefPtr req = new nsDNSAsyncRequest(res, hostname, DNSAdditionalInfo::URL(aInfo), type, aOriginAttributes, listener, flags, af); if (!req) { return NS_ERROR_OUT_OF_MEMORY; } rv = res->ResolveHost(req->mHost, DNSAdditionalInfo::URL(aInfo), DNSAdditionalInfo::Port(aInfo), type, req->mOriginAttributes, flags, af, req); req.forget(result); return rv; } nsresult nsDNSService::CancelAsyncResolveInternal( const nsACString& aHostname, uint16_t aType, nsIDNSService::DNSFlags aFlags, nsIDNSAdditionalInfo* aInfo, nsIDNSListener* aListener, nsresult aReason, const OriginAttributes& aOriginAttributes) { // grab reference to global host resolver and IDN service. beware // simultaneous shutdown!! RefPtr res; nsCOMPtr idn; bool localDomain = false; { MutexAutoLock lock(mLock); if (mDisablePrefetch && (aFlags & RESOLVE_SPECULATE)) { return NS_ERROR_DNS_LOOKUP_QUEUE_FULL; } res = mResolver; idn = mIDN; localDomain = mLocalDomains.Contains(aHostname); } if (!res) { return NS_ERROR_OFFLINE; } nsCString hostname; nsresult rv = PreprocessHostname(localDomain, aHostname, idn, hostname); if (NS_FAILED(rv)) { return rv; } uint16_t af = (aType != RESOLVE_TYPE_DEFAULT) ? 0 : GetAFForLookup(hostname, aFlags); res->CancelAsyncRequest(hostname, DNSAdditionalInfo::URL(aInfo), aType, aOriginAttributes, aFlags, af, aListener, aReason); return NS_OK; } NS_IMETHODIMP nsDNSService::AsyncResolve(const nsACString& aHostname, nsIDNSService::ResolveType aType, nsIDNSService::DNSFlags flags, nsIDNSAdditionalInfo* aInfo, nsIDNSListener* listener, nsIEventTarget* target_, JS::Handle aOriginAttributes, JSContext* aCx, uint8_t aArgc, nsICancelable** result) { OriginAttributes attrs; if (aArgc == 1) { if (!aOriginAttributes.isObject() || !attrs.Init(aCx, aOriginAttributes)) { return NS_ERROR_INVALID_ARG; } } return AsyncResolveInternal(aHostname, aType, flags, aInfo, listener, target_, attrs, result); } NS_IMETHODIMP nsDNSService::AsyncResolveNative( const nsACString& aHostname, nsIDNSService::ResolveType aType, nsIDNSService::DNSFlags flags, nsIDNSAdditionalInfo* aInfo, nsIDNSListener* aListener, nsIEventTarget* target_, const OriginAttributes& aOriginAttributes, nsICancelable** result) { return AsyncResolveInternal(aHostname, aType, flags, aInfo, aListener, target_, aOriginAttributes, result); } NS_IMETHODIMP nsDNSService::NewAdditionalInfo(const nsACString& aTrrURL, int32_t aPort, nsIDNSAdditionalInfo** aInfo) { RefPtr res = new DNSAdditionalInfo(aTrrURL, aPort); res.forget(aInfo); return NS_OK; } NS_IMETHODIMP nsDNSService::CancelAsyncResolve(const nsACString& aHostname, nsIDNSService::ResolveType aType, nsIDNSService::DNSFlags aFlags, nsIDNSAdditionalInfo* aInfo, nsIDNSListener* aListener, nsresult aReason, JS::Handle aOriginAttributes, JSContext* aCx, uint8_t aArgc) { OriginAttributes attrs; if (aArgc == 1) { if (!aOriginAttributes.isObject() || !attrs.Init(aCx, aOriginAttributes)) { return NS_ERROR_INVALID_ARG; } } return CancelAsyncResolveInternal(aHostname, aType, aFlags, aInfo, aListener, aReason, attrs); } NS_IMETHODIMP nsDNSService::CancelAsyncResolveNative( const nsACString& aHostname, nsIDNSService::ResolveType aType, nsIDNSService::DNSFlags aFlags, nsIDNSAdditionalInfo* aInfo, nsIDNSListener* aListener, nsresult aReason, const OriginAttributes& aOriginAttributes) { return CancelAsyncResolveInternal(aHostname, aType, aFlags, aInfo, aListener, aReason, aOriginAttributes); } NS_IMETHODIMP nsDNSService::Resolve(const nsACString& aHostname, nsIDNSService::DNSFlags flags, JS::Handle aOriginAttributes, JSContext* aCx, uint8_t aArgc, nsIDNSRecord** result) { OriginAttributes attrs; if (aArgc == 1) { if (!aOriginAttributes.isObject() || !attrs.Init(aCx, aOriginAttributes)) { return NS_ERROR_INVALID_ARG; } } return ResolveNative(aHostname, flags, attrs, result); } NS_IMETHODIMP nsDNSService::ResolveNative(const nsACString& aHostname, nsIDNSService::DNSFlags flags, const OriginAttributes& aOriginAttributes, nsIDNSRecord** result) { // Synchronous resolution is not available on the main thread. if (NS_IsMainThread()) { return NS_ERROR_NOT_AVAILABLE; } return ResolveInternal(aHostname, flags, aOriginAttributes, result); } nsresult nsDNSService::DeprecatedSyncResolve( const nsACString& aHostname, nsIDNSService::DNSFlags flags, const OriginAttributes& aOriginAttributes, nsIDNSRecord** result) { return ResolveInternal(aHostname, flags, aOriginAttributes, result); } nsresult nsDNSService::ResolveInternal( const nsACString& aHostname, nsIDNSService::DNSFlags flags, const OriginAttributes& aOriginAttributes, nsIDNSRecord** result) { // grab reference to global host resolver and IDN service. beware // simultaneous shutdown!! RefPtr res; nsCOMPtr idn; bool localDomain = false; { MutexAutoLock lock(mLock); res = mResolver; idn = mIDN; localDomain = mLocalDomains.Contains(aHostname); } if (mNotifyResolution) { NS_DispatchToMainThread(new NotifyDNSResolution(aHostname)); } NS_ENSURE_TRUE(res, NS_ERROR_OFFLINE); nsCString hostname; nsresult rv = PreprocessHostname(localDomain, aHostname, idn, hostname); if (NS_FAILED(rv)) { return rv; } if (GetOffline() && (!mOfflineLocalhost || !hostname.LowerCaseEqualsASCII("localhost"))) { flags |= RESOLVE_OFFLINE; } if (DNSForbiddenByActiveProxy(aHostname, flags)) { return NS_ERROR_UNKNOWN_PROXY_HOST; } // // sync resolve: since the host resolver only works asynchronously, we need // to use a mutex and a condvar to wait for the result. however, since the // result may be in the resolvers cache, we might get called back recursively // on the same thread. so, our mutex needs to be re-entrant. in other words, // we need to use a monitor! ;-) // PRMonitor* mon = PR_NewMonitor(); if (!mon) { return NS_ERROR_OUT_OF_MEMORY; } PR_EnterMonitor(mon); RefPtr syncReq = new nsDNSSyncRequest(mon); uint16_t af = GetAFForLookup(hostname, flags); // TRR uses the main thread for the HTTPS channel to the DoH server. // If this were to block the main thread while waiting for TRR it would // likely cause a deadlock. Instead we intentionally choose to not use TRR // for this. if (NS_IsMainThread()) { flags |= RESOLVE_DISABLE_TRR; } rv = res->ResolveHost(hostname, ""_ns, -1, RESOLVE_TYPE_DEFAULT, aOriginAttributes, flags, af, syncReq); if (NS_SUCCEEDED(rv)) { // wait for result while (!syncReq->mDone) { PR_Wait(mon, PR_INTERVAL_NO_TIMEOUT); } if (NS_FAILED(syncReq->mStatus)) { rv = syncReq->mStatus; } else { NS_ASSERTION(syncReq->mHostRecord, "no host record"); RefPtr rec = new nsDNSRecord(syncReq->mHostRecord); rec.forget(result); } } PR_ExitMonitor(mon); PR_DestroyMonitor(mon); return rv; } NS_IMETHODIMP nsDNSService::GetMyHostName(nsACString& result) { char name[100]; if (PR_GetSystemInfo(PR_SI_HOSTNAME, name, sizeof(name)) == PR_SUCCESS) { result = name; return NS_OK; } return NS_ERROR_FAILURE; } NS_IMETHODIMP nsDNSService::Observe(nsISupports* subject, const char* topic, const char16_t* data) { bool flushCache = false; RefPtr resolver = GetResolverLocked(); if (!strcmp(topic, NS_NETWORK_LINK_TOPIC)) { nsAutoCString converted = NS_ConvertUTF16toUTF8(data); if (!strcmp(converted.get(), NS_NETWORK_LINK_DATA_CHANGED)) { flushCache = true; } } else if (!strcmp(topic, "last-pb-context-exited")) { flushCache = true; } else if (!strcmp(topic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID)) { ReadPrefs(NS_ConvertUTF16toUTF8(data).get()); NS_ENSURE_TRUE(resolver, NS_ERROR_NOT_INITIALIZED); if (mResolverPrefsUpdated && resolver) { resolver->SetCacheLimits(mResCacheEntries, mResCacheExpiration, mResCacheGrace); } } else if (!strcmp(topic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) { Shutdown(); } if (flushCache && resolver) { resolver->FlushCache(false); return NS_OK; } return NS_OK; } uint16_t nsDNSService::GetAFForLookup(const nsACString& host, nsIDNSService::DNSFlags flags) { if (StaticPrefs::network_dns_disableIPv6() || (flags & RESOLVE_DISABLE_IPV6)) { return PR_AF_INET; } MutexAutoLock lock(mLock); uint16_t af = PR_AF_UNSPEC; if (!mIPv4OnlyDomains.IsEmpty()) { const char *domain, *domainEnd, *end; uint32_t hostLen, domainLen; // see if host is in one of the IPv4-only domains domain = mIPv4OnlyDomains.BeginReading(); domainEnd = mIPv4OnlyDomains.EndReading(); nsACString::const_iterator hostStart; host.BeginReading(hostStart); hostLen = host.Length(); do { // skip any whitespace while (*domain == ' ' || *domain == '\t') { ++domain; } // find end of this domain in the string end = strchr(domain, ','); if (!end) { end = domainEnd; } // to see if the hostname is in the domain, check if the domain // matches the end of the hostname. domainLen = end - domain; if (domainLen && hostLen >= domainLen) { const char* hostTail = hostStart.get() + hostLen - domainLen; if (nsCRT::strncasecmp(domain, hostTail, domainLen) == 0) { // now, make sure either that the hostname is a direct match or // that the hostname begins with a dot. if (hostLen == domainLen || *hostTail == '.' || *(hostTail - 1) == '.') { af = PR_AF_INET; break; } } } domain = end + 1; } while (*end); } if ((af != PR_AF_INET) && (flags & RESOLVE_DISABLE_IPV4)) { af = PR_AF_INET6; } return af; } NS_IMETHODIMP nsDNSService::GetDNSCacheEntries( nsTArray* args) { RefPtr resolver = GetResolverLocked(); NS_ENSURE_TRUE(resolver, NS_ERROR_NOT_INITIALIZED); resolver->GetDNSCacheEntries(args); return NS_OK; } NS_IMETHODIMP nsDNSService::ClearCache(bool aTrrToo) { RefPtr resolver = GetResolverLocked(); NS_ENSURE_TRUE(resolver, NS_ERROR_NOT_INITIALIZED); resolver->FlushCache(aTrrToo); return NS_OK; } NS_IMETHODIMP nsDNSService::ReloadParentalControlEnabled() { if (mTrrService) { mTrrService->mParentalControlEnabled = TRRService::GetParentalControlEnabledInternal(); } return NS_OK; } NS_IMETHODIMP nsDNSService::SetDetectedTrrURI(const nsACString& aURI) { if (mTrrService) { mTrrService->SetDetectedTrrURI(aURI); } return NS_OK; } NS_IMETHODIMP nsDNSService::SetHeuristicDetectionResult(nsITRRSkipReason::value aValue) { if (mTrrService) { mTrrService->SetHeuristicDetectionResult(aValue); } return NS_OK; } NS_IMETHODIMP nsDNSService::GetHeuristicDetectionResult(nsITRRSkipReason::value* aValue) { if (!mTrrService) { return NS_ERROR_NOT_AVAILABLE; } *aValue = mTrrService->GetHeuristicDetectionResult(); return NS_OK; } NS_IMETHODIMP nsDNSService::GetTRRSkipReasonName(nsITRRSkipReason::value aValue, nsACString& aName) { return mozilla::net::GetTRRSkipReasonName(aValue, aName); } NS_IMETHODIMP nsDNSService::GetCurrentTrrURI(nsACString& aURI) { if (mTrrService) { mTrrService->GetURI(aURI); } return NS_OK; } NS_IMETHODIMP nsDNSService::GetCurrentTrrMode(nsIDNSService::ResolverMode* aMode) { *aMode = nsIDNSService::MODE_NATIVEONLY; // The default mode. if (mTrrService) { *aMode = mTrrService->Mode(); } return NS_OK; } NS_IMETHODIMP nsDNSService::GetCurrentTrrConfirmationState(uint32_t* aConfirmationState) { *aConfirmationState = uint32_t(TRRService::CONFIRM_OFF); if (mTrrService) { *aConfirmationState = mTrrService->ConfirmationState(); } return NS_OK; } NS_IMETHODIMP nsDNSService::GetTrrDomain(nsACString& aTRRDomain) { aTRRDomain.Truncate(); nsAutoCString url; if (mTrrService) { mTrrService->GetURI(url); } nsCOMPtr uri; nsresult rv = NS_NewURI(getter_AddRefs(uri), url); if (NS_FAILED(rv)) { // An empty TRR domain in case of invalid URL. return NS_OK; } return uri->GetHost(aTRRDomain); } nsresult nsDNSService::GetTRRDomainKey(nsACString& aTRRDomain) { aTRRDomain = TRRService::ProviderKey(); return NS_OK; } size_t nsDNSService::SizeOfIncludingThis( mozilla::MallocSizeOf mallocSizeOf) const { // Measurement of the following members may be added later if DMD finds it // is worthwhile: // - mIDN // - mLock size_t n = mallocSizeOf(this); n += mResolver ? mResolver->SizeOfIncludingThis(mallocSizeOf) : 0; n += mIPv4OnlyDomains.SizeOfExcludingThisIfUnshared(mallocSizeOf); n += mLocalDomains.SizeOfExcludingThis(mallocSizeOf); n += mFailedSVCDomainNames.ShallowSizeOfExcludingThis(mallocSizeOf); for (const auto& data : mFailedSVCDomainNames.Values()) { n += data->ShallowSizeOfExcludingThis(mallocSizeOf); for (const auto& name : *data) { n += name.SizeOfExcludingThisIfUnshared(mallocSizeOf); } } return n; } MOZ_DEFINE_MALLOC_SIZE_OF(DNSServiceMallocSizeOf) NS_IMETHODIMP nsDNSService::CollectReports(nsIHandleReportCallback* aHandleReport, nsISupports* aData, bool aAnonymize) { MOZ_COLLECT_REPORT("explicit/network/dns-service", KIND_HEAP, UNITS_BYTES, SizeOfIncludingThis(DNSServiceMallocSizeOf), "Memory used for the DNS service."); return NS_OK; } NS_IMETHODIMP nsDNSService::ReportFailedSVCDomainName(const nsACString& aOwnerName, const nsACString& aSVCDomainName) { MutexAutoLock lock(mLock); mFailedSVCDomainNames.GetOrInsertNew(aOwnerName, 1) ->AppendElement(aSVCDomainName); return NS_OK; } NS_IMETHODIMP nsDNSService::IsSVCDomainNameFailed(const nsACString& aOwnerName, const nsACString& aSVCDomainName, bool* aResult) { NS_ENSURE_ARG(aResult); MutexAutoLock lock(mLock); *aResult = false; nsTArray* failedList = mFailedSVCDomainNames.Get(aOwnerName); if (!failedList) { return NS_OK; } *aResult = failedList->Contains(aSVCDomainName); return NS_OK; } NS_IMETHODIMP nsDNSService::ResetExcludedSVCDomainName(const nsACString& aOwnerName) { MutexAutoLock lock(mLock); mFailedSVCDomainNames.Remove(aOwnerName); return NS_OK; } NS_IMETHODIMP nsDNSService::GetLastConfirmationStatus(nsresult* aConfirmationStatus) { if (!mTrrService) { return NS_ERROR_NOT_AVAILABLE; } *aConfirmationStatus = mTrrService->LastConfirmationStatus(); return NS_OK; } NS_IMETHODIMP nsDNSService::GetLastConfirmationSkipReason( TRRSkippedReason* aSkipReason) { if (!mTrrService) { return NS_ERROR_NOT_AVAILABLE; } *aSkipReason = mTrrService->LastConfirmationSkipReason(); return NS_OK; } namespace mozilla::net { nsresult GetTRRSkipReasonName(TRRSkippedReason aReason, nsACString& aName) { static_assert(TRRSkippedReason::TRR_UNSET == 0); static_assert(TRRSkippedReason::TRR_OK == 1); static_assert(TRRSkippedReason::TRR_NO_GSERVICE == 2); static_assert(TRRSkippedReason::TRR_PARENTAL_CONTROL == 3); static_assert(TRRSkippedReason::TRR_OFF_EXPLICIT == 4); static_assert(TRRSkippedReason::TRR_REQ_MODE_DISABLED == 5); static_assert(TRRSkippedReason::TRR_MODE_NOT_ENABLED == 6); static_assert(TRRSkippedReason::TRR_FAILED == 7); static_assert(TRRSkippedReason::TRR_MODE_UNHANDLED_DEFAULT == 8); static_assert(TRRSkippedReason::TRR_MODE_UNHANDLED_DISABLED == 9); static_assert(TRRSkippedReason::TRR_DISABLED_FLAG == 10); static_assert(TRRSkippedReason::TRR_TIMEOUT == 11); static_assert(TRRSkippedReason::TRR_CHANNEL_DNS_FAIL == 12); static_assert(TRRSkippedReason::TRR_IS_OFFLINE == 13); static_assert(TRRSkippedReason::TRR_NOT_CONFIRMED == 14); static_assert(TRRSkippedReason::TRR_DID_NOT_MAKE_QUERY == 15); static_assert(TRRSkippedReason::TRR_UNKNOWN_CHANNEL_FAILURE == 16); static_assert(TRRSkippedReason::TRR_HOST_BLOCKED_TEMPORARY == 17); static_assert(TRRSkippedReason::TRR_SEND_FAILED == 18); static_assert(TRRSkippedReason::TRR_NET_RESET == 19); static_assert(TRRSkippedReason::TRR_NET_TIMEOUT == 20); static_assert(TRRSkippedReason::TRR_NET_REFUSED == 21); static_assert(TRRSkippedReason::TRR_NET_INTERRUPT == 22); static_assert(TRRSkippedReason::TRR_NET_INADEQ_SEQURITY == 23); static_assert(TRRSkippedReason::TRR_NO_ANSWERS == 24); static_assert(TRRSkippedReason::TRR_DECODE_FAILED == 25); static_assert(TRRSkippedReason::TRR_EXCLUDED == 26); static_assert(TRRSkippedReason::TRR_SERVER_RESPONSE_ERR == 27); static_assert(TRRSkippedReason::TRR_RCODE_FAIL == 28); static_assert(TRRSkippedReason::TRR_NO_CONNECTIVITY == 29); static_assert(TRRSkippedReason::TRR_NXDOMAIN == 30); static_assert(TRRSkippedReason::TRR_REQ_CANCELLED == 31); static_assert(TRRSkippedReason::ODOH_KEY_NOT_USABLE == 32); static_assert(TRRSkippedReason::ODOH_UPDATE_KEY_FAILED == 33); static_assert(TRRSkippedReason::ODOH_KEY_NOT_AVAILABLE == 34); static_assert(TRRSkippedReason::ODOH_ENCRYPTION_FAILED == 35); static_assert(TRRSkippedReason::ODOH_DECRYPTION_FAILED == 36); static_assert(TRRSkippedReason::TRR_HEURISTIC_TRIPPED_GOOGLE_SAFESEARCH == 37); static_assert(TRRSkippedReason::TRR_HEURISTIC_TRIPPED_YOUTUBE_SAFESEARCH == 38); static_assert(TRRSkippedReason::TRR_HEURISTIC_TRIPPED_ZSCALER_CANARY == 39); static_assert(TRRSkippedReason::TRR_HEURISTIC_TRIPPED_CANARY == 40); static_assert(TRRSkippedReason::TRR_HEURISTIC_TRIPPED_MODIFIED_ROOTS == 41); static_assert(TRRSkippedReason::TRR_HEURISTIC_TRIPPED_PARENTAL_CONTROLS == 42); static_assert(TRRSkippedReason::TRR_HEURISTIC_TRIPPED_THIRD_PARTY_ROOTS == 43); static_assert(TRRSkippedReason::TRR_HEURISTIC_TRIPPED_ENTERPRISE_POLICY == 44); static_assert(TRRSkippedReason::TRR_HEURISTIC_TRIPPED_VPN == 45); static_assert(TRRSkippedReason::TRR_HEURISTIC_TRIPPED_PROXY == 46); static_assert(TRRSkippedReason::TRR_HEURISTIC_TRIPPED_NRPT == 47); static_assert(TRRSkippedReason::TRR_BAD_URL == 48); switch (aReason) { case TRRSkippedReason::TRR_UNSET: aName = "TRR_UNSET"_ns; break; case TRRSkippedReason::TRR_OK: aName = "TRR_OK"_ns; break; case TRRSkippedReason::TRR_NO_GSERVICE: aName = "TRR_NO_GSERVICE"_ns; break; case TRRSkippedReason::TRR_PARENTAL_CONTROL: aName = "TRR_PARENTAL_CONTROL"_ns; break; case TRRSkippedReason::TRR_OFF_EXPLICIT: aName = "TRR_OFF_EXPLICIT"_ns; break; case TRRSkippedReason::TRR_REQ_MODE_DISABLED: aName = "TRR_REQ_MODE_DISABLED"_ns; break; case TRRSkippedReason::TRR_MODE_NOT_ENABLED: aName = "TRR_MODE_NOT_ENABLED"_ns; break; case TRRSkippedReason::TRR_FAILED: aName = "TRR_FAILED"_ns; break; case TRRSkippedReason::TRR_MODE_UNHANDLED_DEFAULT: aName = "TRR_MODE_UNHANDLED_DEFAULT"_ns; break; case TRRSkippedReason::TRR_MODE_UNHANDLED_DISABLED: aName = "TRR_MODE_UNHANDLED_DISABLED"_ns; break; case TRRSkippedReason::TRR_DISABLED_FLAG: aName = "TRR_DISABLED_FLAG"_ns; break; case TRRSkippedReason::TRR_TIMEOUT: aName = "TRR_TIMEOUT"_ns; break; case TRRSkippedReason::TRR_CHANNEL_DNS_FAIL: aName = "TRR_CHANNEL_DNS_FAIL"_ns; break; case TRRSkippedReason::TRR_IS_OFFLINE: aName = "TRR_IS_OFFLINE"_ns; break; case TRRSkippedReason::TRR_NOT_CONFIRMED: aName = "TRR_NOT_CONFIRMED"_ns; break; case TRRSkippedReason::TRR_DID_NOT_MAKE_QUERY: aName = "TRR_DID_NOT_MAKE_QUERY"_ns; break; case TRRSkippedReason::TRR_UNKNOWN_CHANNEL_FAILURE: aName = "TRR_UNKNOWN_CHANNEL_FAILURE"_ns; break; case TRRSkippedReason::TRR_HOST_BLOCKED_TEMPORARY: aName = "TRR_HOST_BLOCKED_TEMPORARY"_ns; break; case TRRSkippedReason::TRR_SEND_FAILED: aName = "TRR_SEND_FAILED"_ns; break; case TRRSkippedReason::TRR_NET_RESET: aName = "TRR_NET_RESET"_ns; break; case TRRSkippedReason::TRR_NET_TIMEOUT: aName = "TRR_NET_TIMEOUT"_ns; break; case TRRSkippedReason::TRR_NET_REFUSED: aName = "TRR_NET_REFUSED"_ns; break; case TRRSkippedReason::TRR_NET_INTERRUPT: aName = "TRR_NET_INTERRUPT"_ns; break; case TRRSkippedReason::TRR_NET_INADEQ_SEQURITY: aName = "TRR_NET_INADEQ_SEQURITY"_ns; break; case TRRSkippedReason::TRR_NO_ANSWERS: aName = "TRR_NO_ANSWERS"_ns; break; case TRRSkippedReason::TRR_DECODE_FAILED: aName = "TRR_DECODE_FAILED"_ns; break; case TRRSkippedReason::TRR_EXCLUDED: aName = "TRR_EXCLUDED"_ns; break; case TRRSkippedReason::TRR_SERVER_RESPONSE_ERR: aName = "TRR_SERVER_RESPONSE_ERR"_ns; break; case TRRSkippedReason::TRR_RCODE_FAIL: aName = "TRR_RCODE_FAIL"_ns; break; case TRRSkippedReason::TRR_NO_CONNECTIVITY: aName = "TRR_NO_CONNECTIVITY"_ns; break; case TRRSkippedReason::TRR_NXDOMAIN: aName = "TRR_NXDOMAIN"_ns; break; case TRRSkippedReason::TRR_REQ_CANCELLED: aName = "TRR_REQ_CANCELLED"_ns; break; case TRRSkippedReason::ODOH_KEY_NOT_USABLE: aName = "ODOH_KEY_NOT_USABLE"_ns; break; case TRRSkippedReason::ODOH_UPDATE_KEY_FAILED: aName = "ODOH_UPDATE_KEY_FAILED"_ns; break; case TRRSkippedReason::ODOH_KEY_NOT_AVAILABLE: aName = "ODOH_KEY_NOT_AVAILABLE"_ns; break; case TRRSkippedReason::ODOH_ENCRYPTION_FAILED: aName = "ODOH_ENCRYPTION_FAILED"_ns; break; case TRRSkippedReason::ODOH_DECRYPTION_FAILED: aName = "ODOH_DECRYPTION_FAILED"_ns; break; case TRRSkippedReason::TRR_HEURISTIC_TRIPPED_GOOGLE_SAFESEARCH: aName = "TRR_HEURISTIC_TRIPPED_GOOGLE_SAFESEARCH"_ns; break; case TRRSkippedReason::TRR_HEURISTIC_TRIPPED_YOUTUBE_SAFESEARCH: aName = "TRR_HEURISTIC_TRIPPED_YOUTUBE_SAFESEARCH"_ns; break; case TRRSkippedReason::TRR_HEURISTIC_TRIPPED_ZSCALER_CANARY: aName = "TRR_HEURISTIC_TRIPPED_ZSCALER_CANARY"_ns; break; case TRRSkippedReason::TRR_HEURISTIC_TRIPPED_CANARY: aName = "TRR_HEURISTIC_TRIPPED_CANARY"_ns; break; case TRRSkippedReason::TRR_HEURISTIC_TRIPPED_MODIFIED_ROOTS: aName = "TRR_HEURISTIC_TRIPPED_MODIFIED_ROOTS"_ns; break; case TRRSkippedReason::TRR_HEURISTIC_TRIPPED_PARENTAL_CONTROLS: aName = "TRR_HEURISTIC_TRIPPED_PARENTAL_CONTROLS"_ns; break; case TRRSkippedReason::TRR_HEURISTIC_TRIPPED_THIRD_PARTY_ROOTS: aName = "TRR_HEURISTIC_TRIPPED_THIRD_PARTY_ROOTS"_ns; break; case TRRSkippedReason::TRR_HEURISTIC_TRIPPED_ENTERPRISE_POLICY: aName = "TRR_HEURISTIC_TRIPPED_ENTERPRISE_POLICY"_ns; break; case TRRSkippedReason::TRR_HEURISTIC_TRIPPED_VPN: aName = "TRR_HEURISTIC_TRIPPED_VPN"_ns; break; case TRRSkippedReason::TRR_HEURISTIC_TRIPPED_PROXY: aName = "TRR_HEURISTIC_TRIPPED_PROXY"_ns; break; case TRRSkippedReason::TRR_HEURISTIC_TRIPPED_NRPT: aName = "TRR_HEURISTIC_TRIPPED_NRPT"_ns; break; case TRRSkippedReason::TRR_BAD_URL: aName = "TRR_BAD_URL"_ns; break; default: MOZ_ASSERT(false, "Unknown value"); } return NS_OK; } } // namespace mozilla::net