michael@0: /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ michael@0: /* vim: set sw=4 ts=8 et tw=80 : */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "nsDNSService2.h" michael@0: #include "nsIDNSRecord.h" michael@0: #include "nsIDNSListener.h" michael@0: #include "nsICancelable.h" michael@0: #include "nsIPrefService.h" michael@0: #include "nsIPrefBranch.h" michael@0: #include "nsIServiceManager.h" michael@0: #include "nsIXPConnect.h" michael@0: #include "nsProxyRelease.h" michael@0: #include "nsReadableUtils.h" michael@0: #include "nsString.h" michael@0: #include "nsAutoPtr.h" michael@0: #include "nsNetCID.h" michael@0: #include "nsError.h" michael@0: #include "nsDNSPrefetch.h" michael@0: #include "nsThreadUtils.h" michael@0: #include "nsIProtocolProxyService.h" michael@0: #include "prsystem.h" michael@0: #include "prnetdb.h" michael@0: #include "prmon.h" michael@0: #include "prio.h" michael@0: #include "plstr.h" michael@0: #include "nsIOService.h" michael@0: #include "nsCharSeparatedTokenizer.h" michael@0: #include "nsNetAddr.h" michael@0: #include "nsProxyRelease.h" michael@0: michael@0: #include "mozilla/Attributes.h" michael@0: #include "mozilla/VisualEventTracer.h" michael@0: #include "mozilla/net/NeckoCommon.h" michael@0: #include "mozilla/net/ChildDNSService.h" michael@0: #include "mozilla/net/DNSListenerProxy.h" michael@0: michael@0: using namespace mozilla; michael@0: using namespace mozilla::net; michael@0: michael@0: static const char kPrefDnsCacheEntries[] = "network.dnsCacheEntries"; michael@0: static const char kPrefDnsCacheExpiration[] = "network.dnsCacheExpiration"; michael@0: static const char kPrefDnsCacheGrace[] = "network.dnsCacheExpirationGracePeriod"; michael@0: static const char kPrefIPv4OnlyDomains[] = "network.dns.ipv4OnlyDomains"; michael@0: static const char kPrefDisableIPv6[] = "network.dns.disableIPv6"; michael@0: static const char kPrefDisablePrefetch[] = "network.dns.disablePrefetch"; michael@0: static const char kPrefDnsLocalDomains[] = "network.dns.localDomains"; michael@0: static const char kPrefDnsNotifyResolution[] = "network.dns.notifyResolution"; michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: class nsDNSRecord : public nsIDNSRecord michael@0: { michael@0: public: michael@0: NS_DECL_THREADSAFE_ISUPPORTS michael@0: NS_DECL_NSIDNSRECORD michael@0: michael@0: nsDNSRecord(nsHostRecord *hostRecord) michael@0: : mHostRecord(hostRecord) michael@0: , mIter(nullptr) michael@0: , mIterGenCnt(-1) michael@0: , mDone(false) {} michael@0: michael@0: private: michael@0: virtual ~nsDNSRecord() {} michael@0: michael@0: nsRefPtr mHostRecord; michael@0: NetAddrElement *mIter; michael@0: int mIterGenCnt; // the generation count of michael@0: // mHostRecord->addr_info when we michael@0: // start iterating michael@0: bool mDone; michael@0: }; michael@0: michael@0: NS_IMPL_ISUPPORTS(nsDNSRecord, nsIDNSRecord) michael@0: michael@0: NS_IMETHODIMP michael@0: nsDNSRecord::GetCanonicalName(nsACString &result) michael@0: { michael@0: // this method should only be called if we have a CNAME michael@0: NS_ENSURE_TRUE(mHostRecord->flags & nsHostResolver::RES_CANON_NAME, michael@0: NS_ERROR_NOT_AVAILABLE); michael@0: michael@0: // if the record is for an IP address literal, then the canonical michael@0: // host name is the IP address literal. michael@0: const char *cname; michael@0: { michael@0: MutexAutoLock lock(mHostRecord->addr_info_lock); michael@0: if (mHostRecord->addr_info) michael@0: cname = mHostRecord->addr_info->mCanonicalName ? michael@0: mHostRecord->addr_info->mCanonicalName : michael@0: mHostRecord->addr_info->mHostName; michael@0: else michael@0: cname = mHostRecord->host; michael@0: result.Assign(cname); michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsDNSRecord::GetNextAddr(uint16_t port, NetAddr *addr) michael@0: { michael@0: if (mDone) { michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: } michael@0: michael@0: mHostRecord->addr_info_lock.Lock(); michael@0: if (mHostRecord->addr_info) { michael@0: if (mIterGenCnt != mHostRecord->addr_info_gencnt) { michael@0: // mHostRecord->addr_info has changed, restart the iteration. michael@0: mIter = nullptr; michael@0: mIterGenCnt = mHostRecord->addr_info_gencnt; michael@0: } michael@0: michael@0: bool startedFresh = !mIter; michael@0: michael@0: do { michael@0: if (!mIter) { michael@0: mIter = mHostRecord->addr_info->mAddresses.getFirst(); michael@0: } else { michael@0: mIter = mIter->getNext(); michael@0: } michael@0: } michael@0: while (mIter && mHostRecord->Blacklisted(&mIter->mAddress)); michael@0: michael@0: if (!mIter && startedFresh) { michael@0: // If everything was blacklisted we want to reset the blacklist (and michael@0: // likely relearn it) and return the first address. That is better michael@0: // than nothing. michael@0: mHostRecord->ResetBlacklist(); michael@0: mIter = mHostRecord->addr_info->mAddresses.getFirst(); michael@0: } michael@0: michael@0: if (mIter) { michael@0: memcpy(addr, &mIter->mAddress, sizeof(NetAddr)); michael@0: } michael@0: michael@0: mHostRecord->addr_info_lock.Unlock(); michael@0: michael@0: if (!mIter) { michael@0: mDone = true; michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: } michael@0: } michael@0: else { michael@0: mHostRecord->addr_info_lock.Unlock(); michael@0: michael@0: if (!mHostRecord->addr) { michael@0: // Both mHostRecord->addr_info and mHostRecord->addr are null. michael@0: // This can happen if mHostRecord->addr_info expired and the michael@0: // attempt to reresolve it failed. michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: } michael@0: memcpy(addr, mHostRecord->addr, sizeof(NetAddr)); michael@0: mDone = true; michael@0: } michael@0: michael@0: // set given port michael@0: port = htons(port); michael@0: if (addr->raw.family == AF_INET) { michael@0: addr->inet.port = port; michael@0: } michael@0: else if (addr->raw.family == AF_INET6) { michael@0: addr->inet6.port = port; michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsDNSRecord::GetScriptableNextAddr(uint16_t port, nsINetAddr * *result) michael@0: { michael@0: NetAddr addr; michael@0: nsresult rv = GetNextAddr(port, &addr); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: NS_ADDREF(*result = new nsNetAddr(&addr)); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsDNSRecord::GetNextAddrAsString(nsACString &result) michael@0: { michael@0: NetAddr addr; michael@0: nsresult rv = GetNextAddr(0, &addr); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: char buf[kIPv6CStrBufSize]; michael@0: if (NetAddrToString(&addr, buf, sizeof(buf))) { michael@0: result.Assign(buf); michael@0: return NS_OK; michael@0: } michael@0: NS_ERROR("NetAddrToString failed unexpectedly"); michael@0: return NS_ERROR_FAILURE; // conversion failed for some reason michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsDNSRecord::HasMore(bool *result) michael@0: { michael@0: if (mDone) { michael@0: *result = false; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NetAddrElement *iterCopy = mIter; michael@0: michael@0: NetAddr addr; michael@0: *result = NS_SUCCEEDED(GetNextAddr(0, &addr)); michael@0: michael@0: mIter = iterCopy; michael@0: mDone = false; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsDNSRecord::Rewind() michael@0: { michael@0: mIter = nullptr; michael@0: mIterGenCnt = -1; michael@0: mDone = false; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsDNSRecord::ReportUnusable(uint16_t aPort) michael@0: { michael@0: // right now we don't use the port in the blacklist michael@0: michael@0: MutexAutoLock lock(mHostRecord->addr_info_lock); michael@0: michael@0: // Check that we are using a real addr_info (as opposed to a single michael@0: // constant address), and that the generation count is valid. Otherwise, michael@0: // ignore the report. michael@0: michael@0: if (mHostRecord->addr_info && michael@0: mIterGenCnt == mHostRecord->addr_info_gencnt && michael@0: mIter) { michael@0: mHostRecord->ReportUnusable(&mIter->mAddress); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: class nsDNSAsyncRequest MOZ_FINAL : public nsResolveHostCallback michael@0: , public nsICancelable michael@0: { michael@0: public: michael@0: NS_DECL_THREADSAFE_ISUPPORTS michael@0: NS_DECL_NSICANCELABLE michael@0: michael@0: nsDNSAsyncRequest(nsHostResolver *res, michael@0: const nsACString &host, michael@0: nsIDNSListener *listener, michael@0: uint16_t flags, michael@0: uint16_t af) michael@0: : mResolver(res) michael@0: , mHost(host) michael@0: , mListener(listener) michael@0: , mFlags(flags) michael@0: , mAF(af) {} michael@0: ~nsDNSAsyncRequest() {} michael@0: michael@0: void OnLookupComplete(nsHostResolver *, nsHostRecord *, nsresult); michael@0: // Returns TRUE if the DNS listener arg is the same as the member listener michael@0: // Used in Cancellations to remove DNS requests associated with a michael@0: // particular hostname and nsIDNSListener michael@0: bool EqualsAsyncListener(nsIDNSListener *aListener); michael@0: michael@0: size_t SizeOfIncludingThis(mozilla::MallocSizeOf) const; michael@0: michael@0: nsRefPtr mResolver; michael@0: nsCString mHost; // hostname we're resolving michael@0: nsCOMPtr mListener; michael@0: uint16_t mFlags; michael@0: uint16_t mAF; michael@0: }; michael@0: michael@0: void michael@0: nsDNSAsyncRequest::OnLookupComplete(nsHostResolver *resolver, michael@0: nsHostRecord *hostRecord, michael@0: nsresult status) michael@0: { michael@0: // need to have an owning ref when we issue the callback to enable michael@0: // the caller to be able to addref/release multiple times without michael@0: // destroying the record prematurely. michael@0: nsCOMPtr rec; michael@0: if (NS_SUCCEEDED(status)) { michael@0: NS_ASSERTION(hostRecord, "no host record"); michael@0: rec = new nsDNSRecord(hostRecord); michael@0: if (!rec) michael@0: status = NS_ERROR_OUT_OF_MEMORY; michael@0: } michael@0: michael@0: MOZ_EVENT_TRACER_DONE(this, "net::dns::lookup"); michael@0: michael@0: mListener->OnLookupComplete(this, rec, status); michael@0: mListener = nullptr; michael@0: michael@0: // release the reference to ourselves that was added before we were michael@0: // handed off to the host resolver. michael@0: NS_RELEASE_THIS(); michael@0: } michael@0: michael@0: bool michael@0: nsDNSAsyncRequest::EqualsAsyncListener(nsIDNSListener *aListener) michael@0: { michael@0: return (aListener == mListener); michael@0: } michael@0: michael@0: size_t michael@0: nsDNSAsyncRequest::SizeOfIncludingThis(MallocSizeOf mallocSizeOf) const michael@0: { michael@0: size_t n = mallocSizeOf(this); michael@0: michael@0: // The following fields aren't measured. michael@0: // - mHost, because it's a non-owning pointer michael@0: // - mResolver, because it's a non-owning pointer michael@0: // - mListener, because it's a non-owning pointer michael@0: michael@0: return n; michael@0: } michael@0: michael@0: NS_IMPL_ISUPPORTS(nsDNSAsyncRequest, nsICancelable) michael@0: michael@0: NS_IMETHODIMP michael@0: nsDNSAsyncRequest::Cancel(nsresult reason) michael@0: { michael@0: NS_ENSURE_ARG(NS_FAILED(reason)); michael@0: mResolver->DetachCallback(mHost.get(), mFlags, mAF, this, reason); michael@0: return NS_OK; michael@0: } michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: class nsDNSSyncRequest : public nsResolveHostCallback michael@0: { michael@0: public: michael@0: nsDNSSyncRequest(PRMonitor *mon) michael@0: : mDone(false) michael@0: , mStatus(NS_OK) michael@0: , mMonitor(mon) {} michael@0: virtual ~nsDNSSyncRequest() {} michael@0: michael@0: void OnLookupComplete(nsHostResolver *, nsHostRecord *, nsresult); michael@0: bool EqualsAsyncListener(nsIDNSListener *aListener); michael@0: size_t SizeOfIncludingThis(mozilla::MallocSizeOf) const; michael@0: michael@0: bool mDone; michael@0: nsresult mStatus; michael@0: nsRefPtr mHostRecord; michael@0: michael@0: private: michael@0: PRMonitor *mMonitor; michael@0: }; michael@0: michael@0: void michael@0: nsDNSSyncRequest::OnLookupComplete(nsHostResolver *resolver, michael@0: nsHostRecord *hostRecord, michael@0: nsresult status) michael@0: { michael@0: // store results, and wake up nsDNSService::Resolve to process results. michael@0: PR_EnterMonitor(mMonitor); michael@0: mDone = true; michael@0: mStatus = status; michael@0: mHostRecord = hostRecord; michael@0: PR_Notify(mMonitor); michael@0: PR_ExitMonitor(mMonitor); michael@0: } michael@0: michael@0: bool michael@0: nsDNSSyncRequest::EqualsAsyncListener(nsIDNSListener *aListener) michael@0: { michael@0: // Sync request: no listener to compare michael@0: return false; michael@0: } michael@0: michael@0: size_t michael@0: nsDNSSyncRequest::SizeOfIncludingThis(MallocSizeOf mallocSizeOf) const michael@0: { michael@0: size_t n = mallocSizeOf(this); michael@0: michael@0: // The following fields aren't measured. michael@0: // - mHostRecord, because it's a non-owning pointer michael@0: michael@0: // Measurement of the following members may be added later if DMD finds it michael@0: // is worthwhile: michael@0: // - mMonitor michael@0: michael@0: return n; michael@0: } michael@0: michael@0: class NotifyDNSResolution: public nsRunnable michael@0: { michael@0: public: michael@0: NotifyDNSResolution(nsMainThreadPtrHandle &aObs, michael@0: const nsACString &aHostname) michael@0: : mObs(aObs) michael@0: , mHostname(aHostname) michael@0: { michael@0: MOZ_ASSERT(mObs); michael@0: } michael@0: michael@0: NS_IMETHOD Run() michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: mObs->NotifyObservers(nullptr, michael@0: "dns-resolution-request", michael@0: NS_ConvertUTF8toUTF16(mHostname).get()); michael@0: return NS_OK; michael@0: } michael@0: michael@0: private: michael@0: nsMainThreadPtrHandle mObs; michael@0: nsCString mHostname; michael@0: }; michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: nsDNSService::nsDNSService() michael@0: : mLock("nsDNSServer.mLock") michael@0: , mFirstTime(true) michael@0: , mOffline(false) michael@0: { michael@0: } michael@0: michael@0: nsDNSService::~nsDNSService() michael@0: { michael@0: } michael@0: michael@0: NS_IMPL_ISUPPORTS(nsDNSService, nsIDNSService, nsPIDNSService, nsIObserver, michael@0: nsIMemoryReporter) michael@0: michael@0: /****************************************************************************** michael@0: * nsDNSService impl: michael@0: * singleton instance ctor/dtor methods michael@0: ******************************************************************************/ michael@0: static nsDNSService *gDNSService; michael@0: michael@0: nsIDNSService* michael@0: nsDNSService::GetXPCOMSingleton() michael@0: { michael@0: if (IsNeckoChild()) { michael@0: return ChildDNSService::GetSingleton(); michael@0: } michael@0: michael@0: return GetSingleton(); michael@0: } michael@0: michael@0: nsDNSService* michael@0: nsDNSService::GetSingleton() michael@0: { michael@0: NS_ASSERTION(!IsNeckoChild(), "not a parent process"); michael@0: michael@0: if (gDNSService) { michael@0: NS_ADDREF(gDNSService); michael@0: return gDNSService; michael@0: } michael@0: michael@0: gDNSService = new nsDNSService(); michael@0: if (gDNSService) { michael@0: NS_ADDREF(gDNSService); michael@0: if (NS_FAILED(gDNSService->Init())) { michael@0: NS_RELEASE(gDNSService); michael@0: } michael@0: } michael@0: michael@0: return gDNSService; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsDNSService::Init() michael@0: { michael@0: if (mResolver) michael@0: return NS_OK; michael@0: NS_ENSURE_TRUE(!mResolver, NS_ERROR_ALREADY_INITIALIZED); michael@0: michael@0: // prefs michael@0: uint32_t maxCacheEntries = 400; michael@0: uint32_t maxCacheLifetime = 120; // seconds michael@0: uint32_t lifetimeGracePeriod = 60; // seconds michael@0: bool disableIPv6 = false; michael@0: bool disablePrefetch = false; michael@0: bool disableDNS = false; michael@0: int proxyType = nsIProtocolProxyService::PROXYCONFIG_DIRECT; michael@0: bool notifyResolution = false; michael@0: michael@0: nsAdoptingCString ipv4OnlyDomains; michael@0: nsAdoptingCString localDomains; michael@0: michael@0: // read prefs michael@0: nsCOMPtr prefs = do_GetService(NS_PREFSERVICE_CONTRACTID); michael@0: if (prefs) { michael@0: int32_t val; michael@0: if (NS_SUCCEEDED(prefs->GetIntPref(kPrefDnsCacheEntries, &val))) michael@0: maxCacheEntries = (uint32_t) val; michael@0: if (NS_SUCCEEDED(prefs->GetIntPref(kPrefDnsCacheExpiration, &val))) michael@0: maxCacheLifetime = val; michael@0: if (NS_SUCCEEDED(prefs->GetIntPref(kPrefDnsCacheGrace, &val))) michael@0: lifetimeGracePeriod = val; michael@0: michael@0: // ASSUMPTION: pref branch does not modify out params on failure michael@0: prefs->GetBoolPref(kPrefDisableIPv6, &disableIPv6); michael@0: prefs->GetCharPref(kPrefIPv4OnlyDomains, getter_Copies(ipv4OnlyDomains)); michael@0: prefs->GetCharPref(kPrefDnsLocalDomains, getter_Copies(localDomains)); michael@0: prefs->GetBoolPref(kPrefDisablePrefetch, &disablePrefetch); michael@0: michael@0: // If a manual proxy is in use, disable prefetch implicitly michael@0: prefs->GetIntPref("network.proxy.type", &proxyType); michael@0: michael@0: // If the user wants remote DNS, we should fail any lookups that still michael@0: // make it here. michael@0: prefs->GetBoolPref("network.proxy.socks_remote_dns", &disableDNS); michael@0: michael@0: prefs->GetBoolPref(kPrefDnsNotifyResolution, ¬ifyResolution); michael@0: } michael@0: michael@0: if (mFirstTime) { michael@0: mFirstTime = false; michael@0: michael@0: // register as prefs observer michael@0: if (prefs) { michael@0: prefs->AddObserver(kPrefDnsCacheEntries, this, false); michael@0: prefs->AddObserver(kPrefDnsCacheExpiration, this, false); michael@0: prefs->AddObserver(kPrefDnsCacheGrace, this, false); michael@0: prefs->AddObserver(kPrefIPv4OnlyDomains, this, false); michael@0: prefs->AddObserver(kPrefDnsLocalDomains, this, false); michael@0: prefs->AddObserver(kPrefDisableIPv6, this, false); michael@0: prefs->AddObserver(kPrefDisablePrefetch, this, false); michael@0: prefs->AddObserver(kPrefDnsNotifyResolution, this, false); michael@0: michael@0: // Monitor these to see if there is a change in proxy configuration michael@0: // If a manual proxy is in use, disable prefetch implicitly michael@0: prefs->AddObserver("network.proxy.", this, false); michael@0: } michael@0: michael@0: nsresult rv; michael@0: nsCOMPtr observerService = michael@0: do_GetService("@mozilla.org/observer-service;1", &rv); michael@0: if (NS_SUCCEEDED(rv)) { michael@0: observerService->AddObserver(this, "last-pb-context-exited", false); michael@0: } michael@0: } michael@0: michael@0: nsDNSPrefetch::Initialize(this); michael@0: michael@0: // Don't initialize the resolver if we're in offline mode. michael@0: // Later on, the IO service will reinitialize us when going online. michael@0: if (gIOService->IsOffline() && !gIOService->IsComingOnline()) michael@0: return NS_OK; michael@0: michael@0: nsCOMPtr idn = do_GetService(NS_IDNSERVICE_CONTRACTID); michael@0: michael@0: nsCOMPtr obs = michael@0: do_GetService(NS_OBSERVERSERVICE_CONTRACTID); michael@0: michael@0: nsRefPtr res; michael@0: nsresult rv = nsHostResolver::Create(maxCacheEntries, michael@0: maxCacheLifetime, michael@0: lifetimeGracePeriod, michael@0: getter_AddRefs(res)); michael@0: if (NS_SUCCEEDED(rv)) { michael@0: // now, set all of our member variables while holding the lock michael@0: MutexAutoLock lock(mLock); michael@0: mResolver = res; michael@0: mIDN = idn; michael@0: mIPv4OnlyDomains = ipv4OnlyDomains; // exchanges buffer ownership michael@0: mDisableIPv6 = disableIPv6; michael@0: mDisableDNS = disableDNS; michael@0: michael@0: // Disable prefetching either by explicit preference or if a manual proxy is configured michael@0: mDisablePrefetch = disablePrefetch || (proxyType == nsIProtocolProxyService::PROXYCONFIG_MANUAL); michael@0: michael@0: mLocalDomains.Clear(); michael@0: if (localDomains) { michael@0: nsAdoptingString domains; michael@0: domains.AssignASCII(nsDependentCString(localDomains).get()); michael@0: nsCharSeparatedTokenizer tokenizer(domains, ',', michael@0: nsCharSeparatedTokenizerTemplate<>::SEPARATOR_OPTIONAL); michael@0: michael@0: while (tokenizer.hasMoreTokens()) { michael@0: const nsSubstring& domain = tokenizer.nextToken(); michael@0: mLocalDomains.PutEntry(nsDependentCString(NS_ConvertUTF16toUTF8(domain).get())); michael@0: } michael@0: } michael@0: mNotifyResolution = notifyResolution; michael@0: if (mNotifyResolution) { michael@0: mObserverService = michael@0: new nsMainThreadPtrHolder(obs); michael@0: } michael@0: } michael@0: michael@0: RegisterWeakMemoryReporter(this); michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsDNSService::Shutdown() michael@0: { michael@0: UnregisterWeakMemoryReporter(this); michael@0: michael@0: nsRefPtr res; michael@0: { michael@0: MutexAutoLock lock(mLock); michael@0: res = mResolver; michael@0: mResolver = nullptr; michael@0: } michael@0: if (res) michael@0: res->Shutdown(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsDNSService::GetOffline(bool *offline) michael@0: { michael@0: *offline = mOffline; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsDNSService::SetOffline(bool offline) michael@0: { michael@0: mOffline = offline; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsDNSService::GetPrefetchEnabled(bool *outVal) michael@0: { michael@0: *outVal = !mDisablePrefetch; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsDNSService::SetPrefetchEnabled(bool inVal) michael@0: { michael@0: mDisablePrefetch = !inVal; michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP michael@0: nsDNSService::AsyncResolve(const nsACString &hostname, michael@0: uint32_t flags, michael@0: nsIDNSListener *listener, michael@0: nsIEventTarget *target_, michael@0: nsICancelable **result) michael@0: { michael@0: // grab reference to global host resolver and IDN service. beware michael@0: // simultaneous shutdown!! michael@0: nsRefPtr res; michael@0: nsCOMPtr idn; michael@0: nsCOMPtr target = target_; michael@0: bool localDomain = false; michael@0: { michael@0: MutexAutoLock lock(mLock); michael@0: michael@0: if (mDisablePrefetch && (flags & RESOLVE_SPECULATE)) michael@0: return NS_ERROR_DNS_LOOKUP_QUEUE_FULL; michael@0: michael@0: res = mResolver; michael@0: idn = mIDN; michael@0: localDomain = mLocalDomains.GetEntry(hostname); michael@0: } michael@0: michael@0: if (mNotifyResolution) { michael@0: NS_DispatchToMainThread(new NotifyDNSResolution(mObserverService, michael@0: hostname)); michael@0: } michael@0: michael@0: PRNetAddr tempAddr; michael@0: if (mDisableDNS) { michael@0: // Allow IP lookups through, but nothing else. michael@0: if (PR_StringToNetAddr(hostname.BeginReading(), &tempAddr) != PR_SUCCESS) { michael@0: return NS_ERROR_UNKNOWN_PROXY_HOST; // XXX: NS_ERROR_NOT_IMPLEMENTED? michael@0: } michael@0: } michael@0: michael@0: if (!res) michael@0: return NS_ERROR_OFFLINE; michael@0: michael@0: if (mOffline) michael@0: flags |= RESOLVE_OFFLINE; michael@0: michael@0: const nsACString *hostPtr = &hostname; michael@0: michael@0: if (localDomain) { michael@0: hostPtr = &(NS_LITERAL_CSTRING("localhost")); michael@0: } michael@0: michael@0: nsresult rv; michael@0: nsAutoCString hostACE; michael@0: if (idn && !IsASCII(*hostPtr)) { michael@0: if (IsUTF8(*hostPtr) && michael@0: NS_SUCCEEDED(idn->ConvertUTF8toACE(*hostPtr, hostACE))) { michael@0: hostPtr = &hostACE; michael@0: } else { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: } michael@0: michael@0: // make sure JS callers get notification on the main thread michael@0: nsCOMPtr wrappedListener = do_QueryInterface(listener); michael@0: if (wrappedListener && !target) { michael@0: nsCOMPtr mainThread; michael@0: NS_GetMainThread(getter_AddRefs(mainThread)); michael@0: target = do_QueryInterface(mainThread); michael@0: } michael@0: michael@0: if (target) { michael@0: listener = new DNSListenerProxy(listener, target); michael@0: } michael@0: michael@0: uint16_t af = GetAFForLookup(*hostPtr, flags); michael@0: michael@0: nsDNSAsyncRequest *req = michael@0: new nsDNSAsyncRequest(res, *hostPtr, listener, flags, af); michael@0: if (!req) michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: NS_ADDREF(*result = req); michael@0: michael@0: MOZ_EVENT_TRACER_NAME_OBJECT(req, hostname.BeginReading()); michael@0: MOZ_EVENT_TRACER_WAIT(req, "net::dns::lookup"); michael@0: michael@0: // addref for resolver; will be released when OnLookupComplete is called. michael@0: NS_ADDREF(req); michael@0: rv = res->ResolveHost(req->mHost.get(), flags, af, req); michael@0: if (NS_FAILED(rv)) { michael@0: NS_RELEASE(req); michael@0: NS_RELEASE(*result); michael@0: } michael@0: return rv; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsDNSService::CancelAsyncResolve(const nsACString &aHostname, michael@0: uint32_t aFlags, michael@0: nsIDNSListener *aListener, michael@0: nsresult aReason) michael@0: { michael@0: // grab reference to global host resolver and IDN service. beware michael@0: // simultaneous shutdown!! michael@0: nsRefPtr res; michael@0: nsCOMPtr idn; michael@0: { michael@0: MutexAutoLock lock(mLock); michael@0: michael@0: if (mDisablePrefetch && (aFlags & RESOLVE_SPECULATE)) michael@0: return NS_ERROR_DNS_LOOKUP_QUEUE_FULL; michael@0: michael@0: res = mResolver; michael@0: idn = mIDN; michael@0: } michael@0: if (!res) michael@0: return NS_ERROR_OFFLINE; michael@0: michael@0: nsCString hostname(aHostname); michael@0: michael@0: nsAutoCString hostACE; michael@0: if (idn && !IsASCII(aHostname)) { michael@0: if (IsUTF8(aHostname) && michael@0: NS_SUCCEEDED(idn->ConvertUTF8toACE(aHostname, hostACE))) { michael@0: hostname = hostACE; michael@0: } else { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: } michael@0: michael@0: uint16_t af = GetAFForLookup(hostname, aFlags); michael@0: michael@0: res->CancelAsyncRequest(hostname.get(), aFlags, af, aListener, aReason); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsDNSService::Resolve(const nsACString &hostname, michael@0: uint32_t flags, michael@0: nsIDNSRecord **result) michael@0: { michael@0: // grab reference to global host resolver and IDN service. beware michael@0: // simultaneous shutdown!! michael@0: nsRefPtr res; michael@0: nsCOMPtr idn; michael@0: bool localDomain = false; michael@0: { michael@0: MutexAutoLock lock(mLock); michael@0: res = mResolver; michael@0: idn = mIDN; michael@0: localDomain = mLocalDomains.GetEntry(hostname); michael@0: } michael@0: michael@0: if (mNotifyResolution) { michael@0: NS_DispatchToMainThread(new NotifyDNSResolution(mObserverService, michael@0: hostname)); michael@0: } michael@0: michael@0: NS_ENSURE_TRUE(res, NS_ERROR_OFFLINE); michael@0: michael@0: if (mOffline) michael@0: flags |= RESOLVE_OFFLINE; michael@0: michael@0: PRNetAddr tempAddr; michael@0: if (mDisableDNS) { michael@0: // Allow IP lookups through, but nothing else. michael@0: if (PR_StringToNetAddr(hostname.BeginReading(), &tempAddr) != PR_SUCCESS) { michael@0: return NS_ERROR_UNKNOWN_PROXY_HOST; // XXX: NS_ERROR_NOT_IMPLEMENTED? michael@0: } michael@0: } michael@0: michael@0: const nsACString *hostPtr = &hostname; michael@0: michael@0: if (localDomain) { michael@0: hostPtr = &(NS_LITERAL_CSTRING("localhost")); michael@0: } michael@0: michael@0: nsresult rv; michael@0: nsAutoCString hostACE; michael@0: if (idn && !IsASCII(*hostPtr)) { michael@0: if (IsUTF8(*hostPtr) && michael@0: NS_SUCCEEDED(idn->ConvertUTF8toACE(*hostPtr, hostACE))) { michael@0: hostPtr = &hostACE; michael@0: } else { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: } michael@0: michael@0: // michael@0: // sync resolve: since the host resolver only works asynchronously, we need michael@0: // to use a mutex and a condvar to wait for the result. however, since the michael@0: // result may be in the resolvers cache, we might get called back recursively michael@0: // on the same thread. so, our mutex needs to be re-entrant. in other words, michael@0: // we need to use a monitor! ;-) michael@0: // michael@0: michael@0: PRMonitor *mon = PR_NewMonitor(); michael@0: if (!mon) michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: michael@0: PR_EnterMonitor(mon); michael@0: nsDNSSyncRequest syncReq(mon); michael@0: michael@0: uint16_t af = GetAFForLookup(*hostPtr, flags); michael@0: michael@0: rv = res->ResolveHost(PromiseFlatCString(*hostPtr).get(), flags, af, &syncReq); michael@0: if (NS_SUCCEEDED(rv)) { michael@0: // wait for result michael@0: while (!syncReq.mDone) michael@0: PR_Wait(mon, PR_INTERVAL_NO_TIMEOUT); michael@0: michael@0: if (NS_FAILED(syncReq.mStatus)) michael@0: rv = syncReq.mStatus; michael@0: else { michael@0: NS_ASSERTION(syncReq.mHostRecord, "no host record"); michael@0: nsDNSRecord *rec = new nsDNSRecord(syncReq.mHostRecord); michael@0: if (!rec) michael@0: rv = NS_ERROR_OUT_OF_MEMORY; michael@0: else michael@0: NS_ADDREF(*result = rec); michael@0: } michael@0: } michael@0: michael@0: PR_ExitMonitor(mon); michael@0: PR_DestroyMonitor(mon); michael@0: return rv; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsDNSService::GetMyHostName(nsACString &result) michael@0: { michael@0: char name[100]; michael@0: if (PR_GetSystemInfo(PR_SI_HOSTNAME, name, sizeof(name)) == PR_SUCCESS) { michael@0: result = name; michael@0: return NS_OK; michael@0: } michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsDNSService::Observe(nsISupports *subject, const char *topic, const char16_t *data) michael@0: { michael@0: // we are only getting called if a preference has changed. michael@0: NS_ASSERTION(strcmp(topic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID) == 0 || michael@0: strcmp(topic, "last-pb-context-exited") == 0, michael@0: "unexpected observe call"); michael@0: michael@0: // michael@0: // Shutdown and this function are both only called on the UI thread, so we don't michael@0: // have to worry about mResolver being cleared out from under us. michael@0: // michael@0: // NOTE Shutting down and reinitializing the service like this is obviously michael@0: // suboptimal if Observe gets called several times in a row, but we don't michael@0: // expect that to be the case. michael@0: // michael@0: michael@0: if (mResolver) { michael@0: Shutdown(); michael@0: } michael@0: Init(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: uint16_t michael@0: nsDNSService::GetAFForLookup(const nsACString &host, uint32_t flags) michael@0: { michael@0: if (mDisableIPv6 || (flags & RESOLVE_DISABLE_IPV6)) michael@0: return PR_AF_INET; michael@0: michael@0: MutexAutoLock lock(mLock); michael@0: michael@0: uint16_t af = PR_AF_UNSPEC; michael@0: michael@0: if (!mIPv4OnlyDomains.IsEmpty()) { michael@0: const char *domain, *domainEnd, *end; michael@0: uint32_t hostLen, domainLen; michael@0: michael@0: // see if host is in one of the IPv4-only domains michael@0: domain = mIPv4OnlyDomains.BeginReading(); michael@0: domainEnd = mIPv4OnlyDomains.EndReading(); michael@0: michael@0: nsACString::const_iterator hostStart; michael@0: host.BeginReading(hostStart); michael@0: hostLen = host.Length(); michael@0: michael@0: do { michael@0: // skip any whitespace michael@0: while (*domain == ' ' || *domain == '\t') michael@0: ++domain; michael@0: michael@0: // find end of this domain in the string michael@0: end = strchr(domain, ','); michael@0: if (!end) michael@0: end = domainEnd; michael@0: michael@0: // to see if the hostname is in the domain, check if the domain michael@0: // matches the end of the hostname. michael@0: domainLen = end - domain; michael@0: if (domainLen && hostLen >= domainLen) { michael@0: const char *hostTail = hostStart.get() + hostLen - domainLen; michael@0: if (PL_strncasecmp(domain, hostTail, domainLen) == 0) { michael@0: // now, make sure either that the hostname is a direct match or michael@0: // that the hostname begins with a dot. michael@0: if (hostLen == domainLen || michael@0: *hostTail == '.' || *(hostTail - 1) == '.') { michael@0: af = PR_AF_INET; michael@0: break; michael@0: } michael@0: } michael@0: } michael@0: michael@0: domain = end + 1; michael@0: } while (*end); michael@0: } michael@0: michael@0: if ((af != PR_AF_INET) && (flags & RESOLVE_DISABLE_IPV4)) michael@0: af = PR_AF_INET6; michael@0: michael@0: return af; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsDNSService::GetDNSCacheEntries(nsTArray *args) michael@0: { michael@0: NS_ENSURE_TRUE(mResolver, NS_ERROR_NOT_INITIALIZED); michael@0: mResolver->GetDNSCacheEntries(args); michael@0: return NS_OK; michael@0: } michael@0: michael@0: static size_t michael@0: SizeOfLocalDomainsEntryExcludingThis(nsCStringHashKey* entry, michael@0: MallocSizeOf mallocSizeOf, void*) michael@0: { michael@0: return entry->GetKey().SizeOfExcludingThisMustBeUnshared(mallocSizeOf); michael@0: } michael@0: michael@0: size_t michael@0: nsDNSService::SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const michael@0: { michael@0: // Measurement of the following members may be added later if DMD finds it michael@0: // is worthwhile: michael@0: // - mIDN michael@0: // - mLock michael@0: michael@0: size_t n = mallocSizeOf(this); michael@0: n += mResolver->SizeOfIncludingThis(mallocSizeOf); michael@0: n += mIPv4OnlyDomains.SizeOfExcludingThisMustBeUnshared(mallocSizeOf); michael@0: n += mLocalDomains.SizeOfExcludingThis(SizeOfLocalDomainsEntryExcludingThis, michael@0: mallocSizeOf); michael@0: return n; michael@0: } michael@0: michael@0: MOZ_DEFINE_MALLOC_SIZE_OF(DNSServiceMallocSizeOf) michael@0: michael@0: NS_IMETHODIMP michael@0: nsDNSService::CollectReports(nsIHandleReportCallback* aHandleReport, michael@0: nsISupports* aData) michael@0: { michael@0: return MOZ_COLLECT_REPORT( michael@0: "explicit/network/dns-service", KIND_HEAP, UNITS_BYTES, michael@0: SizeOfIncludingThis(DNSServiceMallocSizeOf), michael@0: "Memory used for the DNS service."); michael@0: } michael@0: