summaryrefslogtreecommitdiffstats
path: root/netwerk/dns/nsDNSService2.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'netwerk/dns/nsDNSService2.cpp')
-rw-r--r--netwerk/dns/nsDNSService2.cpp1071
1 files changed, 1071 insertions, 0 deletions
diff --git a/netwerk/dns/nsDNSService2.cpp b/netwerk/dns/nsDNSService2.cpp
new file mode 100644
index 000000000..05831139e
--- /dev/null
+++ b/netwerk/dns/nsDNSService2.cpp
@@ -0,0 +1,1071 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim: set sw=4 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 "nsICancelable.h"
+#include "nsIPrefService.h"
+#include "nsIPrefBranch.h"
+#include "nsIServiceManager.h"
+#include "nsIXPConnect.h"
+#include "nsProxyRelease.h"
+#include "nsReadableUtils.h"
+#include "nsString.h"
+#include "nsAutoPtr.h"
+#include "nsNetCID.h"
+#include "nsError.h"
+#include "nsDNSPrefetch.h"
+#include "nsThreadUtils.h"
+#include "nsIProtocolProxyService.h"
+#include "prsystem.h"
+#include "prnetdb.h"
+#include "prmon.h"
+#include "prio.h"
+#include "plstr.h"
+#include "nsIOService.h"
+#include "nsCharSeparatedTokenizer.h"
+#include "nsNetAddr.h"
+#include "nsProxyRelease.h"
+#include "nsIObserverService.h"
+#include "nsINetworkLinkService.h"
+
+#include "mozilla/Attributes.h"
+#include "mozilla/net/NeckoCommon.h"
+#include "mozilla/net/ChildDNSService.h"
+#include "mozilla/net/DNSListenerProxy.h"
+#include "mozilla/Services.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 kPrefDisablePrefetch[] = "network.dns.disablePrefetch";
+static const char kPrefBlockDotOnion[] = "network.dns.blockDotOnion";
+static const char kPrefDnsLocalDomains[] = "network.dns.localDomains";
+static const char kPrefDnsOfflineLocalhost[] = "network.dns.offline-localhost";
+static const char kPrefDnsNotifyResolution[] = "network.dns.notifyResolution";
+
+//-----------------------------------------------------------------------------
+
+class nsDNSRecord : public nsIDNSRecord
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIDNSRECORD
+
+ explicit nsDNSRecord(nsHostRecord *hostRecord)
+ : mHostRecord(hostRecord)
+ , mIter(nullptr)
+ , mIterGenCnt(-1)
+ , mDone(false) {}
+
+private:
+ virtual ~nsDNSRecord() = default;
+
+ RefPtr<nsHostRecord> mHostRecord;
+ NetAddrElement *mIter;
+ int mIterGenCnt; // the generation count of
+ // mHostRecord->addr_info when we
+ // start iterating
+ bool mDone;
+};
+
+NS_IMPL_ISUPPORTS(nsDNSRecord, nsIDNSRecord)
+
+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);
+
+ // if the record is for an IP address literal, then the canonical
+ // host name is the IP address literal.
+ const char *cname;
+ {
+ MutexAutoLock lock(mHostRecord->addr_info_lock);
+ if (mHostRecord->addr_info)
+ cname = mHostRecord->addr_info->mCanonicalName ?
+ mHostRecord->addr_info->mCanonicalName :
+ mHostRecord->addr_info->mHostName;
+ else
+ cname = mHostRecord->host;
+ result.Assign(cname);
+ }
+ 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 = nullptr;
+ mIterGenCnt = mHostRecord->addr_info_gencnt;
+ }
+
+ bool startedFresh = !mIter;
+
+ do {
+ if (!mIter) {
+ mIter = mHostRecord->addr_info->mAddresses.getFirst();
+ } else {
+ mIter = mIter->getNext();
+ }
+ }
+ while (mIter && mHostRecord->Blacklisted(&mIter->mAddress));
+
+ if (!mIter && startedFresh) {
+ // If everything was blacklisted we want to reset the blacklist (and
+ // likely relearn it) and return the first address. That is better
+ // than nothing.
+ mHostRecord->ResetBlacklist();
+ mIter = mHostRecord->addr_info->mAddresses.getFirst();
+ }
+
+ if (mIter) {
+ memcpy(addr, &mIter->mAddress, sizeof(NetAddr));
+ }
+
+ mHostRecord->addr_info_lock.Unlock();
+
+ if (!mIter) {
+ mDone = true;
+ 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, 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 (NetAddrElement *iter = mHostRecord->addr_info->mAddresses.getFirst();
+ iter; iter = iter->getNext()) {
+ if (mHostRecord->Blacklisted(&iter->mAddress)) {
+ continue;
+ }
+ NetAddr *addr = aAddressArray.AppendElement(NetAddr());
+ memcpy(addr, &iter->mAddress, sizeof(NetAddr));
+ 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, 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;
+
+ NS_ADDREF(*result = new nsNetAddr(&addr));
+
+ 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 (NetAddrToString(&addr, 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;
+ }
+
+ NetAddrElement *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 = nullptr;
+ mIterGenCnt = -1;
+ mDone = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDNSRecord::ReportUnusable(uint16_t aPort)
+{
+ // right now we don't use the port in the blacklist
+
+ 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 &&
+ mIter) {
+ mHostRecord->ReportUnusable(&mIter->mAddress);
+ }
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+
+class nsDNSAsyncRequest final : public nsResolveHostCallback
+ , public nsICancelable
+{
+ ~nsDNSAsyncRequest() = default;
+
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSICANCELABLE
+
+ nsDNSAsyncRequest(nsHostResolver *res,
+ const nsACString &host,
+ nsIDNSListener *listener,
+ uint16_t flags,
+ uint16_t af,
+ const nsACString &netInterface)
+ : mResolver(res)
+ , mHost(host)
+ , mListener(listener)
+ , mFlags(flags)
+ , mAF(af)
+ , mNetworkInterface(netInterface) {}
+
+ void OnLookupComplete(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
+ nsCOMPtr<nsIDNSListener> mListener;
+ uint16_t mFlags;
+ uint16_t mAF;
+ nsCString mNetworkInterface;
+};
+
+void
+nsDNSAsyncRequest::OnLookupComplete(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)) {
+ NS_ASSERTION(hostRecord, "no host record");
+ rec = new nsDNSRecord(hostRecord);
+ }
+
+ mListener->OnLookupComplete(this, rec, status);
+ mListener = nullptr;
+
+ // release the reference to ourselves that was added before we were
+ // handed off to the host resolver.
+ NS_RELEASE_THIS();
+}
+
+bool
+nsDNSAsyncRequest::EqualsAsyncListener(nsIDNSListener *aListener)
+{
+ nsCOMPtr<nsIDNSListenerProxy> wrapper = do_QueryInterface(mListener);
+ if (wrapper) {
+ nsCOMPtr<nsIDNSListener> originalListener;
+ wrapper->GetOriginalListener(getter_AddRefs(originalListener));
+ return aListener == originalListener;
+ }
+ return (aListener == mListener);
+}
+
+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_IMPL_ISUPPORTS(nsDNSAsyncRequest, nsICancelable)
+
+NS_IMETHODIMP
+nsDNSAsyncRequest::Cancel(nsresult reason)
+{
+ NS_ENSURE_ARG(NS_FAILED(reason));
+ mResolver->DetachCallback(mHost.get(), mFlags, mAF, mNetworkInterface.get(),
+ this, reason);
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+
+class nsDNSSyncRequest : public nsResolveHostCallback
+{
+public:
+ explicit nsDNSSyncRequest(PRMonitor *mon)
+ : mDone(false)
+ , mStatus(NS_OK)
+ , mMonitor(mon) {}
+ virtual ~nsDNSSyncRequest() = default;
+
+ void OnLookupComplete(nsHostResolver *, nsHostRecord *, nsresult) override;
+ bool EqualsAsyncListener(nsIDNSListener *aListener) override;
+ size_t SizeOfIncludingThis(mozilla::MallocSizeOf) const override;
+
+ bool mDone;
+ nsresult mStatus;
+ RefPtr<nsHostRecord> mHostRecord;
+
+private:
+ PRMonitor *mMonitor;
+};
+
+void
+nsDNSSyncRequest::OnLookupComplete(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)
+ : 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;
+};
+
+//-----------------------------------------------------------------------------
+
+nsDNSService::nsDNSService()
+ : mLock("nsDNSServer.mLock")
+ , mDisableIPv6(false)
+ , mDisablePrefetch(false)
+ , mFirstTime(true)
+ , mNotifyResolution(false)
+ , mOfflineLocalhost(false)
+{
+}
+
+nsDNSService::~nsDNSService() = default;
+
+NS_IMPL_ISUPPORTS(nsDNSService, nsIDNSService, nsPIDNSService, nsIObserver,
+ nsIMemoryReporter)
+
+/******************************************************************************
+ * nsDNSService impl:
+ * singleton instance ctor/dtor methods
+ ******************************************************************************/
+static nsDNSService *gDNSService;
+
+nsIDNSService*
+nsDNSService::GetXPCOMSingleton()
+{
+ if (IsNeckoChild()) {
+ return ChildDNSService::GetSingleton();
+ }
+
+ return GetSingleton();
+}
+
+nsDNSService*
+nsDNSService::GetSingleton()
+{
+ NS_ASSERTION(!IsNeckoChild(), "not a parent process");
+
+ if (gDNSService) {
+ NS_ADDREF(gDNSService);
+ return gDNSService;
+ }
+
+ gDNSService = new nsDNSService();
+ if (gDNSService) {
+ NS_ADDREF(gDNSService);
+ if (NS_FAILED(gDNSService->Init())) {
+ NS_RELEASE(gDNSService);
+ }
+ }
+
+ return gDNSService;
+}
+
+NS_IMETHODIMP
+nsDNSService::Init()
+{
+ if (mResolver)
+ return NS_OK;
+ NS_ENSURE_TRUE(!mResolver, NS_ERROR_ALREADY_INITIALIZED);
+ // prefs
+ uint32_t maxCacheEntries = 400;
+ uint32_t defaultCacheLifetime = 120; // seconds
+ uint32_t defaultGracePeriod = 60; // seconds
+ bool disableIPv6 = false;
+ bool offlineLocalhost = true;
+ bool disablePrefetch = false;
+ bool blockDotOnion = true;
+ int proxyType = nsIProtocolProxyService::PROXYCONFIG_DIRECT;
+ bool notifyResolution = false;
+
+ nsAdoptingCString ipv4OnlyDomains;
+ nsAdoptingCString localDomains;
+
+ // read prefs
+ nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
+ if (prefs) {
+ int32_t val;
+ if (NS_SUCCEEDED(prefs->GetIntPref(kPrefDnsCacheEntries, &val)))
+ maxCacheEntries = (uint32_t) val;
+ if (NS_SUCCEEDED(prefs->GetIntPref(kPrefDnsCacheExpiration, &val)))
+ defaultCacheLifetime = val;
+ if (NS_SUCCEEDED(prefs->GetIntPref(kPrefDnsCacheGrace, &val)))
+ defaultGracePeriod = val;
+
+ // ASSUMPTION: pref branch does not modify out params on failure
+ prefs->GetBoolPref(kPrefDisableIPv6, &disableIPv6);
+ prefs->GetCharPref(kPrefIPv4OnlyDomains, getter_Copies(ipv4OnlyDomains));
+ prefs->GetCharPref(kPrefDnsLocalDomains, getter_Copies(localDomains));
+ prefs->GetBoolPref(kPrefDnsOfflineLocalhost, &offlineLocalhost);
+ prefs->GetBoolPref(kPrefDisablePrefetch, &disablePrefetch);
+ prefs->GetBoolPref(kPrefBlockDotOnion, &blockDotOnion);
+
+ // If a manual proxy is in use, disable prefetch implicitly
+ prefs->GetIntPref("network.proxy.type", &proxyType);
+ prefs->GetBoolPref(kPrefDnsNotifyResolution, &notifyResolution);
+
+ if (mFirstTime) {
+ mFirstTime = false;
+
+ // 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(kPrefDisableIPv6, this, false);
+ prefs->AddObserver(kPrefDnsOfflineLocalhost, this, false);
+ prefs->AddObserver(kPrefDisablePrefetch, this, false);
+ prefs->AddObserver(kPrefBlockDotOnion, this, false);
+ prefs->AddObserver(kPrefDnsNotifyResolution, this, false);
+
+ // Monitor these to see if there is a change in proxy configuration
+ // If a manual proxy is in use, disable prefetch implicitly
+ prefs->AddObserver("network.proxy.type", this, false);
+ }
+ }
+
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ if (observerService) {
+ observerService->AddObserver(this, "last-pb-context-exited", false);
+ observerService->AddObserver(this, NS_NETWORK_LINK_TOPIC, false);
+ }
+
+ nsDNSPrefetch::Initialize(this);
+
+ nsCOMPtr<nsIIDNService> idn = do_GetService(NS_IDNSERVICE_CONTRACTID);
+
+ RefPtr<nsHostResolver> res;
+ nsresult rv = nsHostResolver::Create(maxCacheEntries,
+ defaultCacheLifetime,
+ defaultGracePeriod,
+ getter_AddRefs(res));
+ if (NS_SUCCEEDED(rv)) {
+ // now, set all of our member variables while holding the lock
+ MutexAutoLock lock(mLock);
+ mResolver = res;
+ mIDN = idn;
+ mIPv4OnlyDomains = ipv4OnlyDomains; // exchanges buffer ownership
+ mOfflineLocalhost = offlineLocalhost;
+ mDisableIPv6 = disableIPv6;
+ mBlockDotOnion = blockDotOnion;
+
+ // Disable prefetching either by explicit preference or if a manual proxy is configured
+ mDisablePrefetch = disablePrefetch || (proxyType == nsIProtocolProxyService::PROXYCONFIG_MANUAL);
+
+ mLocalDomains.Clear();
+ if (localDomains) {
+ nsCCharSeparatedTokenizer tokenizer(localDomains, ',',
+ nsCCharSeparatedTokenizer::SEPARATOR_OPTIONAL);
+
+ while (tokenizer.hasMoreTokens()) {
+ mLocalDomains.PutEntry(tokenizer.nextToken());
+ }
+ }
+ mNotifyResolution = notifyResolution;
+ }
+
+ RegisterWeakMemoryReporter(this);
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsDNSService::Shutdown()
+{
+ UnregisterWeakMemoryReporter(this);
+
+ RefPtr<nsHostResolver> res;
+ {
+ MutexAutoLock lock(mLock);
+ res = mResolver;
+ mResolver = nullptr;
+ }
+ if (res) {
+ res->Shutdown();
+ }
+
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ if (observerService) {
+ observerService->RemoveObserver(this, NS_NETWORK_LINK_TOPIC);
+ observerService->RemoveObserver(this, "last-pb-context-exited");
+ }
+
+ 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;
+}
+
+nsresult
+nsDNSService::PreprocessHostname(bool aLocalDomain,
+ const nsACString &aInput,
+ nsIIDNService *aIDN,
+ nsACString &aACE)
+{
+ // Enforce RFC 7686
+ if (mBlockDotOnion &&
+ StringEndsWith(aInput, NS_LITERAL_CSTRING(".onion"))) {
+ return NS_ERROR_UNKNOWN_HOST;
+ }
+
+ if (aLocalDomain) {
+ aACE.AssignLiteral("localhost");
+ 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;
+}
+
+NS_IMETHODIMP
+nsDNSService::AsyncResolve(const nsACString &aHostname,
+ uint32_t flags,
+ nsIDNSListener *listener,
+ nsIEventTarget *target_,
+ nsICancelable **result)
+{
+ return AsyncResolveExtended(aHostname, flags, EmptyCString(), listener, target_,
+ result);
+}
+
+NS_IMETHODIMP
+nsDNSService::AsyncResolveExtended(const nsACString &aHostname,
+ uint32_t flags,
+ const nsACString &aNetworkInterface,
+ nsIDNSListener *listener,
+ nsIEventTarget *target_,
+ nsICancelable **result)
+{
+ // grab reference to global host resolver and IDN service. beware
+ // simultaneous shutdown!!
+ RefPtr<nsHostResolver> res;
+ nsCOMPtr<nsIIDNService> idn;
+ nsCOMPtr<nsIEventTarget> target = target_;
+ bool localDomain = false;
+ {
+ MutexAutoLock lock(mLock);
+
+ if (mDisablePrefetch && (flags & RESOLVE_SPECULATE))
+ return NS_ERROR_DNS_LOOKUP_QUEUE_FULL;
+
+ res = mResolver;
+ idn = mIDN;
+ localDomain = mLocalDomains.GetEntry(aHostname);
+ }
+
+ if (mNotifyResolution) {
+ NS_DispatchToMainThread(new NotifyDNSResolution(aHostname));
+ }
+
+ if (!res)
+ return 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;
+ }
+
+ // make sure JS callers get notification on the main thread
+ nsCOMPtr<nsIXPConnectWrappedJS> wrappedListener = do_QueryInterface(listener);
+ if (wrappedListener && !target) {
+ nsCOMPtr<nsIThread> mainThread;
+ NS_GetMainThread(getter_AddRefs(mainThread));
+ target = do_QueryInterface(mainThread);
+ }
+
+ if (target) {
+ listener = new DNSListenerProxy(listener, target);
+ }
+
+ uint16_t af = GetAFForLookup(hostname, flags);
+
+ auto *req =
+ new nsDNSAsyncRequest(res, hostname, listener, flags, af,
+ aNetworkInterface);
+ if (!req)
+ return NS_ERROR_OUT_OF_MEMORY;
+ NS_ADDREF(*result = req);
+
+ // addref for resolver; will be released when OnLookupComplete is called.
+ NS_ADDREF(req);
+ rv = res->ResolveHost(req->mHost.get(), flags, af,
+ req->mNetworkInterface.get(), req);
+ if (NS_FAILED(rv)) {
+ NS_RELEASE(req);
+ NS_RELEASE(*result);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsDNSService::CancelAsyncResolve(const nsACString &aHostname,
+ uint32_t aFlags,
+ nsIDNSListener *aListener,
+ nsresult aReason)
+{
+ return CancelAsyncResolveExtended(aHostname, aFlags, EmptyCString(), aListener,
+ aReason);
+}
+
+NS_IMETHODIMP
+nsDNSService::CancelAsyncResolveExtended(const nsACString &aHostname,
+ uint32_t aFlags,
+ const nsACString &aNetworkInterface,
+ nsIDNSListener *aListener,
+ nsresult aReason)
+{
+ // 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.GetEntry(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 = GetAFForLookup(hostname, aFlags);
+
+ res->CancelAsyncRequest(hostname.get(), aFlags, af,
+ nsPromiseFlatCString(aNetworkInterface).get(), aListener,
+ aReason);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDNSService::Resolve(const nsACString &aHostname,
+ uint32_t flags,
+ 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.GetEntry(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;
+ }
+
+ //
+ // 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);
+ nsDNSSyncRequest syncReq(mon);
+
+ uint16_t af = GetAFForLookup(hostname, flags);
+
+ rv = res->ResolveHost(hostname.get(), 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");
+ auto *rec = new nsDNSRecord(syncReq.mHostRecord);
+ if (!rec)
+ rv = NS_ERROR_OUT_OF_MEMORY;
+ else
+ NS_ADDREF(*result = rec);
+ }
+ }
+
+ 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)
+{
+ // We are only getting called if a preference has changed or there's a
+ // network link event.
+ NS_ASSERTION(strcmp(topic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID) == 0 ||
+ strcmp(topic, "last-pb-context-exited") == 0 ||
+ strcmp(topic, NS_NETWORK_LINK_TOPIC) == 0,
+ "unexpected observe call");
+
+ bool flushCache = false;
+ if (!strcmp(topic, NS_NETWORK_LINK_TOPIC)) {
+ nsAutoCString converted = NS_ConvertUTF16toUTF8(data);
+ if (mResolver && !strcmp(converted.get(), NS_NETWORK_LINK_DATA_CHANGED)) {
+ flushCache = true;
+ }
+ } else if (!strcmp(topic, "last-pb-context-exited")) {
+ flushCache = true;
+ }
+ if (flushCache) {
+ mResolver->FlushCache();
+ return NS_OK;
+ }
+
+ //
+ // Shutdown and this function are both only called on the UI thread, so we don't
+ // have to worry about mResolver being cleared out from under us.
+ //
+ // NOTE Shutting down and reinitializing the service like this is obviously
+ // suboptimal if Observe gets called several times in a row, but we don't
+ // expect that to be the case.
+ //
+
+ if (mResolver) {
+ Shutdown();
+ }
+ Init();
+ return NS_OK;
+}
+
+uint16_t
+nsDNSService::GetAFForLookup(const nsACString &host, uint32_t 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 (PL_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)
+{
+ NS_ENSURE_TRUE(mResolver, NS_ERROR_NOT_INITIALIZED);
+ mResolver->GetDNSCacheEntries(args);
+ 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);
+ 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;
+}
+