diff options
Diffstat (limited to 'netwerk/dns/nsDNSService2.cpp')
-rw-r--r-- | netwerk/dns/nsDNSService2.cpp | 1071 |
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, ¬ifyResolution); + + 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; +} + |