michael@0: /* vim:set ts=4 sw=4 sts=4 et cin: */ 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: #if defined(MOZ_LOGGING) michael@0: #define FORCE_PR_LOG michael@0: #endif michael@0: michael@0: #if defined(HAVE_RES_NINIT) michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: #define RES_RETRY_ON_FAILURE michael@0: #endif michael@0: michael@0: #include michael@0: #include "nsHostResolver.h" michael@0: #include "nsError.h" michael@0: #include "nsISupportsBase.h" michael@0: #include "nsISupportsUtils.h" michael@0: #include "nsAutoPtr.h" michael@0: #include "prthread.h" michael@0: #include "prerror.h" michael@0: #include "prtime.h" michael@0: #include "prlog.h" michael@0: #include "pldhash.h" michael@0: #include "plstr.h" michael@0: #include "nsURLHelper.h" michael@0: #include "nsThreadUtils.h" michael@0: michael@0: #include "mozilla/HashFunctions.h" michael@0: #include "mozilla/TimeStamp.h" michael@0: #include "mozilla/Telemetry.h" michael@0: #include "mozilla/VisualEventTracer.h" michael@0: michael@0: using namespace mozilla; michael@0: using namespace mozilla::net; michael@0: michael@0: //---------------------------------------------------------------------------- michael@0: michael@0: // Use a persistent thread pool in order to avoid spinning up new threads all the time. michael@0: // In particular, thread creation results in a res_init() call from libc which is michael@0: // quite expensive. michael@0: // michael@0: // The pool dynamically grows between 0 and MAX_RESOLVER_THREADS in size. New requests michael@0: // go first to an idle thread. If that cannot be found and there are fewer than MAX_RESOLVER_THREADS michael@0: // currently in the pool a new thread is created for high priority requests. If michael@0: // the new request is at a lower priority a new thread will only be created if michael@0: // there are fewer than HighThreadThreshold currently outstanding. If a thread cannot be michael@0: // created or an idle thread located for the request it is queued. michael@0: // michael@0: // When the pool is greater than HighThreadThreshold in size a thread will be destroyed after michael@0: // ShortIdleTimeoutSeconds of idle time. Smaller pools use LongIdleTimeoutSeconds for a michael@0: // timeout period. michael@0: michael@0: #define HighThreadThreshold MAX_RESOLVER_THREADS_FOR_ANY_PRIORITY michael@0: #define LongIdleTimeoutSeconds 300 // for threads 1 -> HighThreadThreshold michael@0: #define ShortIdleTimeoutSeconds 60 // for threads HighThreadThreshold+1 -> MAX_RESOLVER_THREADS michael@0: michael@0: PR_STATIC_ASSERT (HighThreadThreshold <= MAX_RESOLVER_THREADS); michael@0: michael@0: //---------------------------------------------------------------------------- michael@0: michael@0: #if defined(PR_LOGGING) michael@0: static PRLogModuleInfo *gHostResolverLog = nullptr; michael@0: #define LOG(args) PR_LOG(gHostResolverLog, PR_LOG_DEBUG, args) michael@0: #else michael@0: #define LOG(args) michael@0: #endif michael@0: michael@0: //---------------------------------------------------------------------------- michael@0: michael@0: static inline void michael@0: MoveCList(PRCList &from, PRCList &to) michael@0: { michael@0: if (!PR_CLIST_IS_EMPTY(&from)) { michael@0: to.next = from.next; michael@0: to.prev = from.prev; michael@0: to.next->prev = &to; michael@0: to.prev->next = &to; michael@0: PR_INIT_CLIST(&from); michael@0: } michael@0: } michael@0: michael@0: //---------------------------------------------------------------------------- michael@0: michael@0: #if defined(RES_RETRY_ON_FAILURE) michael@0: michael@0: // this class represents the resolver state for a given thread. if we michael@0: // encounter a lookup failure, then we can invoke the Reset method on an michael@0: // instance of this class to reset the resolver (in case /etc/resolv.conf michael@0: // for example changed). this is mainly an issue on GNU systems since glibc michael@0: // only reads in /etc/resolv.conf once per thread. it may be an issue on michael@0: // other systems as well. michael@0: michael@0: class nsResState michael@0: { michael@0: public: michael@0: nsResState() michael@0: // initialize mLastReset to the time when this object michael@0: // is created. this means that a reset will not occur michael@0: // if a thread is too young. the alternative would be michael@0: // to initialize this to the beginning of time, so that michael@0: // the first failure would cause a reset, but since the michael@0: // thread would have just started up, it likely would michael@0: // already have current /etc/resolv.conf info. michael@0: : mLastReset(PR_IntervalNow()) michael@0: { michael@0: } michael@0: michael@0: bool Reset() michael@0: { michael@0: // reset no more than once per second michael@0: if (PR_IntervalToSeconds(PR_IntervalNow() - mLastReset) < 1) michael@0: return false; michael@0: michael@0: LOG(("Calling 'res_ninit'.\n")); michael@0: michael@0: mLastReset = PR_IntervalNow(); michael@0: return (res_ninit(&_res) == 0); michael@0: } michael@0: michael@0: private: michael@0: PRIntervalTime mLastReset; michael@0: }; michael@0: michael@0: #endif // RES_RETRY_ON_FAILURE michael@0: michael@0: //---------------------------------------------------------------------------- michael@0: michael@0: static inline bool michael@0: IsHighPriority(uint16_t flags) michael@0: { michael@0: return !(flags & (nsHostResolver::RES_PRIORITY_LOW | nsHostResolver::RES_PRIORITY_MEDIUM)); michael@0: } michael@0: michael@0: static inline bool michael@0: IsMediumPriority(uint16_t flags) michael@0: { michael@0: return flags & nsHostResolver::RES_PRIORITY_MEDIUM; michael@0: } michael@0: michael@0: static inline bool michael@0: IsLowPriority(uint16_t flags) michael@0: { michael@0: return flags & nsHostResolver::RES_PRIORITY_LOW; michael@0: } michael@0: michael@0: //---------------------------------------------------------------------------- michael@0: michael@0: // this macro filters out any flags that are not used when constructing the michael@0: // host key. the significant flags are those that would affect the resulting michael@0: // host record (i.e., the flags that are passed down to PR_GetAddrInfoByName). michael@0: #define RES_KEY_FLAGS(_f) ((_f) & nsHostResolver::RES_CANON_NAME) michael@0: michael@0: nsHostRecord::nsHostRecord(const nsHostKey *key) michael@0: : addr_info_lock("nsHostRecord.addr_info_lock") michael@0: , addr_info_gencnt(0) michael@0: , addr_info(nullptr) michael@0: , addr(nullptr) michael@0: , negative(false) michael@0: , resolving(false) michael@0: , onQueue(false) michael@0: , usingAnyThread(false) michael@0: , mDoomed(false) michael@0: { michael@0: host = ((char *) this) + sizeof(nsHostRecord); michael@0: memcpy((char *) host, key->host, strlen(key->host) + 1); michael@0: flags = key->flags; michael@0: af = key->af; michael@0: michael@0: expiration = TimeStamp::NowLoRes(); michael@0: michael@0: PR_INIT_CLIST(this); michael@0: PR_INIT_CLIST(&callbacks); michael@0: } michael@0: michael@0: nsresult michael@0: nsHostRecord::Create(const nsHostKey *key, nsHostRecord **result) michael@0: { michael@0: size_t hostLen = strlen(key->host) + 1; michael@0: size_t size = hostLen + sizeof(nsHostRecord); michael@0: michael@0: // Use placement new to create the object with room for the hostname michael@0: // allocated after it. michael@0: void *place = ::operator new(size); michael@0: *result = new(place) nsHostRecord(key); michael@0: NS_ADDREF(*result); michael@0: michael@0: MOZ_EVENT_TRACER_NAME_OBJECT(*result, key->host); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsHostRecord::~nsHostRecord() michael@0: { michael@0: delete addr_info; michael@0: delete addr; michael@0: } michael@0: michael@0: bool michael@0: nsHostRecord::Blacklisted(NetAddr *aQuery) michael@0: { michael@0: // must call locked michael@0: LOG(("Checking blacklist for host [%s], host record [%p].\n", host, this)); michael@0: michael@0: // skip the string conversion for the common case of no blacklist michael@0: if (!mBlacklistedItems.Length()) { michael@0: return false; michael@0: } michael@0: michael@0: char buf[kIPv6CStrBufSize]; michael@0: if (!NetAddrToString(aQuery, buf, sizeof(buf))) { michael@0: return false; michael@0: } michael@0: nsDependentCString strQuery(buf); michael@0: michael@0: for (uint32_t i = 0; i < mBlacklistedItems.Length(); i++) { michael@0: if (mBlacklistedItems.ElementAt(i).Equals(strQuery)) { michael@0: LOG(("Address [%s] is blacklisted for host [%s].\n", buf, host)); michael@0: return true; michael@0: } michael@0: } michael@0: michael@0: return false; michael@0: } michael@0: michael@0: void michael@0: nsHostRecord::ReportUnusable(NetAddr *aAddress) michael@0: { michael@0: // must call locked michael@0: LOG(("Adding address to blacklist for host [%s], host record [%p].\n", host, this)); michael@0: michael@0: if (negative) michael@0: mDoomed = true; michael@0: michael@0: char buf[kIPv6CStrBufSize]; michael@0: if (NetAddrToString(aAddress, buf, sizeof(buf))) { michael@0: LOG(("Successfully adding address [%s] to blacklist for host [%s].\n", buf, host)); michael@0: mBlacklistedItems.AppendElement(nsCString(buf)); michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsHostRecord::ResetBlacklist() michael@0: { michael@0: // must call locked michael@0: LOG(("Resetting blacklist for host [%s], host record [%p].\n", host, this)); michael@0: mBlacklistedItems.Clear(); michael@0: } michael@0: michael@0: bool michael@0: nsHostRecord::HasUsableResult(uint16_t queryFlags) const michael@0: { michael@0: if (mDoomed) michael@0: return false; michael@0: michael@0: // don't use cached negative results for high priority queries. michael@0: if (negative && IsHighPriority(queryFlags)) michael@0: return false; michael@0: michael@0: return addr_info || addr || negative; michael@0: } michael@0: michael@0: static size_t michael@0: SizeOfResolveHostCallbackListExcludingHead(const PRCList *head, michael@0: MallocSizeOf mallocSizeOf) michael@0: { michael@0: size_t n = 0; michael@0: PRCList *curr = head->next; michael@0: while (curr != head) { michael@0: nsResolveHostCallback *callback = michael@0: static_cast(curr); michael@0: n += callback->SizeOfIncludingThis(mallocSizeOf); michael@0: curr = curr->next; michael@0: } michael@0: return n; michael@0: } michael@0: michael@0: size_t michael@0: nsHostRecord::SizeOfIncludingThis(MallocSizeOf mallocSizeOf) const michael@0: { michael@0: size_t n = mallocSizeOf(this); michael@0: michael@0: // The |host| field (inherited from nsHostKey) actually points to extra michael@0: // memory that is allocated beyond the end of the nsHostRecord (see michael@0: // nsHostRecord::Create()). So it will be included in the michael@0: // |mallocSizeOf(this)| call above. michael@0: michael@0: n += SizeOfResolveHostCallbackListExcludingHead(&callbacks, mallocSizeOf); michael@0: n += addr_info ? addr_info->SizeOfIncludingThis(mallocSizeOf) : 0; michael@0: n += mallocSizeOf(addr); michael@0: michael@0: n += mBlacklistedItems.SizeOfExcludingThis(mallocSizeOf); michael@0: for (size_t i = 0; i < mBlacklistedItems.Length(); i++) { michael@0: n += mBlacklistedItems[i].SizeOfExcludingThisMustBeUnshared(mallocSizeOf); michael@0: } michael@0: return n; michael@0: } michael@0: michael@0: //---------------------------------------------------------------------------- michael@0: michael@0: struct nsHostDBEnt : PLDHashEntryHdr michael@0: { michael@0: nsHostRecord *rec; michael@0: }; michael@0: michael@0: static PLDHashNumber michael@0: HostDB_HashKey(PLDHashTable *table, const void *key) michael@0: { michael@0: const nsHostKey *hk = static_cast(key); michael@0: return AddToHash(HashString(hk->host), RES_KEY_FLAGS(hk->flags), hk->af); michael@0: } michael@0: michael@0: static bool michael@0: HostDB_MatchEntry(PLDHashTable *table, michael@0: const PLDHashEntryHdr *entry, michael@0: const void *key) michael@0: { michael@0: const nsHostDBEnt *he = static_cast(entry); michael@0: const nsHostKey *hk = static_cast(key); michael@0: michael@0: return !strcmp(he->rec->host, hk->host) && michael@0: RES_KEY_FLAGS (he->rec->flags) == RES_KEY_FLAGS(hk->flags) && michael@0: he->rec->af == hk->af; michael@0: } michael@0: michael@0: static void michael@0: HostDB_MoveEntry(PLDHashTable *table, michael@0: const PLDHashEntryHdr *from, michael@0: PLDHashEntryHdr *to) michael@0: { michael@0: static_cast(to)->rec = michael@0: static_cast(from)->rec; michael@0: } michael@0: michael@0: static void michael@0: HostDB_ClearEntry(PLDHashTable *table, michael@0: PLDHashEntryHdr *entry) michael@0: { michael@0: nsHostDBEnt *he = static_cast(entry); michael@0: MOZ_ASSERT(he, "nsHostDBEnt is null!"); michael@0: michael@0: nsHostRecord *hr = he->rec; michael@0: MOZ_ASSERT(hr, "nsHostDBEnt has null host record!"); michael@0: michael@0: LOG(("Clearing cache db entry for host [%s].\n", hr->host)); michael@0: #if defined(DEBUG) && defined(PR_LOGGING) michael@0: { michael@0: MutexAutoLock lock(hr->addr_info_lock); michael@0: if (!hr->addr_info) { michael@0: LOG(("No address info for host [%s].\n", hr->host)); michael@0: } else { michael@0: TimeDuration diff = hr->expiration - TimeStamp::NowLoRes(); michael@0: LOG(("Record for [%s] expires in %f seconds.\n", hr->host, diff.ToSeconds())); michael@0: michael@0: NetAddrElement *addrElement = nullptr; michael@0: char buf[kIPv6CStrBufSize]; michael@0: do { michael@0: if (!addrElement) { michael@0: addrElement = hr->addr_info->mAddresses.getFirst(); michael@0: } else { michael@0: addrElement = addrElement->getNext(); michael@0: } michael@0: michael@0: if (addrElement) { michael@0: NetAddrToString(&addrElement->mAddress, buf, sizeof(buf)); michael@0: LOG((" [%s]\n", buf)); michael@0: } michael@0: } michael@0: while (addrElement); michael@0: } michael@0: } michael@0: #endif michael@0: NS_RELEASE(he->rec); michael@0: } michael@0: michael@0: static bool michael@0: HostDB_InitEntry(PLDHashTable *table, michael@0: PLDHashEntryHdr *entry, michael@0: const void *key) michael@0: { michael@0: nsHostDBEnt *he = static_cast(entry); michael@0: nsHostRecord::Create(static_cast(key), &he->rec); michael@0: return true; michael@0: } michael@0: michael@0: static const PLDHashTableOps gHostDB_ops = michael@0: { michael@0: PL_DHashAllocTable, michael@0: PL_DHashFreeTable, michael@0: HostDB_HashKey, michael@0: HostDB_MatchEntry, michael@0: HostDB_MoveEntry, michael@0: HostDB_ClearEntry, michael@0: PL_DHashFinalizeStub, michael@0: HostDB_InitEntry, michael@0: }; michael@0: michael@0: static PLDHashOperator michael@0: HostDB_RemoveEntry(PLDHashTable *table, michael@0: PLDHashEntryHdr *hdr, michael@0: uint32_t number, michael@0: void *arg) michael@0: { michael@0: return PL_DHASH_REMOVE; michael@0: } michael@0: michael@0: //---------------------------------------------------------------------------- michael@0: michael@0: nsHostResolver::nsHostResolver(uint32_t maxCacheEntries, michael@0: uint32_t maxCacheLifetime, michael@0: uint32_t lifetimeGracePeriod) michael@0: : mMaxCacheEntries(maxCacheEntries) michael@0: , mMaxCacheLifetime(TimeDuration::FromSeconds(maxCacheLifetime)) michael@0: , mGracePeriod(TimeDuration::FromSeconds(lifetimeGracePeriod)) michael@0: , mLock("nsHostResolver.mLock") michael@0: , mIdleThreadCV(mLock, "nsHostResolver.mIdleThreadCV") michael@0: , mNumIdleThreads(0) michael@0: , mThreadCount(0) michael@0: , mActiveAnyThreadCount(0) michael@0: , mEvictionQSize(0) michael@0: , mPendingCount(0) michael@0: , mShutdown(true) michael@0: { michael@0: mCreationTime = PR_Now(); michael@0: PR_INIT_CLIST(&mHighQ); michael@0: PR_INIT_CLIST(&mMediumQ); michael@0: PR_INIT_CLIST(&mLowQ); michael@0: PR_INIT_CLIST(&mEvictionQ); michael@0: michael@0: mLongIdleTimeout = PR_SecondsToInterval(LongIdleTimeoutSeconds); michael@0: mShortIdleTimeout = PR_SecondsToInterval(ShortIdleTimeoutSeconds); michael@0: } michael@0: michael@0: nsHostResolver::~nsHostResolver() michael@0: { michael@0: PL_DHashTableFinish(&mDB); michael@0: } michael@0: michael@0: nsresult michael@0: nsHostResolver::Init() michael@0: { michael@0: PL_DHashTableInit(&mDB, &gHostDB_ops, nullptr, sizeof(nsHostDBEnt), 0); michael@0: michael@0: mShutdown = false; michael@0: michael@0: #if defined(HAVE_RES_NINIT) michael@0: // We want to make sure the system is using the correct resolver settings, michael@0: // so we force it to reload those settings whenever we startup a subsequent michael@0: // nsHostResolver instance. We assume that there is no reason to do this michael@0: // for the first nsHostResolver instance since that is usually created michael@0: // during application startup. michael@0: static int initCount = 0; michael@0: if (initCount++ > 0) { michael@0: LOG(("Calling 'res_ninit'.\n")); michael@0: res_ninit(&_res); michael@0: } michael@0: #endif michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: nsHostResolver::ClearPendingQueue(PRCList *aPendingQ) michael@0: { michael@0: // loop through pending queue, erroring out pending lookups. michael@0: if (!PR_CLIST_IS_EMPTY(aPendingQ)) { michael@0: PRCList *node = aPendingQ->next; michael@0: while (node != aPendingQ) { michael@0: nsHostRecord *rec = static_cast(node); michael@0: node = node->next; michael@0: OnLookupComplete(rec, NS_ERROR_ABORT, nullptr); michael@0: } michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsHostResolver::Shutdown() michael@0: { michael@0: LOG(("Shutting down host resolver.\n")); michael@0: michael@0: PRCList pendingQHigh, pendingQMed, pendingQLow, evictionQ; michael@0: PR_INIT_CLIST(&pendingQHigh); michael@0: PR_INIT_CLIST(&pendingQMed); michael@0: PR_INIT_CLIST(&pendingQLow); michael@0: PR_INIT_CLIST(&evictionQ); michael@0: michael@0: { michael@0: MutexAutoLock lock(mLock); michael@0: michael@0: mShutdown = true; michael@0: michael@0: MoveCList(mHighQ, pendingQHigh); michael@0: MoveCList(mMediumQ, pendingQMed); michael@0: MoveCList(mLowQ, pendingQLow); michael@0: MoveCList(mEvictionQ, evictionQ); michael@0: mEvictionQSize = 0; michael@0: mPendingCount = 0; michael@0: michael@0: if (mNumIdleThreads) michael@0: mIdleThreadCV.NotifyAll(); michael@0: michael@0: // empty host database michael@0: PL_DHashTableEnumerate(&mDB, HostDB_RemoveEntry, nullptr); michael@0: } michael@0: michael@0: ClearPendingQueue(&pendingQHigh); michael@0: ClearPendingQueue(&pendingQMed); michael@0: ClearPendingQueue(&pendingQLow); michael@0: michael@0: if (!PR_CLIST_IS_EMPTY(&evictionQ)) { michael@0: PRCList *node = evictionQ.next; michael@0: while (node != &evictionQ) { michael@0: nsHostRecord *rec = static_cast(node); michael@0: node = node->next; michael@0: NS_RELEASE(rec); michael@0: } michael@0: } michael@0: michael@0: #ifdef NS_BUILD_REFCNT_LOGGING michael@0: michael@0: // Logically join the outstanding worker threads with a timeout. michael@0: // Use this approach instead of PR_JoinThread() because that does michael@0: // not allow a timeout which may be necessary for a semi-responsive michael@0: // shutdown if the thread is blocked on a very slow DNS resolution. michael@0: // mThreadCount is read outside of mLock, but the worst case michael@0: // scenario for that race is one extra 25ms sleep. michael@0: michael@0: PRIntervalTime delay = PR_MillisecondsToInterval(25); michael@0: PRIntervalTime stopTime = PR_IntervalNow() + PR_SecondsToInterval(20); michael@0: while (mThreadCount && PR_IntervalNow() < stopTime) michael@0: PR_Sleep(delay); michael@0: #endif michael@0: } michael@0: michael@0: void michael@0: nsHostResolver::MoveQueue(nsHostRecord *aRec, PRCList &aDestQ) michael@0: { michael@0: NS_ASSERTION(aRec->onQueue, "Moving Host Record Not Currently Queued"); michael@0: michael@0: PR_REMOVE_LINK(aRec); michael@0: PR_APPEND_LINK(aRec, &aDestQ); michael@0: } michael@0: michael@0: nsresult michael@0: nsHostResolver::ResolveHost(const char *host, michael@0: uint16_t flags, michael@0: uint16_t af, michael@0: nsResolveHostCallback *callback) michael@0: { michael@0: NS_ENSURE_TRUE(host && *host, NS_ERROR_UNEXPECTED); michael@0: michael@0: LOG(("Resolving host [%s]%s.\n", michael@0: host, flags & RES_BYPASS_CACHE ? " - bypassing cache" : "")); michael@0: michael@0: // ensure that we are working with a valid hostname before proceeding. see michael@0: // bug 304904 for details. michael@0: if (!net_IsValidHostName(nsDependentCString(host))) michael@0: return NS_ERROR_UNKNOWN_HOST; michael@0: michael@0: // if result is set inside the lock, then we need to issue the michael@0: // callback before returning. michael@0: nsRefPtr result; michael@0: nsresult status = NS_OK, rv = NS_OK; michael@0: { michael@0: MutexAutoLock lock(mLock); michael@0: michael@0: if (mShutdown) michael@0: rv = NS_ERROR_NOT_INITIALIZED; michael@0: else { michael@0: // Used to try to parse to an IP address literal. michael@0: PRNetAddr tempAddr; michael@0: // Unfortunately, PR_StringToNetAddr does not properly initialize michael@0: // the output buffer in the case of IPv6 input. See bug 223145. michael@0: memset(&tempAddr, 0, sizeof(PRNetAddr)); michael@0: michael@0: // check to see if there is already an entry for this |host| michael@0: // in the hash table. if so, then check to see if we can't michael@0: // just reuse the lookup result. otherwise, if there are michael@0: // any pending callbacks, then add to pending callbacks queue, michael@0: // and return. otherwise, add ourselves as first pending michael@0: // callback, and proceed to do the lookup. michael@0: michael@0: nsHostKey key = { host, flags, af }; michael@0: nsHostDBEnt *he = static_cast michael@0: (PL_DHashTableOperate(&mDB, &key, PL_DHASH_ADD)); michael@0: michael@0: // if the record is null, then HostDB_InitEntry failed. michael@0: if (!he || !he->rec) { michael@0: LOG((" Out of memory: no cache entry for [%s].\n", host)); michael@0: rv = NS_ERROR_OUT_OF_MEMORY; michael@0: } michael@0: // do we have a cached result that we can reuse? michael@0: else if (!(flags & RES_BYPASS_CACHE) && michael@0: he->rec->HasUsableResult(flags) && michael@0: TimeStamp::NowLoRes() <= (he->rec->expiration + mGracePeriod)) { michael@0: LOG((" Using cached record for host [%s].\n", host)); michael@0: // put reference to host record on stack... michael@0: result = he->rec; michael@0: Telemetry::Accumulate(Telemetry::DNS_LOOKUP_METHOD2, METHOD_HIT); michael@0: michael@0: // For entries that are in the grace period michael@0: // or all cached negative entries, use the cache but start a new michael@0: // lookup in the background michael@0: ConditionallyRefreshRecord(he->rec, host); michael@0: michael@0: if (he->rec->negative) { michael@0: LOG((" Negative cache entry for[%s].\n", host)); michael@0: Telemetry::Accumulate(Telemetry::DNS_LOOKUP_METHOD2, michael@0: METHOD_NEGATIVE_HIT); michael@0: status = NS_ERROR_UNKNOWN_HOST; michael@0: } michael@0: } michael@0: // if the host name is an IP address literal and has been parsed, michael@0: // go ahead and use it. michael@0: else if (he->rec->addr) { michael@0: LOG((" Using cached address for IP Literal [%s].\n", host)); michael@0: Telemetry::Accumulate(Telemetry::DNS_LOOKUP_METHOD2, michael@0: METHOD_LITERAL); michael@0: result = he->rec; michael@0: } michael@0: // try parsing the host name as an IP address literal to short michael@0: // circuit full host resolution. (this is necessary on some michael@0: // platforms like Win9x. see bug 219376 for more details.) michael@0: else if (PR_StringToNetAddr(host, &tempAddr) == PR_SUCCESS) { michael@0: LOG((" Host is IP Literal [%s].\n", host)); michael@0: // ok, just copy the result into the host record, and be done michael@0: // with it! ;-) michael@0: he->rec->addr = new NetAddr(); michael@0: PRNetAddrToNetAddr(&tempAddr, he->rec->addr); michael@0: // put reference to host record on stack... michael@0: Telemetry::Accumulate(Telemetry::DNS_LOOKUP_METHOD2, michael@0: METHOD_LITERAL); michael@0: result = he->rec; michael@0: } michael@0: else if (mPendingCount >= MAX_NON_PRIORITY_REQUESTS && michael@0: !IsHighPriority(flags) && michael@0: !he->rec->resolving) { michael@0: LOG((" Lookup queue full: dropping %s priority request for " michael@0: "[%s].\n", michael@0: IsMediumPriority(flags) ? "medium" : "low", host)); michael@0: Telemetry::Accumulate(Telemetry::DNS_LOOKUP_METHOD2, michael@0: METHOD_OVERFLOW); michael@0: // This is a lower priority request and we are swamped, so refuse it. michael@0: rv = NS_ERROR_DNS_LOOKUP_QUEUE_FULL; michael@0: } michael@0: else if (flags & RES_OFFLINE) { michael@0: LOG((" Offline request for [%s]; ignoring.\n", host)); michael@0: rv = NS_ERROR_OFFLINE; michael@0: } michael@0: michael@0: // If this is an IPV4 or IPV6 specific request, check if there is michael@0: // an AF_UNSPEC entry we can use. Otherwise, hit the resolver... michael@0: else if (!he->rec->resolving) { michael@0: if (!(flags & RES_BYPASS_CACHE) && michael@0: ((af == PR_AF_INET) || (af == PR_AF_INET6))) { michael@0: // First, search for an entry with AF_UNSPEC michael@0: const nsHostKey unspecKey = { host, flags, PR_AF_UNSPEC }; michael@0: nsHostDBEnt *unspecHe = static_cast michael@0: (PL_DHashTableOperate(&mDB, &unspecKey, PL_DHASH_LOOKUP)); michael@0: NS_ASSERTION(PL_DHASH_ENTRY_IS_FREE(unspecHe) || michael@0: (PL_DHASH_ENTRY_IS_BUSY(unspecHe) && michael@0: unspecHe->rec), michael@0: "Valid host entries should contain a record"); michael@0: if (PL_DHASH_ENTRY_IS_BUSY(unspecHe) && michael@0: unspecHe->rec && michael@0: unspecHe->rec->HasUsableResult(flags) && michael@0: TimeStamp::NowLoRes() <= (he->rec->expiration + mGracePeriod)) { michael@0: michael@0: MOZ_ASSERT(unspecHe->rec->addr_info || unspecHe->rec->negative, michael@0: "Entry should be resolved or negative."); michael@0: michael@0: LOG((" Trying AF_UNSPEC entry for [%s] af: %s.\n", michael@0: host, (af == PR_AF_INET) ? "AF_INET" : "AF_INET6")); michael@0: michael@0: he->rec->addr_info = nullptr; michael@0: if (unspecHe->rec->negative) { michael@0: he->rec->negative = unspecHe->rec->negative; michael@0: } else if (unspecHe->rec->addr_info) { michael@0: // Search for any valid address in the AF_UNSPEC entry michael@0: // in the cache (not blacklisted and from the right michael@0: // family). michael@0: NetAddrElement *addrIter = michael@0: unspecHe->rec->addr_info->mAddresses.getFirst(); michael@0: while (addrIter) { michael@0: if ((af == addrIter->mAddress.inet.family) && michael@0: !unspecHe->rec->Blacklisted(&addrIter->mAddress)) { michael@0: if (!he->rec->addr_info) { michael@0: he->rec->addr_info = new AddrInfo( michael@0: unspecHe->rec->addr_info->mHostName, michael@0: unspecHe->rec->addr_info->mCanonicalName); michael@0: } michael@0: he->rec->addr_info->AddAddress( michael@0: new NetAddrElement(*addrIter)); michael@0: } michael@0: addrIter = addrIter->getNext(); michael@0: } michael@0: } michael@0: if (he->rec->HasUsableResult(flags)) { michael@0: result = he->rec; michael@0: Telemetry::Accumulate(Telemetry::DNS_LOOKUP_METHOD2, michael@0: METHOD_HIT); michael@0: ConditionallyRefreshRecord(he->rec, host); michael@0: } michael@0: // For AF_INET6, a new lookup means another AF_UNSPEC michael@0: // lookup. We have already iterated through the michael@0: // AF_UNSPEC addresses, so we mark this record as michael@0: // negative. michael@0: else if (af == PR_AF_INET6) { michael@0: LOG((" No AF_INET6 in AF_UNSPEC entry: " michael@0: "[%s] unknown host", host)); michael@0: result = he->rec; michael@0: he->rec->negative = true; michael@0: status = NS_ERROR_UNKNOWN_HOST; michael@0: Telemetry::Accumulate(Telemetry::DNS_LOOKUP_METHOD2, michael@0: METHOD_NEGATIVE_HIT); michael@0: } michael@0: } michael@0: } michael@0: // If no valid address was found in the cache or this is an michael@0: // AF_UNSPEC request, then start a new lookup. michael@0: if (!result) { michael@0: LOG((" No usable address in cache for [%s]", host)); michael@0: // Add callback to the list of pending callbacks. michael@0: PR_APPEND_LINK(callback, &he->rec->callbacks); michael@0: he->rec->flags = flags; michael@0: rv = IssueLookup(he->rec); michael@0: Telemetry::Accumulate(Telemetry::DNS_LOOKUP_METHOD2, michael@0: METHOD_NETWORK_FIRST); michael@0: if (NS_FAILED(rv)) { michael@0: PR_REMOVE_AND_INIT_LINK(callback); michael@0: } michael@0: else { michael@0: LOG((" DNS lookup for host [%s] blocking pending " michael@0: "'getaddrinfo' query: callback [%p]", michael@0: host, callback)); michael@0: } michael@0: } michael@0: } michael@0: else { michael@0: LOG((" Host [%s] is being resolved. Appending callback [%p].", michael@0: host, callback)); michael@0: PR_APPEND_LINK(callback, &he->rec->callbacks); michael@0: if (he->rec->onQueue) { michael@0: Telemetry::Accumulate(Telemetry::DNS_LOOKUP_METHOD2, michael@0: METHOD_NETWORK_SHARED); michael@0: michael@0: // Consider the case where we are on a pending queue of michael@0: // lower priority than the request is being made at. michael@0: // In that case we should upgrade to the higher queue. michael@0: michael@0: if (IsHighPriority(flags) && michael@0: !IsHighPriority(he->rec->flags)) { michael@0: // Move from (low|med) to high. michael@0: MoveQueue(he->rec, mHighQ); michael@0: he->rec->flags = flags; michael@0: ConditionallyCreateThread(he->rec); michael@0: } else if (IsMediumPriority(flags) && michael@0: IsLowPriority(he->rec->flags)) { michael@0: // Move from low to med. michael@0: MoveQueue(he->rec, mMediumQ); michael@0: he->rec->flags = flags; michael@0: mIdleThreadCV.Notify(); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: } michael@0: if (result) michael@0: callback->OnLookupComplete(this, result, status); michael@0: return rv; michael@0: } michael@0: michael@0: void michael@0: nsHostResolver::DetachCallback(const char *host, michael@0: uint16_t flags, michael@0: uint16_t af, michael@0: nsResolveHostCallback *callback, michael@0: nsresult status) michael@0: { michael@0: nsRefPtr rec; michael@0: { michael@0: MutexAutoLock lock(mLock); michael@0: michael@0: nsHostKey key = { host, flags, af }; michael@0: nsHostDBEnt *he = static_cast michael@0: (PL_DHashTableOperate(&mDB, &key, PL_DHASH_LOOKUP)); michael@0: if (he && he->rec) { michael@0: // walk list looking for |callback|... we cannot assume michael@0: // that it will be there! michael@0: PRCList *node = he->rec->callbacks.next; michael@0: while (node != &he->rec->callbacks) { michael@0: if (static_cast(node) == callback) { michael@0: PR_REMOVE_LINK(callback); michael@0: rec = he->rec; michael@0: break; michael@0: } michael@0: node = node->next; michael@0: } michael@0: } michael@0: } michael@0: michael@0: // complete callback with the given status code; this would only be done if michael@0: // the record was in the process of being resolved. michael@0: if (rec) michael@0: callback->OnLookupComplete(this, rec, status); michael@0: } michael@0: michael@0: nsresult michael@0: nsHostResolver::ConditionallyCreateThread(nsHostRecord *rec) michael@0: { michael@0: if (mNumIdleThreads) { michael@0: // wake up idle thread to process this lookup michael@0: mIdleThreadCV.Notify(); michael@0: } michael@0: else if ((mThreadCount < HighThreadThreshold) || michael@0: (IsHighPriority(rec->flags) && mThreadCount < MAX_RESOLVER_THREADS)) { michael@0: // dispatch new worker thread michael@0: NS_ADDREF_THIS(); // owning reference passed to thread michael@0: michael@0: mThreadCount++; michael@0: PRThread *thr = PR_CreateThread(PR_SYSTEM_THREAD, michael@0: ThreadFunc, michael@0: this, michael@0: PR_PRIORITY_NORMAL, michael@0: PR_GLOBAL_THREAD, michael@0: PR_UNJOINABLE_THREAD, michael@0: 0); michael@0: if (!thr) { michael@0: mThreadCount--; michael@0: NS_RELEASE_THIS(); michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: } michael@0: } michael@0: #if defined(PR_LOGGING) michael@0: else michael@0: LOG((" Unable to find a thread for looking up host [%s].\n", rec->host)); michael@0: #endif michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsHostResolver::IssueLookup(nsHostRecord *rec) michael@0: { michael@0: MOZ_EVENT_TRACER_WAIT(rec, "net::dns::resolve"); michael@0: michael@0: nsresult rv = NS_OK; michael@0: NS_ASSERTION(!rec->resolving, "record is already being resolved"); michael@0: michael@0: // Add rec to one of the pending queues, possibly removing it from mEvictionQ. michael@0: // If rec is on mEvictionQ, then we can just move the owning michael@0: // reference over to the new active queue. michael@0: if (rec->next == rec) michael@0: NS_ADDREF(rec); michael@0: else { michael@0: PR_REMOVE_LINK(rec); michael@0: mEvictionQSize--; michael@0: } michael@0: michael@0: if (IsHighPriority(rec->flags)) michael@0: PR_APPEND_LINK(rec, &mHighQ); michael@0: else if (IsMediumPriority(rec->flags)) michael@0: PR_APPEND_LINK(rec, &mMediumQ); michael@0: else michael@0: PR_APPEND_LINK(rec, &mLowQ); michael@0: mPendingCount++; michael@0: michael@0: rec->resolving = true; michael@0: rec->onQueue = true; michael@0: michael@0: rv = ConditionallyCreateThread(rec); michael@0: michael@0: LOG ((" DNS thread counters: total=%d any-live=%d idle=%d pending=%d\n", michael@0: mThreadCount, michael@0: mActiveAnyThreadCount, michael@0: mNumIdleThreads, michael@0: mPendingCount)); michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: nsresult michael@0: nsHostResolver::ConditionallyRefreshRecord(nsHostRecord *rec, const char *host) michael@0: { michael@0: if (((TimeStamp::NowLoRes() > rec->expiration) || rec->negative) && michael@0: !rec->resolving) { michael@0: LOG((" Using %s cache entry for host [%s] but starting async renewal.", michael@0: rec->negative ? "negative" :"positive", host)); michael@0: IssueLookup(rec); michael@0: michael@0: if (!rec->negative) { michael@0: // negative entries are constantly being refreshed, only michael@0: // track positive grace period induced renewals michael@0: Telemetry::Accumulate(Telemetry::DNS_LOOKUP_METHOD2, michael@0: METHOD_RENEWAL); michael@0: } michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: nsHostResolver::DeQueue(PRCList &aQ, nsHostRecord **aResult) michael@0: { michael@0: *aResult = static_cast(aQ.next); michael@0: PR_REMOVE_AND_INIT_LINK(*aResult); michael@0: mPendingCount--; michael@0: (*aResult)->onQueue = false; michael@0: } michael@0: michael@0: bool michael@0: nsHostResolver::GetHostToLookup(nsHostRecord **result) michael@0: { michael@0: bool timedOut = false; michael@0: PRIntervalTime epoch, now, timeout; michael@0: michael@0: MutexAutoLock lock(mLock); michael@0: michael@0: timeout = (mNumIdleThreads >= HighThreadThreshold) ? mShortIdleTimeout : mLongIdleTimeout; michael@0: epoch = PR_IntervalNow(); michael@0: michael@0: while (!mShutdown) { michael@0: // remove next record from Q; hand over owning reference. Check high, then med, then low michael@0: michael@0: if (!PR_CLIST_IS_EMPTY(&mHighQ)) { michael@0: DeQueue (mHighQ, result); michael@0: return true; michael@0: } michael@0: michael@0: if (mActiveAnyThreadCount < HighThreadThreshold) { michael@0: if (!PR_CLIST_IS_EMPTY(&mMediumQ)) { michael@0: DeQueue (mMediumQ, result); michael@0: mActiveAnyThreadCount++; michael@0: (*result)->usingAnyThread = true; michael@0: return true; michael@0: } michael@0: michael@0: if (!PR_CLIST_IS_EMPTY(&mLowQ)) { michael@0: DeQueue (mLowQ, result); michael@0: mActiveAnyThreadCount++; michael@0: (*result)->usingAnyThread = true; michael@0: return true; michael@0: } michael@0: } michael@0: michael@0: // Determining timeout is racy, so allow one cycle through checking the queues michael@0: // before exiting. michael@0: if (timedOut) michael@0: break; michael@0: michael@0: // wait for one or more of the following to occur: michael@0: // (1) the pending queue has a host record to process michael@0: // (2) the shutdown flag has been set michael@0: // (3) the thread has been idle for too long michael@0: michael@0: mNumIdleThreads++; michael@0: mIdleThreadCV.Wait(timeout); michael@0: mNumIdleThreads--; michael@0: michael@0: now = PR_IntervalNow(); michael@0: michael@0: if ((PRIntervalTime)(now - epoch) >= timeout) michael@0: timedOut = true; michael@0: else { michael@0: // It is possible that PR_WaitCondVar() was interrupted and returned early, michael@0: // in which case we will loop back and re-enter it. In that case we want to michael@0: // do so with the new timeout reduced to reflect time already spent waiting. michael@0: timeout -= (PRIntervalTime)(now - epoch); michael@0: epoch = now; michael@0: } michael@0: } michael@0: michael@0: // tell thread to exit... michael@0: mThreadCount--; michael@0: return false; michael@0: } michael@0: michael@0: void michael@0: nsHostResolver::OnLookupComplete(nsHostRecord *rec, nsresult status, AddrInfo *result) michael@0: { michael@0: // get the list of pending callbacks for this lookup, and notify michael@0: // them that the lookup is complete. michael@0: PRCList cbs; michael@0: PR_INIT_CLIST(&cbs); michael@0: { michael@0: MutexAutoLock lock(mLock); michael@0: michael@0: // grab list of callbacks to notify michael@0: MoveCList(rec->callbacks, cbs); michael@0: michael@0: // update record fields. We might have a rec->addr_info already if a michael@0: // previous lookup result expired and we're reresolving it.. michael@0: AddrInfo *old_addr_info; michael@0: { michael@0: MutexAutoLock lock(rec->addr_info_lock); michael@0: old_addr_info = rec->addr_info; michael@0: rec->addr_info = result; michael@0: rec->addr_info_gencnt++; michael@0: } michael@0: delete old_addr_info; michael@0: michael@0: rec->expiration = TimeStamp::NowLoRes(); michael@0: if (result) { michael@0: rec->expiration += mMaxCacheLifetime; michael@0: rec->negative = false; michael@0: } michael@0: else { michael@0: rec->expiration += TimeDuration::FromSeconds(60); /* one minute for negative cache */ michael@0: rec->negative = true; michael@0: } michael@0: rec->resolving = false; michael@0: michael@0: if (rec->usingAnyThread) { michael@0: mActiveAnyThreadCount--; michael@0: rec->usingAnyThread = false; michael@0: } michael@0: michael@0: if (!mShutdown) { michael@0: // add to mEvictionQ michael@0: PR_APPEND_LINK(rec, &mEvictionQ); michael@0: NS_ADDREF(rec); michael@0: if (mEvictionQSize < mMaxCacheEntries) michael@0: mEvictionQSize++; michael@0: else { michael@0: // remove first element on mEvictionQ michael@0: nsHostRecord *head = michael@0: static_cast(PR_LIST_HEAD(&mEvictionQ)); michael@0: PR_REMOVE_AND_INIT_LINK(head); michael@0: PL_DHashTableOperate(&mDB, (nsHostKey *) head, PL_DHASH_REMOVE); michael@0: michael@0: if (!head->negative) { michael@0: // record the age of the entry upon eviction. michael@0: TimeDuration age = TimeStamp::NowLoRes() - michael@0: (head->expiration - mMaxCacheLifetime); michael@0: Telemetry::Accumulate(Telemetry::DNS_CLEANUP_AGE, michael@0: static_cast(age.ToSeconds() / 60)); michael@0: } michael@0: michael@0: // release reference to rec owned by mEvictionQ michael@0: NS_RELEASE(head); michael@0: } michael@0: } michael@0: } michael@0: michael@0: MOZ_EVENT_TRACER_DONE(rec, "net::dns::resolve"); michael@0: michael@0: if (!PR_CLIST_IS_EMPTY(&cbs)) { michael@0: PRCList *node = cbs.next; michael@0: while (node != &cbs) { michael@0: nsResolveHostCallback *callback = michael@0: static_cast(node); michael@0: node = node->next; michael@0: callback->OnLookupComplete(this, rec, status); michael@0: // NOTE: callback must not be dereferenced after this point!! michael@0: } michael@0: } michael@0: michael@0: NS_RELEASE(rec); michael@0: } michael@0: michael@0: void michael@0: nsHostResolver::CancelAsyncRequest(const char *host, michael@0: uint16_t flags, michael@0: uint16_t af, michael@0: nsIDNSListener *aListener, michael@0: nsresult status) michael@0: michael@0: { michael@0: MutexAutoLock lock(mLock); michael@0: michael@0: // Lookup the host record associated with host, flags & address family michael@0: nsHostKey key = { host, flags, af }; michael@0: nsHostDBEnt *he = static_cast michael@0: (PL_DHashTableOperate(&mDB, &key, PL_DHASH_LOOKUP)); michael@0: if (he && he->rec) { michael@0: nsHostRecord* recPtr = nullptr; michael@0: PRCList *node = he->rec->callbacks.next; michael@0: // Remove the first nsDNSAsyncRequest callback which matches the michael@0: // supplied listener object michael@0: while (node != &he->rec->callbacks) { michael@0: nsResolveHostCallback *callback michael@0: = static_cast(node); michael@0: if (callback && (callback->EqualsAsyncListener(aListener))) { michael@0: // Remove from the list of callbacks michael@0: PR_REMOVE_LINK(callback); michael@0: recPtr = he->rec; michael@0: callback->OnLookupComplete(this, recPtr, status); michael@0: break; michael@0: } michael@0: node = node->next; michael@0: } michael@0: michael@0: // If there are no more callbacks, remove the hash table entry michael@0: if (recPtr && PR_CLIST_IS_EMPTY(&recPtr->callbacks)) { michael@0: PL_DHashTableOperate(&mDB, (nsHostKey *)recPtr, PL_DHASH_REMOVE); michael@0: // If record is on a Queue, remove it and then deref it michael@0: if (recPtr->next != recPtr) { michael@0: PR_REMOVE_LINK(recPtr); michael@0: NS_RELEASE(recPtr); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: static size_t michael@0: SizeOfHostDBEntExcludingThis(PLDHashEntryHdr* hdr, MallocSizeOf mallocSizeOf, michael@0: void*) michael@0: { michael@0: nsHostDBEnt* ent = static_cast(hdr); michael@0: return ent->rec->SizeOfIncludingThis(mallocSizeOf); michael@0: } michael@0: michael@0: size_t michael@0: nsHostResolver::SizeOfIncludingThis(MallocSizeOf mallocSizeOf) const michael@0: { michael@0: MutexAutoLock lock(mLock); michael@0: michael@0: size_t n = mallocSizeOf(this); michael@0: n += PL_DHashTableSizeOfExcludingThis(&mDB, SizeOfHostDBEntExcludingThis, michael@0: mallocSizeOf); michael@0: michael@0: // The following fields aren't measured. michael@0: // - mHighQ, mMediumQ, mLowQ, mEvictionQ, because they just point to michael@0: // nsHostRecords that also pointed to by entries |mDB|, and measured when michael@0: // |mDB| is measured. michael@0: michael@0: return n; michael@0: } michael@0: michael@0: void michael@0: nsHostResolver::ThreadFunc(void *arg) michael@0: { michael@0: LOG(("DNS lookup thread - starting execution.\n")); michael@0: michael@0: static nsThreadPoolNaming naming; michael@0: naming.SetThreadPoolName(NS_LITERAL_CSTRING("DNS Resolver")); michael@0: michael@0: #if defined(RES_RETRY_ON_FAILURE) michael@0: nsResState rs; michael@0: #endif michael@0: nsHostResolver *resolver = (nsHostResolver *)arg; michael@0: nsHostRecord *rec; michael@0: PRAddrInfo *prai = nullptr; michael@0: while (resolver->GetHostToLookup(&rec)) { michael@0: LOG(("DNS lookup thread - Calling getaddrinfo for host [%s].\n", michael@0: rec->host)); michael@0: michael@0: int flags = PR_AI_ADDRCONFIG; michael@0: if (!(rec->flags & RES_CANON_NAME)) michael@0: flags |= PR_AI_NOCANONNAME; michael@0: michael@0: TimeStamp startTime = TimeStamp::Now(); michael@0: MOZ_EVENT_TRACER_EXEC(rec, "net::dns::resolve"); michael@0: michael@0: // We need to remove IPv4 records manually michael@0: // because PR_GetAddrInfoByName doesn't support PR_AF_INET6. michael@0: bool disableIPv4 = rec->af == PR_AF_INET6; michael@0: uint16_t af = disableIPv4 ? PR_AF_UNSPEC : rec->af; michael@0: prai = PR_GetAddrInfoByName(rec->host, af, flags); michael@0: #if defined(RES_RETRY_ON_FAILURE) michael@0: if (!prai && rs.Reset()) michael@0: prai = PR_GetAddrInfoByName(rec->host, af, flags); michael@0: #endif michael@0: michael@0: TimeDuration elapsed = TimeStamp::Now() - startTime; michael@0: uint32_t millis = static_cast(elapsed.ToMilliseconds()); michael@0: michael@0: // convert error code to nsresult michael@0: nsresult status; michael@0: AddrInfo *ai = nullptr; michael@0: if (prai) { michael@0: const char *cname = nullptr; michael@0: if (rec->flags & RES_CANON_NAME) michael@0: cname = PR_GetCanonNameFromAddrInfo(prai); michael@0: ai = new AddrInfo(rec->host, prai, disableIPv4, cname); michael@0: PR_FreeAddrInfo(prai); michael@0: if (ai->mAddresses.isEmpty()) { michael@0: delete ai; michael@0: ai = nullptr; michael@0: } michael@0: } michael@0: if (ai) { michael@0: status = NS_OK; michael@0: michael@0: Telemetry::Accumulate(!rec->addr_info_gencnt ? michael@0: Telemetry::DNS_LOOKUP_TIME : michael@0: Telemetry::DNS_RENEWAL_TIME, michael@0: millis); michael@0: } michael@0: else { michael@0: status = NS_ERROR_UNKNOWN_HOST; michael@0: Telemetry::Accumulate(Telemetry::DNS_FAILED_LOOKUP_TIME, millis); michael@0: } michael@0: michael@0: // OnLookupComplete may release "rec", log before we lose it. michael@0: LOG(("DNS lookup thread - lookup completed for host [%s]: %s.\n", michael@0: rec->host, ai ? "success" : "failure: unknown host")); michael@0: resolver->OnLookupComplete(rec, status, ai); michael@0: } michael@0: NS_RELEASE(resolver); michael@0: LOG(("DNS lookup thread - queue empty, thread finished.\n")); michael@0: } michael@0: michael@0: nsresult michael@0: nsHostResolver::Create(uint32_t maxCacheEntries, michael@0: uint32_t maxCacheLifetime, michael@0: uint32_t lifetimeGracePeriod, michael@0: nsHostResolver **result) michael@0: { michael@0: #if defined(PR_LOGGING) michael@0: if (!gHostResolverLog) michael@0: gHostResolverLog = PR_NewLogModule("nsHostResolver"); michael@0: #endif michael@0: michael@0: nsHostResolver *res = new nsHostResolver(maxCacheEntries, michael@0: maxCacheLifetime, michael@0: lifetimeGracePeriod); michael@0: if (!res) michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: NS_ADDREF(res); michael@0: michael@0: nsresult rv = res->Init(); michael@0: if (NS_FAILED(rv)) michael@0: NS_RELEASE(res); michael@0: michael@0: *result = res; michael@0: return rv; michael@0: } michael@0: michael@0: PLDHashOperator michael@0: CacheEntryEnumerator(PLDHashTable *table, PLDHashEntryHdr *entry, michael@0: uint32_t number, void *arg) michael@0: { michael@0: // We don't pay attention to address literals, only resolved domains. michael@0: // Also require a host. michael@0: nsHostRecord *rec = static_cast(entry)->rec; michael@0: MOZ_ASSERT(rec, "rec should never be null here!"); michael@0: if (!rec || !rec->addr_info || !rec->host) { michael@0: return PL_DHASH_NEXT; michael@0: } michael@0: michael@0: DNSCacheEntries info; michael@0: info.hostname = rec->host; michael@0: info.family = rec->af; michael@0: info.expiration = (int64_t)(rec->expiration - TimeStamp::NowLoRes()).ToSeconds(); michael@0: if (info.expiration <= 0) { michael@0: // We only need valid DNS cache entries michael@0: return PL_DHASH_NEXT; michael@0: } michael@0: michael@0: { michael@0: MutexAutoLock lock(rec->addr_info_lock); michael@0: michael@0: NetAddr *addr = nullptr; michael@0: NetAddrElement *addrElement = rec->addr_info->mAddresses.getFirst(); michael@0: if (addrElement) { michael@0: addr = &addrElement->mAddress; michael@0: } michael@0: while (addr) { michael@0: char buf[kIPv6CStrBufSize]; michael@0: if (NetAddrToString(addr, buf, sizeof(buf))) { michael@0: info.hostaddr.AppendElement(buf); michael@0: } michael@0: addr = nullptr; michael@0: addrElement = addrElement->getNext(); michael@0: if (addrElement) { michael@0: addr = &addrElement->mAddress; michael@0: } michael@0: } michael@0: } michael@0: michael@0: nsTArray *args = static_cast *>(arg); michael@0: args->AppendElement(info); michael@0: michael@0: return PL_DHASH_NEXT; michael@0: } michael@0: michael@0: void michael@0: nsHostResolver::GetDNSCacheEntries(nsTArray *args) michael@0: { michael@0: PL_DHashTableEnumerate(&mDB, CacheEntryEnumerator, args); michael@0: }