diff options
Diffstat (limited to '')
-rw-r--r-- | netwerk/dns/nsDNSService2.cpp | 1790 |
1 files changed, 1790 insertions, 0 deletions
diff --git a/netwerk/dns/nsDNSService2.cpp b/netwerk/dns/nsDNSService2.cpp new file mode 100644 index 0000000000..d36cf7e9a7 --- /dev/null +++ b/netwerk/dns/nsDNSService2.cpp @@ -0,0 +1,1790 @@ +/* -*- 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 kPrefDisableIPv6[] = "network.dns.disableIPv6"; +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<AddrHostRecord> 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<AddrInfo> mAddrInfo; + nsTArray<NetAddr>::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<NetAddr>::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<NetAddr>::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<NetAddr>& 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<nsNetAddr> 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<NetAddr>::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<NetAddr>::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<TypeHostRecord> 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<nsCString>& aRecords) { + // deep copy + return mHostRecord->GetRecords(aRecords); +} + +NS_IMETHODIMP +nsDNSByTypeRecord::GetRecordsAsOneString(nsACString& aRecords) { + // deep copy + return mHostRecord->GetRecordsAsOneString(aRecords); +} + +NS_IMETHODIMP +nsDNSByTypeRecord::GetRecords(nsTArray<RefPtr<nsISVCBRecord>>& 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<RefPtr<nsISVCBRecord>>& 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<nsHostResolver> 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<nsIDNSListener> 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<nsIDNSRecord> 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<uintptr_t>(mListener.get()); + RefPtr<DNSListenerProxy> wrapper = do_QueryObject(mListener); + if (wrapper) { + originalListenerAddr = wrapper->GetOriginalListenerAddress(); + } + + uintptr_t listenerAddr = reinterpret_cast<uintptr_t>(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<nsHostRecord> 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<nsIObserverService> obs = services::GetObserverService(); + if (obs) { + obs->NotifyObservers(nullptr, "dns-resolution-request", + NS_ConvertUTF8toUTF16(mHostname).get()); + } + return NS_OK; + } + + private: + nsCString mHostname; +}; + +//----------------------------------------------------------------------------- + +static StaticRefPtr<DNSServiceWrapper> gDNSServiceWrapper; + +NS_IMPL_ISUPPORTS(DNSServiceWrapper, nsIDNSService, nsPIDNSService) + +// static +already_AddRefed<nsIDNSService> 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<nsPIDNSService> 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<nsDNSService> gDNSService; +static Atomic<bool> gInited(false); + +already_AddRefed<nsIDNSService> GetOrInitDNSService() { + if (gInited) { + return nsDNSService::GetXPCOMSingleton(); + } + + nsCOMPtr<nsIDNSService> dns = nullptr; + auto initTask = [&dns]() { dns = do_GetService(NS_DNSSERVICE_CID); }; + if (!NS_IsMainThread()) { + // Forward to the main thread synchronously. + RefPtr<nsIThread> mainThread = do_GetMainThread(); + if (!mainThread) { + return nullptr; + } + + SyncRunnable::DispatchToThread( + mainThread, NS_NewRunnableFunction("GetOrInitDNSService", initTask)); + } else { + initTask(); + } + + return dns.forget(); +} + +already_AddRefed<nsIDNSService> nsDNSService::GetXPCOMSingleton() { + auto getDNSHelper = []() -> already_AddRefed<nsIDNSService> { + 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() || XRE_IsSocketProcess()) { + return GetSingleton(); + } + + if (XRE_IsContentProcess()) { + return ChildDNSService::GetSingleton(); + } + + return nullptr; + }; + + if (gInited) { + return getDNSHelper(); + } + + nsCOMPtr<nsIDNSService> dns = getDNSHelper(); + if (dns) { + gInited = true; + } + return dns.forget(); +} + +already_AddRefed<nsDNSService> nsDNSService::GetSingleton() { + MOZ_ASSERT_IF(nsIOService::UseSocketProcess(), XRE_IsSocketProcess()); + + 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, kPrefDisableIPv6)) { + if (NS_SUCCEEDED(Preferences::GetBool(kPrefDisableIPv6, &tmpbool))) { + mDisableIPv6 = tmpbool; + } + } + 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<NS_IsAsciiWhitespace, + nsTokenizerFlags::SeparatorOptional>( + 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<nsIObserverService> 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<nsHostResolver> 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<nsIPrefBranch> 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(kPrefDisableIPv6, this, false); + prefs->AddObserver(kPrefDnsOfflineLocalhost, this, false); + prefs->AddObserver(kPrefBlockDotOnion, this, false); + prefs->AddObserver(kPrefDnsNotifyResolution, this, false); + AddPrefObserver(prefs); + } + + nsDNSPrefetch::Initialize(this); + + RegisterWeakMemoryReporter(this); + + mTrrService = new TRRService(); + if (NS_FAILED(mTrrService->Init())) { + mTrrService = nullptr; + } + + nsCOMPtr<nsIIDNService> idn = do_GetService(NS_IDNSERVICE_CONTRACTID); + mIDN = idn; + + return NS_OK; +} + +NS_IMETHODIMP +nsDNSService::Shutdown() { + UnregisterWeakMemoryReporter(this); + + RefPtr<nsHostResolver> res; + { + MutexAutoLock lock(mLock); + res = std::move(mResolver); + } + if (res) { + // Shutdown outside lock. + res->Shutdown(); + } + + nsCOMPtr<nsIObserverService> 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<nsIIOService> 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<nsHostResolver> 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<nsHostResolver> res; + nsCOMPtr<nsIIDNService> idn; + nsCOMPtr<nsIEventTarget> target = target_; + nsCOMPtr<nsIDNSListener> 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<nsIXPConnectWrappedJS> 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<nsDNSAsyncRequest> 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<nsHostResolver> res; + nsCOMPtr<nsIIDNService> 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<JS::Value> 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<DNSAdditionalInfo> 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<JS::Value> 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<JS::Value> 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<nsHostResolver> res; + nsCOMPtr<nsIIDNService> 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<nsDNSSyncRequest> 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<nsDNSRecord> 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<nsHostResolver> 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 (mDisableIPv6 || (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<mozilla::net::DNSCacheEntries>* args) { + RefPtr<nsHostResolver> resolver = GetResolverLocked(); + NS_ENSURE_TRUE(resolver, NS_ERROR_NOT_INITIALIZED); + resolver->GetDNSCacheEntries(args); + return NS_OK; +} + +NS_IMETHODIMP +nsDNSService::ClearCache(bool aTrrToo) { + RefPtr<nsHostResolver> 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<nsIURI> 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<nsCString>* 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); + + 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; + default: + MOZ_ASSERT(false, "Unknown value"); + } + + return NS_OK; +} +} // namespace mozilla::net |