diff options
Diffstat (limited to 'netwerk/dns/nsHostResolver.cpp')
-rw-r--r-- | netwerk/dns/nsHostResolver.cpp | 1579 |
1 files changed, 1579 insertions, 0 deletions
diff --git a/netwerk/dns/nsHostResolver.cpp b/netwerk/dns/nsHostResolver.cpp new file mode 100644 index 000000000..f2e26cadd --- /dev/null +++ b/netwerk/dns/nsHostResolver.cpp @@ -0,0 +1,1579 @@ +/* vim:set ts=4 sw=4 sts=4 et cin: */ +/* 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/. */ + +#if defined(HAVE_RES_NINIT) +#include <sys/types.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <arpa/nameser.h> +#include <resolv.h> +#define RES_RETRY_ON_FAILURE +#endif + +#include <stdlib.h> +#include <ctime> +#include "nsHostResolver.h" +#include "nsError.h" +#include "nsISupportsBase.h" +#include "nsISupportsUtils.h" +#include "nsAutoPtr.h" +#include "nsPrintfCString.h" +#include "prthread.h" +#include "prerror.h" +#include "prtime.h" +#include "mozilla/Logging.h" +#include "PLDHashTable.h" +#include "plstr.h" +#include "nsURLHelper.h" +#include "nsThreadUtils.h" +#include "GetAddrInfo.h" + +#include "mozilla/HashFunctions.h" +#include "mozilla/TimeStamp.h" +#include "mozilla/Telemetry.h" +#include "mozilla/DebugOnly.h" +#include "mozilla/Preferences.h" + +using namespace mozilla; +using namespace mozilla::net; + +// None of our implementations expose a TTL for negative responses, so we use a +// constant always. +static const unsigned int NEGATIVE_RECORD_LIFETIME = 60; + +//---------------------------------------------------------------------------- + +// Use a persistent thread pool in order to avoid spinning up new threads all the time. +// In particular, thread creation results in a res_init() call from libc which is +// quite expensive. +// +// The pool dynamically grows between 0 and MAX_RESOLVER_THREADS in size. New requests +// go first to an idle thread. If that cannot be found and there are fewer than MAX_RESOLVER_THREADS +// currently in the pool a new thread is created for high priority requests. If +// the new request is at a lower priority a new thread will only be created if +// there are fewer than HighThreadThreshold currently outstanding. If a thread cannot be +// created or an idle thread located for the request it is queued. +// +// When the pool is greater than HighThreadThreshold in size a thread will be destroyed after +// ShortIdleTimeoutSeconds of idle time. Smaller pools use LongIdleTimeoutSeconds for a +// timeout period. + +#define HighThreadThreshold MAX_RESOLVER_THREADS_FOR_ANY_PRIORITY +#define LongIdleTimeoutSeconds 300 // for threads 1 -> HighThreadThreshold +#define ShortIdleTimeoutSeconds 60 // for threads HighThreadThreshold+1 -> MAX_RESOLVER_THREADS + +static_assert(HighThreadThreshold <= MAX_RESOLVER_THREADS, + "High Thread Threshold should be less equal Maximum allowed thread"); + +//---------------------------------------------------------------------------- + +static LazyLogModule gHostResolverLog("nsHostResolver"); +#define LOG(args) MOZ_LOG(gHostResolverLog, mozilla::LogLevel::Debug, args) +#define LOG_ENABLED() MOZ_LOG_TEST(gHostResolverLog, mozilla::LogLevel::Debug) + +#define LOG_HOST(host, interface) host, \ + (interface && interface[0] != '\0') ? " on interface " : "", \ + (interface && interface[0] != '\0') ? interface : "" + +//---------------------------------------------------------------------------- + +static inline void +MoveCList(PRCList &from, PRCList &to) +{ + if (!PR_CLIST_IS_EMPTY(&from)) { + to.next = from.next; + to.prev = from.prev; + to.next->prev = &to; + to.prev->next = &to; + PR_INIT_CLIST(&from); + } +} + +//---------------------------------------------------------------------------- + +#if defined(RES_RETRY_ON_FAILURE) + +// this class represents the resolver state for a given thread. if we +// encounter a lookup failure, then we can invoke the Reset method on an +// instance of this class to reset the resolver (in case /etc/resolv.conf +// for example changed). this is mainly an issue on GNU systems since glibc +// only reads in /etc/resolv.conf once per thread. it may be an issue on +// other systems as well. + +class nsResState +{ +public: + nsResState() + // initialize mLastReset to the time when this object + // is created. this means that a reset will not occur + // if a thread is too young. the alternative would be + // to initialize this to the beginning of time, so that + // the first failure would cause a reset, but since the + // thread would have just started up, it likely would + // already have current /etc/resolv.conf info. + : mLastReset(PR_IntervalNow()) + { + } + + bool Reset() + { + // reset no more than once per second + if (PR_IntervalToSeconds(PR_IntervalNow() - mLastReset) < 1) + return false; + + LOG(("Calling 'res_ninit'.\n")); + + mLastReset = PR_IntervalNow(); + return (res_ninit(&_res) == 0); + } + +private: + PRIntervalTime mLastReset; +}; + +#endif // RES_RETRY_ON_FAILURE + +//---------------------------------------------------------------------------- + +static inline bool +IsHighPriority(uint16_t flags) +{ + return !(flags & (nsHostResolver::RES_PRIORITY_LOW | nsHostResolver::RES_PRIORITY_MEDIUM)); +} + +static inline bool +IsMediumPriority(uint16_t flags) +{ + return flags & nsHostResolver::RES_PRIORITY_MEDIUM; +} + +static inline bool +IsLowPriority(uint16_t flags) +{ + return flags & nsHostResolver::RES_PRIORITY_LOW; +} + +//---------------------------------------------------------------------------- +// this macro filters out any flags that are not used when constructing the +// host key. the significant flags are those that would affect the resulting +// host record (i.e., the flags that are passed down to PR_GetAddrInfoByName). +#define RES_KEY_FLAGS(_f) ((_f) & nsHostResolver::RES_CANON_NAME) + +nsHostRecord::nsHostRecord(const nsHostKey *key) + : addr_info_lock("nsHostRecord.addr_info_lock") + , addr_info_gencnt(0) + , addr_info(nullptr) + , addr(nullptr) + , negative(false) + , resolving(false) + , onQueue(false) + , usingAnyThread(false) + , mDoomed(false) +#if TTL_AVAILABLE + , mGetTtl(false) +#endif + , mBlacklistedCount(0) + , mResolveAgain(false) +{ + host = ((char *) this) + sizeof(nsHostRecord); + memcpy((char *) host, key->host, strlen(key->host) + 1); + flags = key->flags; + af = key->af; + netInterface = host + strlen(key->host) + 1; + memcpy((char *) netInterface, key->netInterface, + strlen(key->netInterface) + 1); + PR_INIT_CLIST(this); + PR_INIT_CLIST(&callbacks); +} + +nsresult +nsHostRecord::Create(const nsHostKey *key, nsHostRecord **result) +{ + size_t hostLen = strlen(key->host) + 1; + size_t netInterfaceLen = strlen(key->netInterface) + 1; + size_t size = hostLen + netInterfaceLen + sizeof(nsHostRecord); + + // Use placement new to create the object with room for the hostname and + // network interface name allocated after it. + void *place = ::operator new(size); + *result = new(place) nsHostRecord(key); + NS_ADDREF(*result); + + return NS_OK; +} + +void +nsHostRecord::SetExpiration(const mozilla::TimeStamp& now, unsigned int valid, unsigned int grace) +{ + mValidStart = now; + mGraceStart = now + TimeDuration::FromSeconds(valid); + mValidEnd = now + TimeDuration::FromSeconds(valid + grace); +} + +void +nsHostRecord::CopyExpirationTimesAndFlagsFrom(const nsHostRecord *aFromHostRecord) +{ + // This is used to copy information from a cache entry to a record. All + // information necessary for HasUsableRecord needs to be copied. + mValidStart = aFromHostRecord->mValidStart; + mValidEnd = aFromHostRecord->mValidEnd; + mGraceStart = aFromHostRecord->mGraceStart; + mDoomed = aFromHostRecord->mDoomed; +} + +nsHostRecord::~nsHostRecord() +{ + Telemetry::Accumulate(Telemetry::DNS_BLACKLIST_COUNT, mBlacklistedCount); + delete addr_info; + delete addr; +} + +bool +nsHostRecord::Blacklisted(NetAddr *aQuery) +{ + // must call locked + LOG(("Checking blacklist for host [%s%s%s], host record [%p].\n", + LOG_HOST(host, netInterface), this)); + + // skip the string conversion for the common case of no blacklist + if (!mBlacklistedItems.Length()) { + return false; + } + + char buf[kIPv6CStrBufSize]; + if (!NetAddrToString(aQuery, buf, sizeof(buf))) { + return false; + } + nsDependentCString strQuery(buf); + + for (uint32_t i = 0; i < mBlacklistedItems.Length(); i++) { + if (mBlacklistedItems.ElementAt(i).Equals(strQuery)) { + LOG(("Address [%s] is blacklisted for host [%s%s%s].\n", buf, + LOG_HOST(host, netInterface))); + return true; + } + } + + return false; +} + +void +nsHostRecord::ReportUnusable(NetAddr *aAddress) +{ + // must call locked + LOG(("Adding address to blacklist for host [%s%s%s], host record [%p].\n", + LOG_HOST(host, netInterface), this)); + + ++mBlacklistedCount; + + if (negative) + mDoomed = true; + + char buf[kIPv6CStrBufSize]; + if (NetAddrToString(aAddress, buf, sizeof(buf))) { + LOG(("Successfully adding address [%s] to blacklist for host " + "[%s%s%s].\n", buf, LOG_HOST(host, netInterface))); + mBlacklistedItems.AppendElement(nsCString(buf)); + } +} + +void +nsHostRecord::ResetBlacklist() +{ + // must call locked + LOG(("Resetting blacklist for host [%s%s%s], host record [%p].\n", + LOG_HOST(host, netInterface), this)); + mBlacklistedItems.Clear(); +} + +nsHostRecord::ExpirationStatus +nsHostRecord::CheckExpiration(const mozilla::TimeStamp& now) const { + if (!mGraceStart.IsNull() && now >= mGraceStart + && !mValidEnd.IsNull() && now < mValidEnd) { + return nsHostRecord::EXP_GRACE; + } else if (!mValidEnd.IsNull() && now < mValidEnd) { + return nsHostRecord::EXP_VALID; + } + + return nsHostRecord::EXP_EXPIRED; +} + + +bool +nsHostRecord::HasUsableResult(const mozilla::TimeStamp& now, uint16_t queryFlags) const +{ + if (mDoomed) { + return false; + } + + // don't use cached negative results for high priority queries. + if (negative && IsHighPriority(queryFlags)) { + return false; + } + + if (CheckExpiration(now) == EXP_EXPIRED) { + return false; + } + + return addr_info || addr || negative; +} + +static size_t +SizeOfResolveHostCallbackListExcludingHead(const PRCList *head, + MallocSizeOf mallocSizeOf) +{ + size_t n = 0; + PRCList *curr = head->next; + while (curr != head) { + nsResolveHostCallback *callback = + static_cast<nsResolveHostCallback*>(curr); + n += callback->SizeOfIncludingThis(mallocSizeOf); + curr = curr->next; + } + return n; +} + +size_t +nsHostRecord::SizeOfIncludingThis(MallocSizeOf mallocSizeOf) const +{ + size_t n = mallocSizeOf(this); + + // The |host| field (inherited from nsHostKey) actually points to extra + // memory that is allocated beyond the end of the nsHostRecord (see + // nsHostRecord::Create()). So it will be included in the + // |mallocSizeOf(this)| call above. + + n += SizeOfResolveHostCallbackListExcludingHead(&callbacks, mallocSizeOf); + n += addr_info ? addr_info->SizeOfIncludingThis(mallocSizeOf) : 0; + n += mallocSizeOf(addr); + + n += mBlacklistedItems.ShallowSizeOfExcludingThis(mallocSizeOf); + for (size_t i = 0; i < mBlacklistedItems.Length(); i++) { + n += mBlacklistedItems[i].SizeOfExcludingThisIfUnshared(mallocSizeOf); + } + return n; +} + +nsHostRecord::DnsPriority +nsHostRecord::GetPriority(uint16_t aFlags) +{ + if (IsHighPriority(aFlags)){ + return nsHostRecord::DNS_PRIORITY_HIGH; + } else if (IsMediumPriority(aFlags)) { + return nsHostRecord::DNS_PRIORITY_MEDIUM; + } + + return nsHostRecord::DNS_PRIORITY_LOW; +} + +// Returns true if the entry can be removed, or false if it should be left. +// Sets mResolveAgain true for entries being resolved right now. +bool +nsHostRecord::RemoveOrRefresh() +{ + if (resolving) { + if (!onQueue) { + // The request has been passed to the OS resolver. The resultant DNS + // record should be considered stale and not trusted; set a flag to + // ensure it is called again. + mResolveAgain = true; + } + // if Onqueue is true, the host entry is already added to the cache + // but is still pending to get resolved: just leave it in hash. + return false; + } + // Already resolved; not in a pending state; remove from cache. + return true; +} + +//---------------------------------------------------------------------------- + +struct nsHostDBEnt : PLDHashEntryHdr +{ + nsHostRecord *rec; +}; + +static PLDHashNumber +HostDB_HashKey(const void *key) +{ + const nsHostKey *hk = static_cast<const nsHostKey *>(key); + return AddToHash(HashString(hk->host), RES_KEY_FLAGS(hk->flags), hk->af, + HashString(hk->netInterface)); +} + +static bool +HostDB_MatchEntry(const PLDHashEntryHdr *entry, + const void *key) +{ + const nsHostDBEnt *he = static_cast<const nsHostDBEnt *>(entry); + const nsHostKey *hk = static_cast<const nsHostKey *>(key); + + return !strcmp(he->rec->host ? he->rec->host : "", + hk->host ? hk->host : "") && + RES_KEY_FLAGS (he->rec->flags) == RES_KEY_FLAGS(hk->flags) && + he->rec->af == hk->af && + !strcmp(he->rec->netInterface, hk->netInterface); +} + +static void +HostDB_MoveEntry(PLDHashTable *table, + const PLDHashEntryHdr *from, + PLDHashEntryHdr *to) +{ + static_cast<nsHostDBEnt *>(to)->rec = + static_cast<const nsHostDBEnt *>(from)->rec; +} + +static void +HostDB_ClearEntry(PLDHashTable *table, + PLDHashEntryHdr *entry) +{ + nsHostDBEnt *he = static_cast<nsHostDBEnt*>(entry); + MOZ_ASSERT(he, "nsHostDBEnt is null!"); + + nsHostRecord *hr = he->rec; + MOZ_ASSERT(hr, "nsHostDBEnt has null host record!"); + + LOG(("Clearing cache db entry for host [%s%s%s].\n", + LOG_HOST(hr->host, hr->netInterface))); +#if defined(DEBUG) + { + MutexAutoLock lock(hr->addr_info_lock); + if (!hr->addr_info) { + LOG(("No address info for host [%s%s%s].\n", + LOG_HOST(hr->host, hr->netInterface))); + } else { + if (!hr->mValidEnd.IsNull()) { + TimeDuration diff = hr->mValidEnd - TimeStamp::NowLoRes(); + LOG(("Record for host [%s%s%s] expires in %f seconds.\n", + LOG_HOST(hr->host, hr->netInterface), + diff.ToSeconds())); + } else { + LOG(("Record for host [%s%s%s] not yet valid.\n", + LOG_HOST(hr->host, hr->netInterface))); + } + + NetAddrElement *addrElement = nullptr; + char buf[kIPv6CStrBufSize]; + do { + if (!addrElement) { + addrElement = hr->addr_info->mAddresses.getFirst(); + } else { + addrElement = addrElement->getNext(); + } + + if (addrElement) { + NetAddrToString(&addrElement->mAddress, buf, sizeof(buf)); + LOG((" [%s]\n", buf)); + } + } + while (addrElement); + } + } +#endif + NS_RELEASE(he->rec); +} + +static void +HostDB_InitEntry(PLDHashEntryHdr *entry, + const void *key) +{ + nsHostDBEnt *he = static_cast<nsHostDBEnt *>(entry); + nsHostRecord::Create(static_cast<const nsHostKey *>(key), &he->rec); +} + +static const PLDHashTableOps gHostDB_ops = +{ + HostDB_HashKey, + HostDB_MatchEntry, + HostDB_MoveEntry, + HostDB_ClearEntry, + HostDB_InitEntry, +}; + +//---------------------------------------------------------------------------- + +#if TTL_AVAILABLE +static const char kPrefGetTtl[] = "network.dns.get-ttl"; +static bool sGetTtlEnabled = false; + +static void DnsPrefChanged(const char* aPref, void* aClosure) +{ + MOZ_ASSERT(NS_IsMainThread(), + "Should be getting pref changed notification on main thread!"); + + if (strcmp(aPref, kPrefGetTtl) != 0) { + LOG(("DnsPrefChanged ignoring pref \"%s\"", aPref)); + return; + } + + auto self = static_cast<nsHostResolver*>(aClosure); + MOZ_ASSERT(self); + + sGetTtlEnabled = Preferences::GetBool(kPrefGetTtl); +} +#endif + +nsHostResolver::nsHostResolver(uint32_t maxCacheEntries, + uint32_t defaultCacheEntryLifetime, + uint32_t defaultGracePeriod) + : mMaxCacheEntries(maxCacheEntries) + , mDefaultCacheLifetime(defaultCacheEntryLifetime) + , mDefaultGracePeriod(defaultGracePeriod) + , mLock("nsHostResolver.mLock") + , mIdleThreadCV(mLock, "nsHostResolver.mIdleThreadCV") + , mDB(&gHostDB_ops, sizeof(nsHostDBEnt), 0) + , mEvictionQSize(0) + , mShutdown(true) + , mNumIdleThreads(0) + , mThreadCount(0) + , mActiveAnyThreadCount(0) + , mPendingCount(0) +{ + mCreationTime = PR_Now(); + PR_INIT_CLIST(&mHighQ); + PR_INIT_CLIST(&mMediumQ); + PR_INIT_CLIST(&mLowQ); + PR_INIT_CLIST(&mEvictionQ); + + mLongIdleTimeout = PR_SecondsToInterval(LongIdleTimeoutSeconds); + mShortIdleTimeout = PR_SecondsToInterval(ShortIdleTimeoutSeconds); +} + +nsHostResolver::~nsHostResolver() = default; + +nsresult +nsHostResolver::Init() +{ + if (NS_FAILED(GetAddrInfoInit())) { + return NS_ERROR_FAILURE; + } + + mShutdown = false; + +#if TTL_AVAILABLE + // The preferences probably haven't been loaded from the disk yet, so we + // need to register a callback that will set up the experiment once they + // are. We also need to explicitly set a value for the props otherwise the + // callback won't be called. + { + DebugOnly<nsresult> rv = Preferences::RegisterCallbackAndCall( + &DnsPrefChanged, kPrefGetTtl, this); + NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), + "Could not register DNS TTL pref callback."); + } +#endif + +#if defined(HAVE_RES_NINIT) + // We want to make sure the system is using the correct resolver settings, + // so we force it to reload those settings whenever we startup a subsequent + // nsHostResolver instance. We assume that there is no reason to do this + // for the first nsHostResolver instance since that is usually created + // during application startup. + static int initCount = 0; + if (initCount++ > 0) { + LOG(("Calling 'res_ninit'.\n")); + res_ninit(&_res); + } +#endif + return NS_OK; +} + +void +nsHostResolver::ClearPendingQueue(PRCList *aPendingQ) +{ + // loop through pending queue, erroring out pending lookups. + if (!PR_CLIST_IS_EMPTY(aPendingQ)) { + PRCList *node = aPendingQ->next; + while (node != aPendingQ) { + nsHostRecord *rec = static_cast<nsHostRecord *>(node); + node = node->next; + OnLookupComplete(rec, NS_ERROR_ABORT, nullptr); + } + } +} + +// +// FlushCache() is what we call when the network has changed. We must not +// trust names that were resolved before this change. They may resolve +// differently now. +// +// This function removes all existing resolved host entries from the hash. +// Names that are in the pending queues can be left there. Entries in the +// cache that have 'Resolve' set true but not 'onQueue' are being resolved +// right now, so we need to mark them to get re-resolved on completion! + +void +nsHostResolver::FlushCache() +{ + MutexAutoLock lock(mLock); + mEvictionQSize = 0; + + // Clear the evictionQ and remove all its corresponding entries from + // the cache first + if (!PR_CLIST_IS_EMPTY(&mEvictionQ)) { + PRCList *node = mEvictionQ.next; + while (node != &mEvictionQ) { + nsHostRecord *rec = static_cast<nsHostRecord *>(node); + node = node->next; + PR_REMOVE_AND_INIT_LINK(rec); + mDB.Remove((nsHostKey *) rec); + NS_RELEASE(rec); + } + } + + // Refresh the cache entries that are resolving RIGHT now, remove the rest. + for (auto iter = mDB.Iter(); !iter.Done(); iter.Next()) { + auto entry = static_cast<nsHostDBEnt *>(iter.Get()); + // Try to remove the record, or mark it for refresh. + if (entry->rec->RemoveOrRefresh()) { + PR_REMOVE_LINK(entry->rec); + iter.Remove(); + } + } +} + +void +nsHostResolver::Shutdown() +{ + LOG(("Shutting down host resolver.\n")); + +#if TTL_AVAILABLE + { + DebugOnly<nsresult> rv = Preferences::UnregisterCallback( + &DnsPrefChanged, kPrefGetTtl, this); + NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), + "Could not unregister DNS TTL pref callback."); + } +#endif + + PRCList pendingQHigh, pendingQMed, pendingQLow, evictionQ; + PR_INIT_CLIST(&pendingQHigh); + PR_INIT_CLIST(&pendingQMed); + PR_INIT_CLIST(&pendingQLow); + PR_INIT_CLIST(&evictionQ); + + { + MutexAutoLock lock(mLock); + + mShutdown = true; + + MoveCList(mHighQ, pendingQHigh); + MoveCList(mMediumQ, pendingQMed); + MoveCList(mLowQ, pendingQLow); + MoveCList(mEvictionQ, evictionQ); + mEvictionQSize = 0; + mPendingCount = 0; + + if (mNumIdleThreads) + mIdleThreadCV.NotifyAll(); + + // empty host database + mDB.Clear(); + } + + ClearPendingQueue(&pendingQHigh); + ClearPendingQueue(&pendingQMed); + ClearPendingQueue(&pendingQLow); + + if (!PR_CLIST_IS_EMPTY(&evictionQ)) { + PRCList *node = evictionQ.next; + while (node != &evictionQ) { + nsHostRecord *rec = static_cast<nsHostRecord *>(node); + node = node->next; + NS_RELEASE(rec); + } + } + +#ifdef NS_BUILD_REFCNT_LOGGING + + // Logically join the outstanding worker threads with a timeout. + // Use this approach instead of PR_JoinThread() because that does + // not allow a timeout which may be necessary for a semi-responsive + // shutdown if the thread is blocked on a very slow DNS resolution. + // mThreadCount is read outside of mLock, but the worst case + // scenario for that race is one extra 25ms sleep. + + PRIntervalTime delay = PR_MillisecondsToInterval(25); + PRIntervalTime stopTime = PR_IntervalNow() + PR_SecondsToInterval(20); + while (mThreadCount && PR_IntervalNow() < stopTime) + PR_Sleep(delay); +#endif + + { + mozilla::DebugOnly<nsresult> rv = GetAddrInfoShutdown(); + NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), + "Failed to shutdown GetAddrInfo"); + } +} + +void +nsHostResolver::MoveQueue(nsHostRecord *aRec, PRCList &aDestQ) +{ + NS_ASSERTION(aRec->onQueue, "Moving Host Record Not Currently Queued"); + + PR_REMOVE_LINK(aRec); + PR_APPEND_LINK(aRec, &aDestQ); +} + +nsresult +nsHostResolver::ResolveHost(const char *host, + uint16_t flags, + uint16_t af, + const char *netInterface, + nsResolveHostCallback *callback) +{ + NS_ENSURE_TRUE(host && *host, NS_ERROR_UNEXPECTED); + NS_ENSURE_TRUE(netInterface, NS_ERROR_UNEXPECTED); + + LOG(("Resolving host [%s%s%s]%s.\n", LOG_HOST(host, netInterface), + flags & RES_BYPASS_CACHE ? " - bypassing cache" : "")); + + // ensure that we are working with a valid hostname before proceeding. see + // bug 304904 for details. + if (!net_IsValidHostName(nsDependentCString(host))) + return NS_ERROR_UNKNOWN_HOST; + + // if result is set inside the lock, then we need to issue the + // callback before returning. + RefPtr<nsHostRecord> result; + nsresult status = NS_OK, rv = NS_OK; + { + MutexAutoLock lock(mLock); + + if (mShutdown) + rv = NS_ERROR_NOT_INITIALIZED; + else { + // Used to try to parse to an IP address literal. + PRNetAddr tempAddr; + // Unfortunately, PR_StringToNetAddr does not properly initialize + // the output buffer in the case of IPv6 input. See bug 223145. + memset(&tempAddr, 0, sizeof(PRNetAddr)); + + // check to see if there is already an entry for this |host| + // in the hash table. if so, then check to see if we can't + // just reuse the lookup result. otherwise, if there are + // any pending callbacks, then add to pending callbacks queue, + // and return. otherwise, add ourselves as first pending + // callback, and proceed to do the lookup. + + nsHostKey key = { host, flags, af, netInterface }; + auto he = static_cast<nsHostDBEnt*>(mDB.Add(&key, fallible)); + + // if the record is null, the hash table OOM'd. + if (!he) { + LOG((" Out of memory: no cache entry for host [%s%s%s].\n", + LOG_HOST(host, netInterface))); + rv = NS_ERROR_OUT_OF_MEMORY; + } + // do we have a cached result that we can reuse? + else if (!(flags & RES_BYPASS_CACHE) && + he->rec->HasUsableResult(TimeStamp::NowLoRes(), flags)) { + LOG((" Using cached record for host [%s%s%s].\n", + LOG_HOST(host, netInterface))); + // put reference to host record on stack... + result = he->rec; + Telemetry::Accumulate(Telemetry::DNS_LOOKUP_METHOD2, METHOD_HIT); + + // For entries that are in the grace period + // or all cached negative entries, use the cache but start a new + // lookup in the background + ConditionallyRefreshRecord(he->rec, host); + + if (he->rec->negative) { + LOG((" Negative cache entry for host [%s%s%s].\n", + LOG_HOST(host, netInterface))); + Telemetry::Accumulate(Telemetry::DNS_LOOKUP_METHOD2, + METHOD_NEGATIVE_HIT); + status = NS_ERROR_UNKNOWN_HOST; + } + } + // if the host name is an IP address literal and has been parsed, + // go ahead and use it. + else if (he->rec->addr) { + LOG((" Using cached address for IP Literal [%s].\n", host)); + Telemetry::Accumulate(Telemetry::DNS_LOOKUP_METHOD2, + METHOD_LITERAL); + result = he->rec; + } + // try parsing the host name as an IP address literal to short + // circuit full host resolution. (this is necessary on some + // platforms like Win9x. see bug 219376 for more details.) + else if (PR_StringToNetAddr(host, &tempAddr) == PR_SUCCESS) { + LOG((" Host is IP Literal [%s].\n", host)); + // ok, just copy the result into the host record, and be done + // with it! ;-) + he->rec->addr = new NetAddr(); + PRNetAddrToNetAddr(&tempAddr, he->rec->addr); + // put reference to host record on stack... + Telemetry::Accumulate(Telemetry::DNS_LOOKUP_METHOD2, + METHOD_LITERAL); + result = he->rec; + } + else if (mPendingCount >= MAX_NON_PRIORITY_REQUESTS && + !IsHighPriority(flags) && + !he->rec->resolving) { + LOG((" Lookup queue full: dropping %s priority request for " + "host [%s%s%s].\n", + IsMediumPriority(flags) ? "medium" : "low", + LOG_HOST(host, netInterface))); + Telemetry::Accumulate(Telemetry::DNS_LOOKUP_METHOD2, + METHOD_OVERFLOW); + // This is a lower priority request and we are swamped, so refuse it. + rv = NS_ERROR_DNS_LOOKUP_QUEUE_FULL; + } + else if (flags & RES_OFFLINE) { + LOG((" Offline request for host [%s%s%s]; ignoring.\n", + LOG_HOST(host, netInterface))); + rv = NS_ERROR_OFFLINE; + } + + // If this is an IPV4 or IPV6 specific request, check if there is + // an AF_UNSPEC entry we can use. Otherwise, hit the resolver... + else if (!he->rec->resolving) { + if (!(flags & RES_BYPASS_CACHE) && + ((af == PR_AF_INET) || (af == PR_AF_INET6))) { + // First, search for an entry with AF_UNSPEC + const nsHostKey unspecKey = { host, flags, PR_AF_UNSPEC, + netInterface }; + auto unspecHe = + static_cast<nsHostDBEnt*>(mDB.Search(&unspecKey)); + NS_ASSERTION(!unspecHe || + (unspecHe && unspecHe->rec), + "Valid host entries should contain a record"); + TimeStamp now = TimeStamp::NowLoRes(); + if (unspecHe && + unspecHe->rec->HasUsableResult(now, flags)) { + + MOZ_ASSERT(unspecHe->rec->addr_info || unspecHe->rec->negative, + "Entry should be resolved or negative."); + + LOG((" Trying AF_UNSPEC entry for host [%s%s%s] af: %s.\n", + LOG_HOST(host, netInterface), + (af == PR_AF_INET) ? "AF_INET" : "AF_INET6")); + + he->rec->addr_info = nullptr; + if (unspecHe->rec->negative) { + he->rec->negative = unspecHe->rec->negative; + he->rec->CopyExpirationTimesAndFlagsFrom(unspecHe->rec); + } else if (unspecHe->rec->addr_info) { + // Search for any valid address in the AF_UNSPEC entry + // in the cache (not blacklisted and from the right + // family). + NetAddrElement *addrIter = + unspecHe->rec->addr_info->mAddresses.getFirst(); + while (addrIter) { + if ((af == addrIter->mAddress.inet.family) && + !unspecHe->rec->Blacklisted(&addrIter->mAddress)) { + if (!he->rec->addr_info) { + he->rec->addr_info = new AddrInfo( + unspecHe->rec->addr_info->mHostName, + unspecHe->rec->addr_info->mCanonicalName); + he->rec->CopyExpirationTimesAndFlagsFrom(unspecHe->rec); + } + he->rec->addr_info->AddAddress( + new NetAddrElement(*addrIter)); + } + addrIter = addrIter->getNext(); + } + } + // Now check if we have a new record. + if (he->rec->HasUsableResult(now, flags)) { + result = he->rec; + if (he->rec->negative) { + status = NS_ERROR_UNKNOWN_HOST; + } + Telemetry::Accumulate(Telemetry::DNS_LOOKUP_METHOD2, + METHOD_HIT); + ConditionallyRefreshRecord(he->rec, host); + } + // For AF_INET6, a new lookup means another AF_UNSPEC + // lookup. We have already iterated through the + // AF_UNSPEC addresses, so we mark this record as + // negative. + else if (af == PR_AF_INET6) { + LOG((" No AF_INET6 in AF_UNSPEC entry: " + "host [%s%s%s] unknown host.", + LOG_HOST(host, netInterface))); + result = he->rec; + he->rec->negative = true; + status = NS_ERROR_UNKNOWN_HOST; + Telemetry::Accumulate(Telemetry::DNS_LOOKUP_METHOD2, + METHOD_NEGATIVE_HIT); + } + } + } + // If no valid address was found in the cache or this is an + // AF_UNSPEC request, then start a new lookup. + if (!result) { + LOG((" No usable address in cache for host [%s%s%s].", + LOG_HOST(host, netInterface))); + + // Add callback to the list of pending callbacks. + PR_APPEND_LINK(callback, &he->rec->callbacks); + he->rec->flags = flags; + rv = IssueLookup(he->rec); + Telemetry::Accumulate(Telemetry::DNS_LOOKUP_METHOD2, + METHOD_NETWORK_FIRST); + if (NS_FAILED(rv)) { + PR_REMOVE_AND_INIT_LINK(callback); + } + else { + LOG((" DNS lookup for host [%s%s%s] blocking " + "pending 'getaddrinfo' query: callback [%p]", + LOG_HOST(host, netInterface), callback)); + } + } + } + else { + LOG((" Host [%s%s%s] is being resolved. Appending callback " + "[%p].", LOG_HOST(host, netInterface), callback)); + + PR_APPEND_LINK(callback, &he->rec->callbacks); + if (he->rec->onQueue) { + Telemetry::Accumulate(Telemetry::DNS_LOOKUP_METHOD2, + METHOD_NETWORK_SHARED); + + // Consider the case where we are on a pending queue of + // lower priority than the request is being made at. + // In that case we should upgrade to the higher queue. + + if (IsHighPriority(flags) && + !IsHighPriority(he->rec->flags)) { + // Move from (low|med) to high. + MoveQueue(he->rec, mHighQ); + he->rec->flags = flags; + ConditionallyCreateThread(he->rec); + } else if (IsMediumPriority(flags) && + IsLowPriority(he->rec->flags)) { + // Move from low to med. + MoveQueue(he->rec, mMediumQ); + he->rec->flags = flags; + mIdleThreadCV.Notify(); + } + } + } + } + } + if (result) { + callback->OnLookupComplete(this, result, status); + } + + return rv; +} + +void +nsHostResolver::DetachCallback(const char *host, + uint16_t flags, + uint16_t af, + const char *netInterface, + nsResolveHostCallback *callback, + nsresult status) +{ + RefPtr<nsHostRecord> rec; + { + MutexAutoLock lock(mLock); + + nsHostKey key = { host, flags, af, netInterface }; + auto he = static_cast<nsHostDBEnt*>(mDB.Search(&key)); + if (he) { + // walk list looking for |callback|... we cannot assume + // that it will be there! + PRCList *node = he->rec->callbacks.next; + while (node != &he->rec->callbacks) { + if (static_cast<nsResolveHostCallback *>(node) == callback) { + PR_REMOVE_LINK(callback); + rec = he->rec; + break; + } + node = node->next; + } + } + } + + // complete callback with the given status code; this would only be done if + // the record was in the process of being resolved. + if (rec) + callback->OnLookupComplete(this, rec, status); +} + +nsresult +nsHostResolver::ConditionallyCreateThread(nsHostRecord *rec) +{ + if (mNumIdleThreads) { + // wake up idle thread to process this lookup + mIdleThreadCV.Notify(); + } + else if ((mThreadCount < HighThreadThreshold) || + (IsHighPriority(rec->flags) && mThreadCount < MAX_RESOLVER_THREADS)) { + // dispatch new worker thread + NS_ADDREF_THIS(); // owning reference passed to thread + + mThreadCount++; + PRThread *thr = PR_CreateThread(PR_SYSTEM_THREAD, + ThreadFunc, + this, + PR_PRIORITY_NORMAL, + PR_GLOBAL_THREAD, + PR_UNJOINABLE_THREAD, + 0); + if (!thr) { + mThreadCount--; + NS_RELEASE_THIS(); + return NS_ERROR_OUT_OF_MEMORY; + } + } + else { + LOG((" Unable to find a thread for looking up host [%s%s%s].\n", + LOG_HOST(rec->host, rec->netInterface))); + } + return NS_OK; +} + +nsresult +nsHostResolver::IssueLookup(nsHostRecord *rec) +{ + nsresult rv = NS_OK; + NS_ASSERTION(!rec->resolving, "record is already being resolved"); + + // Add rec to one of the pending queues, possibly removing it from mEvictionQ. + // If rec is on mEvictionQ, then we can just move the owning + // reference over to the new active queue. + if (rec->next == rec) + NS_ADDREF(rec); + else { + PR_REMOVE_LINK(rec); + mEvictionQSize--; + } + + switch (nsHostRecord::GetPriority(rec->flags)) { + case nsHostRecord::DNS_PRIORITY_HIGH: + PR_APPEND_LINK(rec, &mHighQ); + break; + + case nsHostRecord::DNS_PRIORITY_MEDIUM: + PR_APPEND_LINK(rec, &mMediumQ); + break; + + case nsHostRecord::DNS_PRIORITY_LOW: + PR_APPEND_LINK(rec, &mLowQ); + break; + } + mPendingCount++; + + rec->resolving = true; + rec->onQueue = true; + + rv = ConditionallyCreateThread(rec); + + LOG ((" DNS thread counters: total=%d any-live=%d idle=%d pending=%d\n", + static_cast<uint32_t>(mThreadCount), + static_cast<uint32_t>(mActiveAnyThreadCount), + static_cast<uint32_t>(mNumIdleThreads), + static_cast<uint32_t>(mPendingCount))); + + return rv; +} + +nsresult +nsHostResolver::ConditionallyRefreshRecord(nsHostRecord *rec, const char *host) +{ + if ((rec->CheckExpiration(TimeStamp::NowLoRes()) != nsHostRecord::EXP_VALID + || rec->negative) && !rec->resolving) { + LOG((" Using %s cache entry for host [%s] but starting async renewal.", + rec->negative ? "negative" :"positive", host)); + IssueLookup(rec); + + if (!rec->negative) { + // negative entries are constantly being refreshed, only + // track positive grace period induced renewals + Telemetry::Accumulate(Telemetry::DNS_LOOKUP_METHOD2, + METHOD_RENEWAL); + } + } + return NS_OK; +} + +void +nsHostResolver::DeQueue(PRCList &aQ, nsHostRecord **aResult) +{ + *aResult = static_cast<nsHostRecord *>(aQ.next); + PR_REMOVE_AND_INIT_LINK(*aResult); + mPendingCount--; + (*aResult)->onQueue = false; +} + +bool +nsHostResolver::GetHostToLookup(nsHostRecord **result) +{ + bool timedOut = false; + PRIntervalTime epoch, now, timeout; + + MutexAutoLock lock(mLock); + + timeout = (mNumIdleThreads >= HighThreadThreshold) ? mShortIdleTimeout : mLongIdleTimeout; + epoch = PR_IntervalNow(); + + while (!mShutdown) { + // remove next record from Q; hand over owning reference. Check high, then med, then low + +#if TTL_AVAILABLE + #define SET_GET_TTL(var, val) \ + (var)->mGetTtl = sGetTtlEnabled && (val) +#else + #define SET_GET_TTL(var, val) +#endif + + if (!PR_CLIST_IS_EMPTY(&mHighQ)) { + DeQueue (mHighQ, result); + SET_GET_TTL(*result, false); + return true; + } + + if (mActiveAnyThreadCount < HighThreadThreshold) { + if (!PR_CLIST_IS_EMPTY(&mMediumQ)) { + DeQueue (mMediumQ, result); + mActiveAnyThreadCount++; + (*result)->usingAnyThread = true; + SET_GET_TTL(*result, true); + return true; + } + + if (!PR_CLIST_IS_EMPTY(&mLowQ)) { + DeQueue (mLowQ, result); + mActiveAnyThreadCount++; + (*result)->usingAnyThread = true; + SET_GET_TTL(*result, true); + return true; + } + } + + // Determining timeout is racy, so allow one cycle through checking the queues + // before exiting. + if (timedOut) + break; + + // wait for one or more of the following to occur: + // (1) the pending queue has a host record to process + // (2) the shutdown flag has been set + // (3) the thread has been idle for too long + + mNumIdleThreads++; + mIdleThreadCV.Wait(timeout); + mNumIdleThreads--; + + now = PR_IntervalNow(); + + if ((PRIntervalTime)(now - epoch) >= timeout) + timedOut = true; + else { + // It is possible that PR_WaitCondVar() was interrupted and returned early, + // in which case we will loop back and re-enter it. In that case we want to + // do so with the new timeout reduced to reflect time already spent waiting. + timeout -= (PRIntervalTime)(now - epoch); + epoch = now; + } + } + + // tell thread to exit... + return false; +} + +void +nsHostResolver::PrepareRecordExpiration(nsHostRecord* rec) const +{ + MOZ_ASSERT(((bool)rec->addr_info) != rec->negative); + if (!rec->addr_info) { + rec->SetExpiration(TimeStamp::NowLoRes(), + NEGATIVE_RECORD_LIFETIME, 0); + LOG(("Caching host [%s%s%s] negative record for %u seconds.\n", + LOG_HOST(rec->host, rec->netInterface), + NEGATIVE_RECORD_LIFETIME)); + return; + } + + unsigned int lifetime = mDefaultCacheLifetime; + unsigned int grace = mDefaultGracePeriod; +#if TTL_AVAILABLE + unsigned int ttl = mDefaultCacheLifetime; + if (sGetTtlEnabled) { + MutexAutoLock lock(rec->addr_info_lock); + if (rec->addr_info && rec->addr_info->ttl != AddrInfo::NO_TTL_DATA) { + ttl = rec->addr_info->ttl; + } + lifetime = ttl; + grace = 0; + } +#endif + + rec->SetExpiration(TimeStamp::NowLoRes(), lifetime, grace); + LOG(("Caching host [%s%s%s] record for %u seconds (grace %d).", + LOG_HOST(rec->host, rec->netInterface), lifetime, grace)); +} + +static bool +different_rrset(AddrInfo *rrset1, AddrInfo *rrset2) +{ + if (!rrset1 || !rrset2) { + return true; + } + + LOG(("different_rrset %s\n", rrset1->mHostName)); + nsTArray<NetAddr> orderedSet1; + nsTArray<NetAddr> orderedSet2; + + for (NetAddrElement *element = rrset1->mAddresses.getFirst(); + element; element = element->getNext()) { + if (LOG_ENABLED()) { + char buf[128]; + NetAddrToString(&element->mAddress, buf, 128); + LOG(("different_rrset add to set 1 %s\n", buf)); + } + orderedSet1.InsertElementAt(orderedSet1.Length(), element->mAddress); + } + + for (NetAddrElement *element = rrset2->mAddresses.getFirst(); + element; element = element->getNext()) { + if (LOG_ENABLED()) { + char buf[128]; + NetAddrToString(&element->mAddress, buf, 128); + LOG(("different_rrset add to set 2 %s\n", buf)); + } + orderedSet2.InsertElementAt(orderedSet2.Length(), element->mAddress); + } + + if (orderedSet1.Length() != orderedSet2.Length()) { + LOG(("different_rrset true due to length change\n")); + return true; + } + orderedSet1.Sort(); + orderedSet2.Sort(); + + for (uint32_t i = 0; i < orderedSet1.Length(); ++i) { + if (!(orderedSet1[i] == orderedSet2[i])) { + LOG(("different_rrset true due to content change\n")); + return true; + } + } + LOG(("different_rrset false\n")); + return false; +} + +// +// OnLookupComplete() checks if the resolving should be redone and if so it +// returns LOOKUP_RESOLVEAGAIN, but only if 'status' is not NS_ERROR_ABORT. +// takes ownership of AddrInfo parameter +nsHostResolver::LookupStatus +nsHostResolver::OnLookupComplete(nsHostRecord* rec, nsresult status, AddrInfo* newRRSet) +{ + // get the list of pending callbacks for this lookup, and notify + // them that the lookup is complete. + PRCList cbs; + PR_INIT_CLIST(&cbs); + { + MutexAutoLock lock(mLock); + + if (rec->mResolveAgain && (status != NS_ERROR_ABORT)) { + LOG(("nsHostResolver record %p resolve again due to flushcache\n", rec)); + rec->mResolveAgain = false; + delete newRRSet; + return LOOKUP_RESOLVEAGAIN; + } + + // grab list of callbacks to notify + MoveCList(rec->callbacks, cbs); + + // update record fields. We might have a rec->addr_info already if a + // previous lookup result expired and we're reresolving it.. + AddrInfo *old_addr_info; + { + MutexAutoLock lock(rec->addr_info_lock); + if (different_rrset(rec->addr_info, newRRSet)) { + LOG(("nsHostResolver record %p new gencnt\n", rec)); + old_addr_info = rec->addr_info; + rec->addr_info = newRRSet; + rec->addr_info_gencnt++; + } else { + if (rec->addr_info && newRRSet) { + rec->addr_info->ttl = newRRSet->ttl; + } + old_addr_info = newRRSet; + } + } + delete old_addr_info; + + rec->negative = !rec->addr_info; + PrepareRecordExpiration(rec); + rec->resolving = false; + + if (rec->usingAnyThread) { + mActiveAnyThreadCount--; + rec->usingAnyThread = false; + } + + if (!mShutdown) { + // add to mEvictionQ + PR_APPEND_LINK(rec, &mEvictionQ); + NS_ADDREF(rec); + if (mEvictionQSize < mMaxCacheEntries) + mEvictionQSize++; + else { + // remove first element on mEvictionQ + nsHostRecord *head = + static_cast<nsHostRecord *>(PR_LIST_HEAD(&mEvictionQ)); + PR_REMOVE_AND_INIT_LINK(head); + mDB.Remove((nsHostKey *) head); + + if (!head->negative) { + // record the age of the entry upon eviction. + TimeDuration age = TimeStamp::NowLoRes() - head->mValidStart; + Telemetry::Accumulate(Telemetry::DNS_CLEANUP_AGE, + static_cast<uint32_t>(age.ToSeconds() / 60)); + } + + // release reference to rec owned by mEvictionQ + NS_RELEASE(head); + } +#if TTL_AVAILABLE + if (!rec->mGetTtl && !rec->resolving && sGetTtlEnabled) { + LOG(("Issuing second async lookup for TTL for host [%s%s%s].", + LOG_HOST(rec->host, rec->netInterface))); + rec->flags = + (rec->flags & ~RES_PRIORITY_MEDIUM) | RES_PRIORITY_LOW; + DebugOnly<nsresult> rv = IssueLookup(rec); + NS_WARNING_ASSERTION( + NS_SUCCEEDED(rv), + "Could not issue second async lookup for TTL."); + } +#endif + } + } + + if (!PR_CLIST_IS_EMPTY(&cbs)) { + PRCList *node = cbs.next; + while (node != &cbs) { + nsResolveHostCallback *callback = + static_cast<nsResolveHostCallback *>(node); + node = node->next; + callback->OnLookupComplete(this, rec, status); + // NOTE: callback must not be dereferenced after this point!! + } + } + + NS_RELEASE(rec); + + return LOOKUP_OK; +} + +void +nsHostResolver::CancelAsyncRequest(const char *host, + uint16_t flags, + uint16_t af, + const char *netInterface, + nsIDNSListener *aListener, + nsresult status) + +{ + MutexAutoLock lock(mLock); + + // Lookup the host record associated with host, flags & address family + nsHostKey key = { host, flags, af, netInterface }; + auto he = static_cast<nsHostDBEnt*>(mDB.Search(&key)); + if (he) { + nsHostRecord* recPtr = nullptr; + PRCList *node = he->rec->callbacks.next; + // Remove the first nsDNSAsyncRequest callback which matches the + // supplied listener object + while (node != &he->rec->callbacks) { + nsResolveHostCallback *callback + = static_cast<nsResolveHostCallback *>(node); + if (callback && (callback->EqualsAsyncListener(aListener))) { + // Remove from the list of callbacks + PR_REMOVE_LINK(callback); + recPtr = he->rec; + callback->OnLookupComplete(this, recPtr, status); + break; + } + node = node->next; + } + + // If there are no more callbacks, remove the hash table entry + if (recPtr && PR_CLIST_IS_EMPTY(&recPtr->callbacks)) { + mDB.Remove((nsHostKey *)recPtr); + // If record is on a Queue, remove it and then deref it + if (recPtr->next != recPtr) { + PR_REMOVE_LINK(recPtr); + NS_RELEASE(recPtr); + } + } + } +} + +size_t +nsHostResolver::SizeOfIncludingThis(MallocSizeOf mallocSizeOf) const +{ + MutexAutoLock lock(mLock); + + size_t n = mallocSizeOf(this); + + n += mDB.ShallowSizeOfExcludingThis(mallocSizeOf); + for (auto iter = mDB.ConstIter(); !iter.Done(); iter.Next()) { + auto entry = static_cast<nsHostDBEnt*>(iter.Get()); + n += entry->rec->SizeOfIncludingThis(mallocSizeOf); + } + + // The following fields aren't measured. + // - mHighQ, mMediumQ, mLowQ, mEvictionQ, because they just point to + // nsHostRecords that also pointed to by entries |mDB|, and measured when + // |mDB| is measured. + + return n; +} + +void +nsHostResolver::ThreadFunc(void *arg) +{ + LOG(("DNS lookup thread - starting execution.\n")); + + static nsThreadPoolNaming naming; + naming.SetThreadPoolName(NS_LITERAL_CSTRING("DNS Resolver")); + +#if defined(RES_RETRY_ON_FAILURE) + nsResState rs; +#endif + nsHostResolver *resolver = (nsHostResolver *)arg; + nsHostRecord *rec = nullptr; + AddrInfo *ai = nullptr; + + while (rec || resolver->GetHostToLookup(&rec)) { + LOG(("DNS lookup thread - Calling getaddrinfo for host [%s%s%s].\n", + LOG_HOST(rec->host, rec->netInterface))); + + TimeStamp startTime = TimeStamp::Now(); +#if TTL_AVAILABLE + bool getTtl = rec->mGetTtl; +#else + bool getTtl = false; +#endif + + nsresult status = GetAddrInfo(rec->host, rec->af, rec->flags, rec->netInterface, + &ai, getTtl); +#if defined(RES_RETRY_ON_FAILURE) + if (NS_FAILED(status) && rs.Reset()) { + status = GetAddrInfo(rec->host, rec->af, rec->flags, rec->netInterface, &ai, + getTtl); + } +#endif + + { // obtain lock to check shutdown and manage inter-module telemetry + MutexAutoLock lock(resolver->mLock); + + if (!resolver->mShutdown) { + TimeDuration elapsed = TimeStamp::Now() - startTime; + uint32_t millis = static_cast<uint32_t>(elapsed.ToMilliseconds()); + + if (NS_SUCCEEDED(status)) { + Telemetry::ID histogramID; + if (!rec->addr_info_gencnt) { + // Time for initial lookup. + histogramID = Telemetry::DNS_LOOKUP_TIME; + } else if (!getTtl) { + // Time for renewal; categorized by expiration strategy. + histogramID = Telemetry::DNS_RENEWAL_TIME; + } else { + // Time to get TTL; categorized by expiration strategy. + histogramID = Telemetry::DNS_RENEWAL_TIME_FOR_TTL; + } + Telemetry::Accumulate(histogramID, millis); + } else { + Telemetry::Accumulate(Telemetry::DNS_FAILED_LOOKUP_TIME, millis); + } + } + } + + // OnLookupComplete may release "rec", long before we lose it. + LOG(("DNS lookup thread - lookup completed for host [%s%s%s]: %s.\n", + LOG_HOST(rec->host, rec->netInterface), + ai ? "success" : "failure: unknown host")); + + if (LOOKUP_RESOLVEAGAIN == resolver->OnLookupComplete(rec, status, ai)) { + // leave 'rec' assigned and loop to make a renewed host resolve + LOG(("DNS lookup thread - Re-resolving host [%s%s%s].\n", + LOG_HOST(rec->host, rec->netInterface))); + } else { + rec = nullptr; + } + } + resolver->mThreadCount--; + NS_RELEASE(resolver); + LOG(("DNS lookup thread - queue empty, thread finished.\n")); +} + +nsresult +nsHostResolver::Create(uint32_t maxCacheEntries, + uint32_t defaultCacheEntryLifetime, + uint32_t defaultGracePeriod, + nsHostResolver **result) +{ + auto *res = new nsHostResolver(maxCacheEntries, defaultCacheEntryLifetime, + defaultGracePeriod); + NS_ADDREF(res); + + nsresult rv = res->Init(); + if (NS_FAILED(rv)) + NS_RELEASE(res); + + *result = res; + return rv; +} + +void +nsHostResolver::GetDNSCacheEntries(nsTArray<DNSCacheEntries> *args) +{ + for (auto iter = mDB.Iter(); !iter.Done(); iter.Next()) { + // We don't pay attention to address literals, only resolved domains. + // Also require a host. + auto entry = static_cast<nsHostDBEnt*>(iter.Get()); + nsHostRecord* rec = entry->rec; + MOZ_ASSERT(rec, "rec should never be null here!"); + if (!rec || !rec->addr_info || !rec->host) { + continue; + } + + DNSCacheEntries info; + info.hostname = rec->host; + info.family = rec->af; + info.netInterface = rec->netInterface; + info.expiration = + (int64_t)(rec->mValidEnd - TimeStamp::NowLoRes()).ToSeconds(); + if (info.expiration <= 0) { + // We only need valid DNS cache entries + continue; + } + + { + MutexAutoLock lock(rec->addr_info_lock); + + NetAddr *addr = nullptr; + NetAddrElement *addrElement = rec->addr_info->mAddresses.getFirst(); + if (addrElement) { + addr = &addrElement->mAddress; + } + while (addr) { + char buf[kIPv6CStrBufSize]; + if (NetAddrToString(addr, buf, sizeof(buf))) { + info.hostaddr.AppendElement(buf); + } + addr = nullptr; + addrElement = addrElement->getNext(); + if (addrElement) { + addr = &addrElement->mAddress; + } + } + } + + args->AppendElement(info); + } +} |