Bumping manifests a=b2g-bump
[gecko.git] / netwerk / dns / nsHostResolver.cpp
blobe1160066b7a98e46c9d8d250644e2fd0a67dd8a3
1 /* vim:set ts=4 sw=4 sts=4 et cin: */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #if defined(HAVE_RES_NINIT)
7 #include <sys/types.h>
8 #include <netinet/in.h>
9 #include <arpa/inet.h>
10 #include <arpa/nameser.h>
11 #include <resolv.h>
12 #define RES_RETRY_ON_FAILURE
13 #endif
15 #include <stdlib.h>
16 #include <ctime>
17 #include "nsHostResolver.h"
18 #include "nsError.h"
19 #include "nsISupportsBase.h"
20 #include "nsISupportsUtils.h"
21 #include "nsAutoPtr.h"
22 #include "nsPrintfCString.h"
23 #include "prthread.h"
24 #include "prerror.h"
25 #include "prtime.h"
26 #include "prlog.h"
27 #include "pldhash.h"
28 #include "plstr.h"
29 #include "nsURLHelper.h"
30 #include "nsThreadUtils.h"
31 #include "GetAddrInfo.h"
33 #include "mozilla/HashFunctions.h"
34 #include "mozilla/TimeStamp.h"
35 #include "mozilla/Telemetry.h"
36 #include "mozilla/VisualEventTracer.h"
37 #include "mozilla/DebugOnly.h"
38 #include "mozilla/Preferences.h"
40 using namespace mozilla;
41 using namespace mozilla::net;
43 // None of our implementations expose a TTL for negative responses, so we use a
44 // constant always.
45 static const unsigned int NEGATIVE_RECORD_LIFETIME = 60;
47 //----------------------------------------------------------------------------
49 // Use a persistent thread pool in order to avoid spinning up new threads all the time.
50 // In particular, thread creation results in a res_init() call from libc which is
51 // quite expensive.
53 // The pool dynamically grows between 0 and MAX_RESOLVER_THREADS in size. New requests
54 // go first to an idle thread. If that cannot be found and there are fewer than MAX_RESOLVER_THREADS
55 // currently in the pool a new thread is created for high priority requests. If
56 // the new request is at a lower priority a new thread will only be created if
57 // there are fewer than HighThreadThreshold currently outstanding. If a thread cannot be
58 // created or an idle thread located for the request it is queued.
60 // When the pool is greater than HighThreadThreshold in size a thread will be destroyed after
61 // ShortIdleTimeoutSeconds of idle time. Smaller pools use LongIdleTimeoutSeconds for a
62 // timeout period.
64 #define HighThreadThreshold MAX_RESOLVER_THREADS_FOR_ANY_PRIORITY
65 #define LongIdleTimeoutSeconds 300 // for threads 1 -> HighThreadThreshold
66 #define ShortIdleTimeoutSeconds 60 // for threads HighThreadThreshold+1 -> MAX_RESOLVER_THREADS
68 PR_STATIC_ASSERT (HighThreadThreshold <= MAX_RESOLVER_THREADS);
70 //----------------------------------------------------------------------------
72 #if defined(PR_LOGGING)
73 static PRLogModuleInfo *gHostResolverLog = nullptr;
74 #define LOG(args) PR_LOG(gHostResolverLog, PR_LOG_DEBUG, args)
75 #else
76 #define LOG(args)
77 #endif
79 #define LOG_HOST(host, interface) host, \
80 (interface && interface[0] != '\0') ? " on interface " : "", \
81 (interface && interface[0] != '\0') ? interface : ""
83 //----------------------------------------------------------------------------
85 static inline void
86 MoveCList(PRCList &from, PRCList &to)
88 if (!PR_CLIST_IS_EMPTY(&from)) {
89 to.next = from.next;
90 to.prev = from.prev;
91 to.next->prev = &to;
92 to.prev->next = &to;
93 PR_INIT_CLIST(&from);
97 //----------------------------------------------------------------------------
99 #if defined(RES_RETRY_ON_FAILURE)
101 // this class represents the resolver state for a given thread. if we
102 // encounter a lookup failure, then we can invoke the Reset method on an
103 // instance of this class to reset the resolver (in case /etc/resolv.conf
104 // for example changed). this is mainly an issue on GNU systems since glibc
105 // only reads in /etc/resolv.conf once per thread. it may be an issue on
106 // other systems as well.
108 class nsResState
110 public:
111 nsResState()
112 // initialize mLastReset to the time when this object
113 // is created. this means that a reset will not occur
114 // if a thread is too young. the alternative would be
115 // to initialize this to the beginning of time, so that
116 // the first failure would cause a reset, but since the
117 // thread would have just started up, it likely would
118 // already have current /etc/resolv.conf info.
119 : mLastReset(PR_IntervalNow())
123 bool Reset()
125 // reset no more than once per second
126 if (PR_IntervalToSeconds(PR_IntervalNow() - mLastReset) < 1)
127 return false;
129 LOG(("Calling 'res_ninit'.\n"));
131 mLastReset = PR_IntervalNow();
132 return (res_ninit(&_res) == 0);
135 private:
136 PRIntervalTime mLastReset;
139 #endif // RES_RETRY_ON_FAILURE
141 //----------------------------------------------------------------------------
143 static inline bool
144 IsHighPriority(uint16_t flags)
146 return !(flags & (nsHostResolver::RES_PRIORITY_LOW | nsHostResolver::RES_PRIORITY_MEDIUM));
149 static inline bool
150 IsMediumPriority(uint16_t flags)
152 return flags & nsHostResolver::RES_PRIORITY_MEDIUM;
155 static inline bool
156 IsLowPriority(uint16_t flags)
158 return flags & nsHostResolver::RES_PRIORITY_LOW;
161 //----------------------------------------------------------------------------
162 // this macro filters out any flags that are not used when constructing the
163 // host key. the significant flags are those that would affect the resulting
164 // host record (i.e., the flags that are passed down to PR_GetAddrInfoByName).
165 #define RES_KEY_FLAGS(_f) ((_f) & nsHostResolver::RES_CANON_NAME)
167 nsHostRecord::nsHostRecord(const nsHostKey *key)
168 : addr_info_lock("nsHostRecord.addr_info_lock")
169 , addr_info_gencnt(0)
170 , addr_info(nullptr)
171 , addr(nullptr)
172 , negative(false)
173 , resolving(false)
174 , onQueue(false)
175 , usingAnyThread(false)
176 , mDoomed(false)
177 #if TTL_AVAILABLE
178 , mGetTtl(false)
179 #endif
180 , mBlacklistedCount(0)
181 , mResolveAgain(false)
183 host = ((char *) this) + sizeof(nsHostRecord);
184 memcpy((char *) host, key->host, strlen(key->host) + 1);
185 flags = key->flags;
186 af = key->af;
187 netInterface = host + strlen(key->host) + 1;
188 memcpy((char *) netInterface, key->netInterface,
189 strlen(key->netInterface) + 1);
190 PR_INIT_CLIST(this);
191 PR_INIT_CLIST(&callbacks);
194 nsresult
195 nsHostRecord::Create(const nsHostKey *key, nsHostRecord **result)
197 size_t hostLen = strlen(key->host) + 1;
198 size_t netInterfaceLen = strlen(key->netInterface) + 1;
199 size_t size = hostLen + netInterfaceLen + sizeof(nsHostRecord);
201 // Use placement new to create the object with room for the hostname and
202 // network interface name allocated after it.
203 void *place = ::operator new(size);
204 *result = new(place) nsHostRecord(key);
205 NS_ADDREF(*result);
207 MOZ_EVENT_TRACER_NAME_OBJECT(*result, key->host);
209 return NS_OK;
212 void
213 nsHostRecord::SetExpiration(const mozilla::TimeStamp& now, unsigned int valid, unsigned int grace)
215 mValidStart = now;
216 mGraceStart = now + TimeDuration::FromSeconds(valid);
217 mValidEnd = now + TimeDuration::FromSeconds(valid + grace);
220 void
221 nsHostRecord::CopyExpirationTimesAndFlagsFrom(const nsHostRecord *aFromHostRecord)
223 // This is used to copy information from a cache entry to a record. All
224 // information necessary for HasUsableRecord needs to be copied.
225 mValidStart = aFromHostRecord->mValidStart;
226 mValidEnd = aFromHostRecord->mValidEnd;
227 mGraceStart = aFromHostRecord->mGraceStart;
228 mDoomed = aFromHostRecord->mDoomed;
231 nsHostRecord::~nsHostRecord()
233 Telemetry::Accumulate(Telemetry::DNS_BLACKLIST_COUNT, mBlacklistedCount);
234 delete addr_info;
235 delete addr;
238 bool
239 nsHostRecord::Blacklisted(NetAddr *aQuery)
241 // must call locked
242 LOG(("Checking blacklist for host [%s%s%s], host record [%p].\n",
243 LOG_HOST(host, netInterface), this));
245 // skip the string conversion for the common case of no blacklist
246 if (!mBlacklistedItems.Length()) {
247 return false;
250 char buf[kIPv6CStrBufSize];
251 if (!NetAddrToString(aQuery, buf, sizeof(buf))) {
252 return false;
254 nsDependentCString strQuery(buf);
256 for (uint32_t i = 0; i < mBlacklistedItems.Length(); i++) {
257 if (mBlacklistedItems.ElementAt(i).Equals(strQuery)) {
258 LOG(("Address [%s] is blacklisted for host [%s%s%s].\n", buf,
259 LOG_HOST(host, netInterface)));
260 return true;
264 return false;
267 void
268 nsHostRecord::ReportUnusable(NetAddr *aAddress)
270 // must call locked
271 LOG(("Adding address to blacklist for host [%s%s%s], host record [%p].\n",
272 LOG_HOST(host, netInterface), this));
274 ++mBlacklistedCount;
276 if (negative)
277 mDoomed = true;
279 char buf[kIPv6CStrBufSize];
280 if (NetAddrToString(aAddress, buf, sizeof(buf))) {
281 LOG(("Successfully adding address [%s] to blacklist for host "
282 "[%s%s%s].\n", buf, LOG_HOST(host, netInterface)));
283 mBlacklistedItems.AppendElement(nsCString(buf));
287 void
288 nsHostRecord::ResetBlacklist()
290 // must call locked
291 LOG(("Resetting blacklist for host [%s%s%s], host record [%p].\n",
292 LOG_HOST(host, netInterface), this));
293 mBlacklistedItems.Clear();
296 nsHostRecord::ExpirationStatus
297 nsHostRecord::CheckExpiration(const mozilla::TimeStamp& now) const {
298 if (!mGraceStart.IsNull() && now >= mGraceStart
299 && !mValidEnd.IsNull() && now < mValidEnd) {
300 return nsHostRecord::EXP_GRACE;
301 } else if (!mValidEnd.IsNull() && now < mValidEnd) {
302 return nsHostRecord::EXP_VALID;
305 return nsHostRecord::EXP_EXPIRED;
309 bool
310 nsHostRecord::HasUsableResult(const mozilla::TimeStamp& now, uint16_t queryFlags) const
312 if (mDoomed) {
313 return false;
316 // don't use cached negative results for high priority queries.
317 if (negative && IsHighPriority(queryFlags)) {
318 return false;
321 if (CheckExpiration(now) == EXP_EXPIRED) {
322 return false;
325 return addr_info || addr || negative;
328 static size_t
329 SizeOfResolveHostCallbackListExcludingHead(const PRCList *head,
330 MallocSizeOf mallocSizeOf)
332 size_t n = 0;
333 PRCList *curr = head->next;
334 while (curr != head) {
335 nsResolveHostCallback *callback =
336 static_cast<nsResolveHostCallback*>(curr);
337 n += callback->SizeOfIncludingThis(mallocSizeOf);
338 curr = curr->next;
340 return n;
343 size_t
344 nsHostRecord::SizeOfIncludingThis(MallocSizeOf mallocSizeOf) const
346 size_t n = mallocSizeOf(this);
348 // The |host| field (inherited from nsHostKey) actually points to extra
349 // memory that is allocated beyond the end of the nsHostRecord (see
350 // nsHostRecord::Create()). So it will be included in the
351 // |mallocSizeOf(this)| call above.
353 n += SizeOfResolveHostCallbackListExcludingHead(&callbacks, mallocSizeOf);
354 n += addr_info ? addr_info->SizeOfIncludingThis(mallocSizeOf) : 0;
355 n += mallocSizeOf(addr);
357 n += mBlacklistedItems.SizeOfExcludingThis(mallocSizeOf);
358 for (size_t i = 0; i < mBlacklistedItems.Length(); i++) {
359 n += mBlacklistedItems[i].SizeOfExcludingThisMustBeUnshared(mallocSizeOf);
361 return n;
364 nsHostRecord::DnsPriority
365 nsHostRecord::GetPriority(uint16_t aFlags)
367 if (IsHighPriority(aFlags)){
368 return nsHostRecord::DNS_PRIORITY_HIGH;
369 } else if (IsMediumPriority(aFlags)) {
370 return nsHostRecord::DNS_PRIORITY_MEDIUM;
373 return nsHostRecord::DNS_PRIORITY_LOW;
376 // Returns true if the entry can be removed, or false if it should be left.
377 // Sets mResolveAgain true for entries being resolved right now.
378 bool
379 nsHostRecord::RemoveOrRefresh()
381 if (resolving) {
382 if (!onQueue) {
383 // The request has been passed to the OS resolver. The resultant DNS
384 // record should be considered stale and not trusted; set a flag to
385 // ensure it is called again.
386 mResolveAgain = true;
388 // if Onqueue is true, the host entry is already added to the cache
389 // but is still pending to get resolved: just leave it in hash.
390 return false;
392 // Already resolved; not in a pending state; remove from cache.
393 return true;
396 //----------------------------------------------------------------------------
398 struct nsHostDBEnt : PLDHashEntryHdr
400 nsHostRecord *rec;
403 static PLDHashNumber
404 HostDB_HashKey(PLDHashTable *table, const void *key)
406 const nsHostKey *hk = static_cast<const nsHostKey *>(key);
407 return AddToHash(HashString(hk->host), RES_KEY_FLAGS(hk->flags), hk->af,
408 HashString(hk->netInterface));
411 static bool
412 HostDB_MatchEntry(PLDHashTable *table,
413 const PLDHashEntryHdr *entry,
414 const void *key)
416 const nsHostDBEnt *he = static_cast<const nsHostDBEnt *>(entry);
417 const nsHostKey *hk = static_cast<const nsHostKey *>(key);
419 return !strcmp(he->rec->host, hk->host) &&
420 RES_KEY_FLAGS (he->rec->flags) == RES_KEY_FLAGS(hk->flags) &&
421 he->rec->af == hk->af &&
422 !strcmp(he->rec->netInterface, hk->netInterface);
425 static void
426 HostDB_MoveEntry(PLDHashTable *table,
427 const PLDHashEntryHdr *from,
428 PLDHashEntryHdr *to)
430 static_cast<nsHostDBEnt *>(to)->rec =
431 static_cast<const nsHostDBEnt *>(from)->rec;
434 static void
435 HostDB_ClearEntry(PLDHashTable *table,
436 PLDHashEntryHdr *entry)
438 nsHostDBEnt *he = static_cast<nsHostDBEnt*>(entry);
439 MOZ_ASSERT(he, "nsHostDBEnt is null!");
441 nsHostRecord *hr = he->rec;
442 MOZ_ASSERT(hr, "nsHostDBEnt has null host record!");
444 LOG(("Clearing cache db entry for host [%s%s%s].\n",
445 LOG_HOST(hr->host, hr->netInterface)));
446 #if defined(DEBUG) && defined(PR_LOGGING)
448 MutexAutoLock lock(hr->addr_info_lock);
449 if (!hr->addr_info) {
450 LOG(("No address info for host [%s%s%s].\n",
451 LOG_HOST(hr->host, hr->netInterface)));
452 } else {
453 if (!hr->mValidEnd.IsNull()) {
454 TimeDuration diff = hr->mValidEnd - TimeStamp::NowLoRes();
455 LOG(("Record for host [%s%s%s] expires in %f seconds.\n",
456 LOG_HOST(hr->host, hr->netInterface),
457 diff.ToSeconds()));
458 } else {
459 LOG(("Record for host [%s%s%s] not yet valid.\n",
460 LOG_HOST(hr->host, hr->netInterface)));
463 NetAddrElement *addrElement = nullptr;
464 char buf[kIPv6CStrBufSize];
465 do {
466 if (!addrElement) {
467 addrElement = hr->addr_info->mAddresses.getFirst();
468 } else {
469 addrElement = addrElement->getNext();
472 if (addrElement) {
473 NetAddrToString(&addrElement->mAddress, buf, sizeof(buf));
474 LOG((" [%s]\n", buf));
477 while (addrElement);
480 #endif
481 NS_RELEASE(he->rec);
484 static bool
485 HostDB_InitEntry(PLDHashTable *table,
486 PLDHashEntryHdr *entry,
487 const void *key)
489 nsHostDBEnt *he = static_cast<nsHostDBEnt *>(entry);
490 nsHostRecord::Create(static_cast<const nsHostKey *>(key), &he->rec);
491 return true;
494 static const PLDHashTableOps gHostDB_ops =
496 PL_DHashAllocTable,
497 PL_DHashFreeTable,
498 HostDB_HashKey,
499 HostDB_MatchEntry,
500 HostDB_MoveEntry,
501 HostDB_ClearEntry,
502 PL_DHashFinalizeStub,
503 HostDB_InitEntry,
506 static PLDHashOperator
507 HostDB_RemoveEntry(PLDHashTable *table,
508 PLDHashEntryHdr *hdr,
509 uint32_t number,
510 void *arg)
512 return PL_DHASH_REMOVE;
515 static PLDHashOperator
516 HostDB_PruneEntry(PLDHashTable *table,
517 PLDHashEntryHdr *hdr,
518 uint32_t number,
519 void *arg)
521 nsHostDBEnt* ent = static_cast<nsHostDBEnt *>(hdr);
522 // Try to remove the record, or mark it for refresh
523 if (ent->rec->RemoveOrRefresh()) {
524 PR_REMOVE_LINK(ent->rec);
525 return PL_DHASH_REMOVE;
527 return PL_DHASH_NEXT;
530 //----------------------------------------------------------------------------
532 #if TTL_AVAILABLE
533 static const char kPrefGetTtl[] = "network.dns.get-ttl";
534 static bool sGetTtlEnabled = false;
536 static void DnsPrefChanged(const char* aPref, void* aClosure)
538 MOZ_ASSERT(NS_IsMainThread(),
539 "Should be getting pref changed notification on main thread!");
541 if (strcmp(aPref, kPrefGetTtl) != 0) {
542 LOG(("DnsPrefChanged ignoring pref \"%s\"", aPref));
543 return;
546 auto self = static_cast<nsHostResolver*>(aClosure);
547 MOZ_ASSERT(self);
549 sGetTtlEnabled = Preferences::GetBool(kPrefGetTtl);
551 #endif
553 nsHostResolver::nsHostResolver(uint32_t maxCacheEntries,
554 uint32_t defaultCacheEntryLifetime,
555 uint32_t defaultGracePeriod)
556 : mMaxCacheEntries(maxCacheEntries)
557 , mDefaultCacheLifetime(defaultCacheEntryLifetime)
558 , mDefaultGracePeriod(defaultGracePeriod)
559 , mLock("nsHostResolver.mLock")
560 , mIdleThreadCV(mLock, "nsHostResolver.mIdleThreadCV")
561 , mNumIdleThreads(0)
562 , mThreadCount(0)
563 , mActiveAnyThreadCount(0)
564 , mEvictionQSize(0)
565 , mPendingCount(0)
566 , mShutdown(true)
568 mCreationTime = PR_Now();
569 PR_INIT_CLIST(&mHighQ);
570 PR_INIT_CLIST(&mMediumQ);
571 PR_INIT_CLIST(&mLowQ);
572 PR_INIT_CLIST(&mEvictionQ);
574 mLongIdleTimeout = PR_SecondsToInterval(LongIdleTimeoutSeconds);
575 mShortIdleTimeout = PR_SecondsToInterval(ShortIdleTimeoutSeconds);
578 nsHostResolver::~nsHostResolver()
580 PL_DHashTableFinish(&mDB);
583 nsresult
584 nsHostResolver::Init()
586 if (NS_FAILED(GetAddrInfoInit())) {
587 return NS_ERROR_FAILURE;
590 PL_DHashTableInit(&mDB, &gHostDB_ops, nullptr, sizeof(nsHostDBEnt), 0);
592 mShutdown = false;
594 #if TTL_AVAILABLE
595 // The preferences probably haven't been loaded from the disk yet, so we
596 // need to register a callback that will set up the experiment once they
597 // are. We also need to explicitly set a value for the props otherwise the
598 // callback won't be called.
600 DebugOnly<nsresult> rv = Preferences::RegisterCallbackAndCall(
601 &DnsPrefChanged, kPrefGetTtl, this);
602 NS_WARN_IF_FALSE(NS_SUCCEEDED(rv),
603 "Could not register DNS TTL pref callback.");
605 #endif
607 #if defined(HAVE_RES_NINIT)
608 // We want to make sure the system is using the correct resolver settings,
609 // so we force it to reload those settings whenever we startup a subsequent
610 // nsHostResolver instance. We assume that there is no reason to do this
611 // for the first nsHostResolver instance since that is usually created
612 // during application startup.
613 static int initCount = 0;
614 if (initCount++ > 0) {
615 LOG(("Calling 'res_ninit'.\n"));
616 res_ninit(&_res);
618 #endif
619 return NS_OK;
622 void
623 nsHostResolver::ClearPendingQueue(PRCList *aPendingQ)
625 // loop through pending queue, erroring out pending lookups.
626 if (!PR_CLIST_IS_EMPTY(aPendingQ)) {
627 PRCList *node = aPendingQ->next;
628 while (node != aPendingQ) {
629 nsHostRecord *rec = static_cast<nsHostRecord *>(node);
630 node = node->next;
631 OnLookupComplete(rec, NS_ERROR_ABORT, nullptr);
637 // FlushCache() is what we call when the network has changed. We must not
638 // trust names that were resolved before this change. They may resolve
639 // differently now.
641 // This function removes all existing resolved host entries from the hash.
642 // Names that are in the pending queues can be left there. Entries in the
643 // cache that have 'Resolve' set true but not 'onQueue' are being resolved
644 // right now, so we need to mark them to get re-resolved on completion!
646 void
647 nsHostResolver::FlushCache()
649 MutexAutoLock lock(mLock);
650 mEvictionQSize = 0;
652 // Clear the evictionQ and remove all its corresponding entries from
653 // the cache first
654 if (!PR_CLIST_IS_EMPTY(&mEvictionQ)) {
655 PRCList *node = mEvictionQ.next;
656 while (node != &mEvictionQ) {
657 nsHostRecord *rec = static_cast<nsHostRecord *>(node);
658 node = node->next;
659 PR_REMOVE_AND_INIT_LINK(rec);
660 PL_DHashTableRemove(&mDB, (nsHostKey *) rec);
661 NS_RELEASE(rec);
665 // Refresh the cache entries that are resolving RIGHT now, remove the rest.
666 PL_DHashTableEnumerate(&mDB, HostDB_PruneEntry, nullptr);
669 void
670 nsHostResolver::Shutdown()
672 LOG(("Shutting down host resolver.\n"));
674 #if TTL_AVAILABLE
676 DebugOnly<nsresult> rv = Preferences::UnregisterCallback(
677 &DnsPrefChanged, kPrefGetTtl, this);
678 NS_WARN_IF_FALSE(NS_SUCCEEDED(rv),
679 "Could not unregister DNS TTL pref callback.");
681 #endif
683 PRCList pendingQHigh, pendingQMed, pendingQLow, evictionQ;
684 PR_INIT_CLIST(&pendingQHigh);
685 PR_INIT_CLIST(&pendingQMed);
686 PR_INIT_CLIST(&pendingQLow);
687 PR_INIT_CLIST(&evictionQ);
690 MutexAutoLock lock(mLock);
692 mShutdown = true;
694 MoveCList(mHighQ, pendingQHigh);
695 MoveCList(mMediumQ, pendingQMed);
696 MoveCList(mLowQ, pendingQLow);
697 MoveCList(mEvictionQ, evictionQ);
698 mEvictionQSize = 0;
699 mPendingCount = 0;
701 if (mNumIdleThreads)
702 mIdleThreadCV.NotifyAll();
704 // empty host database
705 PL_DHashTableEnumerate(&mDB, HostDB_RemoveEntry, nullptr);
708 ClearPendingQueue(&pendingQHigh);
709 ClearPendingQueue(&pendingQMed);
710 ClearPendingQueue(&pendingQLow);
712 if (!PR_CLIST_IS_EMPTY(&evictionQ)) {
713 PRCList *node = evictionQ.next;
714 while (node != &evictionQ) {
715 nsHostRecord *rec = static_cast<nsHostRecord *>(node);
716 node = node->next;
717 NS_RELEASE(rec);
721 #ifdef NS_BUILD_REFCNT_LOGGING
723 // Logically join the outstanding worker threads with a timeout.
724 // Use this approach instead of PR_JoinThread() because that does
725 // not allow a timeout which may be necessary for a semi-responsive
726 // shutdown if the thread is blocked on a very slow DNS resolution.
727 // mThreadCount is read outside of mLock, but the worst case
728 // scenario for that race is one extra 25ms sleep.
730 PRIntervalTime delay = PR_MillisecondsToInterval(25);
731 PRIntervalTime stopTime = PR_IntervalNow() + PR_SecondsToInterval(20);
732 while (mThreadCount && PR_IntervalNow() < stopTime)
733 PR_Sleep(delay);
734 #endif
737 mozilla::DebugOnly<nsresult> rv = GetAddrInfoShutdown();
738 NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "Failed to shutdown GetAddrInfo");
742 void
743 nsHostResolver::MoveQueue(nsHostRecord *aRec, PRCList &aDestQ)
745 NS_ASSERTION(aRec->onQueue, "Moving Host Record Not Currently Queued");
747 PR_REMOVE_LINK(aRec);
748 PR_APPEND_LINK(aRec, &aDestQ);
751 nsresult
752 nsHostResolver::ResolveHost(const char *host,
753 uint16_t flags,
754 uint16_t af,
755 const char *netInterface,
756 nsResolveHostCallback *callback)
758 NS_ENSURE_TRUE(host && *host, NS_ERROR_UNEXPECTED);
759 NS_ENSURE_TRUE(netInterface, NS_ERROR_UNEXPECTED);
761 LOG(("Resolving host [%s%s%s]%s.\n", LOG_HOST(host, netInterface),
762 flags & RES_BYPASS_CACHE ? " - bypassing cache" : ""));
764 // ensure that we are working with a valid hostname before proceeding. see
765 // bug 304904 for details.
766 if (!net_IsValidHostName(nsDependentCString(host)))
767 return NS_ERROR_UNKNOWN_HOST;
769 // if result is set inside the lock, then we need to issue the
770 // callback before returning.
771 nsRefPtr<nsHostRecord> result;
772 nsresult status = NS_OK, rv = NS_OK;
774 MutexAutoLock lock(mLock);
776 if (mShutdown)
777 rv = NS_ERROR_NOT_INITIALIZED;
778 else {
779 // Used to try to parse to an IP address literal.
780 PRNetAddr tempAddr;
781 // Unfortunately, PR_StringToNetAddr does not properly initialize
782 // the output buffer in the case of IPv6 input. See bug 223145.
783 memset(&tempAddr, 0, sizeof(PRNetAddr));
785 // check to see if there is already an entry for this |host|
786 // in the hash table. if so, then check to see if we can't
787 // just reuse the lookup result. otherwise, if there are
788 // any pending callbacks, then add to pending callbacks queue,
789 // and return. otherwise, add ourselves as first pending
790 // callback, and proceed to do the lookup.
792 nsHostKey key = { host, flags, af, netInterface };
793 nsHostDBEnt *he = static_cast<nsHostDBEnt *>
794 (PL_DHashTableAdd(&mDB, &key));
796 // if the record is null, then HostDB_InitEntry failed.
797 if (!he || !he->rec) {
798 LOG((" Out of memory: no cache entry for host [%s%s%s].\n",
799 LOG_HOST(host, netInterface)));
800 rv = NS_ERROR_OUT_OF_MEMORY;
802 // do we have a cached result that we can reuse?
803 else if (!(flags & RES_BYPASS_CACHE) &&
804 he->rec->HasUsableResult(TimeStamp::NowLoRes(), flags)) {
805 LOG((" Using cached record for host [%s%s%s].\n",
806 LOG_HOST(host, netInterface)));
807 // put reference to host record on stack...
808 result = he->rec;
809 Telemetry::Accumulate(Telemetry::DNS_LOOKUP_METHOD2, METHOD_HIT);
811 // For entries that are in the grace period
812 // or all cached negative entries, use the cache but start a new
813 // lookup in the background
814 ConditionallyRefreshRecord(he->rec, host);
816 if (he->rec->negative) {
817 LOG((" Negative cache entry for host [%s%s%s].\n",
818 LOG_HOST(host, netInterface)));
819 Telemetry::Accumulate(Telemetry::DNS_LOOKUP_METHOD2,
820 METHOD_NEGATIVE_HIT);
821 status = NS_ERROR_UNKNOWN_HOST;
824 // if the host name is an IP address literal and has been parsed,
825 // go ahead and use it.
826 else if (he->rec->addr) {
827 LOG((" Using cached address for IP Literal [%s].\n", host));
828 Telemetry::Accumulate(Telemetry::DNS_LOOKUP_METHOD2,
829 METHOD_LITERAL);
830 result = he->rec;
832 // try parsing the host name as an IP address literal to short
833 // circuit full host resolution. (this is necessary on some
834 // platforms like Win9x. see bug 219376 for more details.)
835 else if (PR_StringToNetAddr(host, &tempAddr) == PR_SUCCESS) {
836 LOG((" Host is IP Literal [%s].\n", host));
837 // ok, just copy the result into the host record, and be done
838 // with it! ;-)
839 he->rec->addr = new NetAddr();
840 PRNetAddrToNetAddr(&tempAddr, he->rec->addr);
841 // put reference to host record on stack...
842 Telemetry::Accumulate(Telemetry::DNS_LOOKUP_METHOD2,
843 METHOD_LITERAL);
844 result = he->rec;
846 else if (mPendingCount >= MAX_NON_PRIORITY_REQUESTS &&
847 !IsHighPriority(flags) &&
848 !he->rec->resolving) {
849 LOG((" Lookup queue full: dropping %s priority request for "
850 "host [%s%s%s].\n",
851 IsMediumPriority(flags) ? "medium" : "low",
852 LOG_HOST(host, netInterface)));
853 Telemetry::Accumulate(Telemetry::DNS_LOOKUP_METHOD2,
854 METHOD_OVERFLOW);
855 // This is a lower priority request and we are swamped, so refuse it.
856 rv = NS_ERROR_DNS_LOOKUP_QUEUE_FULL;
858 else if (flags & RES_OFFLINE) {
859 LOG((" Offline request for host [%s%s%s]; ignoring.\n",
860 LOG_HOST(host, netInterface)));
861 rv = NS_ERROR_OFFLINE;
864 // If this is an IPV4 or IPV6 specific request, check if there is
865 // an AF_UNSPEC entry we can use. Otherwise, hit the resolver...
866 else if (!he->rec->resolving) {
867 if (!(flags & RES_BYPASS_CACHE) &&
868 ((af == PR_AF_INET) || (af == PR_AF_INET6))) {
869 // First, search for an entry with AF_UNSPEC
870 const nsHostKey unspecKey = { host, flags, PR_AF_UNSPEC,
871 netInterface };
872 nsHostDBEnt *unspecHe = static_cast<nsHostDBEnt *>
873 (PL_DHashTableLookup(&mDB, &unspecKey));
874 NS_ASSERTION(PL_DHASH_ENTRY_IS_FREE(unspecHe) ||
875 (PL_DHASH_ENTRY_IS_BUSY(unspecHe) &&
876 unspecHe->rec),
877 "Valid host entries should contain a record");
878 TimeStamp now = TimeStamp::NowLoRes();
879 if (PL_DHASH_ENTRY_IS_BUSY(unspecHe) &&
880 unspecHe->rec &&
881 unspecHe->rec->HasUsableResult(now, flags)) {
883 MOZ_ASSERT(unspecHe->rec->addr_info || unspecHe->rec->negative,
884 "Entry should be resolved or negative.");
886 LOG((" Trying AF_UNSPEC entry for host [%s%s%s] af: %s.\n",
887 LOG_HOST(host, netInterface),
888 (af == PR_AF_INET) ? "AF_INET" : "AF_INET6"));
890 he->rec->addr_info = nullptr;
891 if (unspecHe->rec->negative) {
892 he->rec->negative = unspecHe->rec->negative;
893 he->rec->CopyExpirationTimesAndFlagsFrom(unspecHe->rec);
894 } else if (unspecHe->rec->addr_info) {
895 // Search for any valid address in the AF_UNSPEC entry
896 // in the cache (not blacklisted and from the right
897 // family).
898 NetAddrElement *addrIter =
899 unspecHe->rec->addr_info->mAddresses.getFirst();
900 while (addrIter) {
901 if ((af == addrIter->mAddress.inet.family) &&
902 !unspecHe->rec->Blacklisted(&addrIter->mAddress)) {
903 if (!he->rec->addr_info) {
904 he->rec->addr_info = new AddrInfo(
905 unspecHe->rec->addr_info->mHostName,
906 unspecHe->rec->addr_info->mCanonicalName);
907 he->rec->CopyExpirationTimesAndFlagsFrom(unspecHe->rec);
909 he->rec->addr_info->AddAddress(
910 new NetAddrElement(*addrIter));
912 addrIter = addrIter->getNext();
915 // Now check if we have a new record.
916 if (he->rec->HasUsableResult(now, flags)) {
917 result = he->rec;
918 if (he->rec->negative) {
919 status = NS_ERROR_UNKNOWN_HOST;
921 Telemetry::Accumulate(Telemetry::DNS_LOOKUP_METHOD2,
922 METHOD_HIT);
923 ConditionallyRefreshRecord(he->rec, host);
925 // For AF_INET6, a new lookup means another AF_UNSPEC
926 // lookup. We have already iterated through the
927 // AF_UNSPEC addresses, so we mark this record as
928 // negative.
929 else if (af == PR_AF_INET6) {
930 LOG((" No AF_INET6 in AF_UNSPEC entry: "
931 "host [%s%s%s] unknown host.",
932 LOG_HOST(host, netInterface)));
933 result = he->rec;
934 he->rec->negative = true;
935 status = NS_ERROR_UNKNOWN_HOST;
936 Telemetry::Accumulate(Telemetry::DNS_LOOKUP_METHOD2,
937 METHOD_NEGATIVE_HIT);
941 // If no valid address was found in the cache or this is an
942 // AF_UNSPEC request, then start a new lookup.
943 if (!result) {
944 LOG((" No usable address in cache for host [%s%s%s].",
945 LOG_HOST(host, netInterface)));
947 // Add callback to the list of pending callbacks.
948 PR_APPEND_LINK(callback, &he->rec->callbacks);
949 he->rec->flags = flags;
950 rv = IssueLookup(he->rec);
951 Telemetry::Accumulate(Telemetry::DNS_LOOKUP_METHOD2,
952 METHOD_NETWORK_FIRST);
953 if (NS_FAILED(rv)) {
954 PR_REMOVE_AND_INIT_LINK(callback);
956 else {
957 LOG((" DNS lookup for host [%s%s%s] blocking "
958 "pending 'getaddrinfo' query: callback [%p]",
959 LOG_HOST(host, netInterface), callback));
963 else {
964 LOG((" Host [%s%s%s] is being resolved. Appending callback "
965 "[%p].", LOG_HOST(host, netInterface), callback));
967 PR_APPEND_LINK(callback, &he->rec->callbacks);
968 if (he->rec->onQueue) {
969 Telemetry::Accumulate(Telemetry::DNS_LOOKUP_METHOD2,
970 METHOD_NETWORK_SHARED);
972 // Consider the case where we are on a pending queue of
973 // lower priority than the request is being made at.
974 // In that case we should upgrade to the higher queue.
976 if (IsHighPriority(flags) &&
977 !IsHighPriority(he->rec->flags)) {
978 // Move from (low|med) to high.
979 MoveQueue(he->rec, mHighQ);
980 he->rec->flags = flags;
981 ConditionallyCreateThread(he->rec);
982 } else if (IsMediumPriority(flags) &&
983 IsLowPriority(he->rec->flags)) {
984 // Move from low to med.
985 MoveQueue(he->rec, mMediumQ);
986 he->rec->flags = flags;
987 mIdleThreadCV.Notify();
993 if (result) {
994 callback->OnLookupComplete(this, result, status);
997 return rv;
1000 void
1001 nsHostResolver::DetachCallback(const char *host,
1002 uint16_t flags,
1003 uint16_t af,
1004 const char *netInterface,
1005 nsResolveHostCallback *callback,
1006 nsresult status)
1008 nsRefPtr<nsHostRecord> rec;
1010 MutexAutoLock lock(mLock);
1012 nsHostKey key = { host, flags, af, netInterface };
1013 nsHostDBEnt *he = static_cast<nsHostDBEnt *>
1014 (PL_DHashTableLookup(&mDB, &key));
1015 if (he && he->rec) {
1016 // walk list looking for |callback|... we cannot assume
1017 // that it will be there!
1018 PRCList *node = he->rec->callbacks.next;
1019 while (node != &he->rec->callbacks) {
1020 if (static_cast<nsResolveHostCallback *>(node) == callback) {
1021 PR_REMOVE_LINK(callback);
1022 rec = he->rec;
1023 break;
1025 node = node->next;
1030 // complete callback with the given status code; this would only be done if
1031 // the record was in the process of being resolved.
1032 if (rec)
1033 callback->OnLookupComplete(this, rec, status);
1036 nsresult
1037 nsHostResolver::ConditionallyCreateThread(nsHostRecord *rec)
1039 if (mNumIdleThreads) {
1040 // wake up idle thread to process this lookup
1041 mIdleThreadCV.Notify();
1043 else if ((mThreadCount < HighThreadThreshold) ||
1044 (IsHighPriority(rec->flags) && mThreadCount < MAX_RESOLVER_THREADS)) {
1045 // dispatch new worker thread
1046 NS_ADDREF_THIS(); // owning reference passed to thread
1048 mThreadCount++;
1049 PRThread *thr = PR_CreateThread(PR_SYSTEM_THREAD,
1050 ThreadFunc,
1051 this,
1052 PR_PRIORITY_NORMAL,
1053 PR_GLOBAL_THREAD,
1054 PR_UNJOINABLE_THREAD,
1056 if (!thr) {
1057 mThreadCount--;
1058 NS_RELEASE_THIS();
1059 return NS_ERROR_OUT_OF_MEMORY;
1062 #if defined(PR_LOGGING)
1063 else {
1064 LOG((" Unable to find a thread for looking up host [%s%s%s].\n",
1065 LOG_HOST(rec->host, rec->netInterface)));
1067 #endif
1068 return NS_OK;
1071 nsresult
1072 nsHostResolver::IssueLookup(nsHostRecord *rec)
1074 MOZ_EVENT_TRACER_WAIT(rec, "net::dns::resolve");
1076 nsresult rv = NS_OK;
1077 NS_ASSERTION(!rec->resolving, "record is already being resolved");
1079 // Add rec to one of the pending queues, possibly removing it from mEvictionQ.
1080 // If rec is on mEvictionQ, then we can just move the owning
1081 // reference over to the new active queue.
1082 if (rec->next == rec)
1083 NS_ADDREF(rec);
1084 else {
1085 PR_REMOVE_LINK(rec);
1086 mEvictionQSize--;
1089 switch (nsHostRecord::GetPriority(rec->flags)) {
1090 case nsHostRecord::DNS_PRIORITY_HIGH:
1091 PR_APPEND_LINK(rec, &mHighQ);
1092 break;
1094 case nsHostRecord::DNS_PRIORITY_MEDIUM:
1095 PR_APPEND_LINK(rec, &mMediumQ);
1096 break;
1098 case nsHostRecord::DNS_PRIORITY_LOW:
1099 PR_APPEND_LINK(rec, &mLowQ);
1100 break;
1102 mPendingCount++;
1104 rec->resolving = true;
1105 rec->onQueue = true;
1107 rv = ConditionallyCreateThread(rec);
1109 LOG ((" DNS thread counters: total=%d any-live=%d idle=%d pending=%d\n",
1110 mThreadCount,
1111 mActiveAnyThreadCount,
1112 mNumIdleThreads,
1113 mPendingCount));
1115 return rv;
1118 nsresult
1119 nsHostResolver::ConditionallyRefreshRecord(nsHostRecord *rec, const char *host)
1121 if ((rec->CheckExpiration(TimeStamp::NowLoRes()) != nsHostRecord::EXP_VALID
1122 || rec->negative) && !rec->resolving) {
1123 LOG((" Using %s cache entry for host [%s] but starting async renewal.",
1124 rec->negative ? "negative" :"positive", host));
1125 IssueLookup(rec);
1127 if (!rec->negative) {
1128 // negative entries are constantly being refreshed, only
1129 // track positive grace period induced renewals
1130 Telemetry::Accumulate(Telemetry::DNS_LOOKUP_METHOD2,
1131 METHOD_RENEWAL);
1134 return NS_OK;
1137 void
1138 nsHostResolver::DeQueue(PRCList &aQ, nsHostRecord **aResult)
1140 *aResult = static_cast<nsHostRecord *>(aQ.next);
1141 PR_REMOVE_AND_INIT_LINK(*aResult);
1142 mPendingCount--;
1143 (*aResult)->onQueue = false;
1146 bool
1147 nsHostResolver::GetHostToLookup(nsHostRecord **result)
1149 bool timedOut = false;
1150 PRIntervalTime epoch, now, timeout;
1152 MutexAutoLock lock(mLock);
1154 timeout = (mNumIdleThreads >= HighThreadThreshold) ? mShortIdleTimeout : mLongIdleTimeout;
1155 epoch = PR_IntervalNow();
1157 while (!mShutdown) {
1158 // remove next record from Q; hand over owning reference. Check high, then med, then low
1160 #if TTL_AVAILABLE
1161 #define SET_GET_TTL(var, val) \
1162 (var)->mGetTtl = sGetTtlEnabled && (val)
1163 #else
1164 #define SET_GET_TTL(var, val)
1165 #endif
1167 if (!PR_CLIST_IS_EMPTY(&mHighQ)) {
1168 DeQueue (mHighQ, result);
1169 SET_GET_TTL(*result, false);
1170 return true;
1173 if (mActiveAnyThreadCount < HighThreadThreshold) {
1174 if (!PR_CLIST_IS_EMPTY(&mMediumQ)) {
1175 DeQueue (mMediumQ, result);
1176 mActiveAnyThreadCount++;
1177 (*result)->usingAnyThread = true;
1178 SET_GET_TTL(*result, true);
1179 return true;
1182 if (!PR_CLIST_IS_EMPTY(&mLowQ)) {
1183 DeQueue (mLowQ, result);
1184 mActiveAnyThreadCount++;
1185 (*result)->usingAnyThread = true;
1186 SET_GET_TTL(*result, true);
1187 return true;
1191 // Determining timeout is racy, so allow one cycle through checking the queues
1192 // before exiting.
1193 if (timedOut)
1194 break;
1196 // wait for one or more of the following to occur:
1197 // (1) the pending queue has a host record to process
1198 // (2) the shutdown flag has been set
1199 // (3) the thread has been idle for too long
1201 mNumIdleThreads++;
1202 mIdleThreadCV.Wait(timeout);
1203 mNumIdleThreads--;
1205 now = PR_IntervalNow();
1207 if ((PRIntervalTime)(now - epoch) >= timeout)
1208 timedOut = true;
1209 else {
1210 // It is possible that PR_WaitCondVar() was interrupted and returned early,
1211 // in which case we will loop back and re-enter it. In that case we want to
1212 // do so with the new timeout reduced to reflect time already spent waiting.
1213 timeout -= (PRIntervalTime)(now - epoch);
1214 epoch = now;
1218 // tell thread to exit...
1219 mThreadCount--;
1220 return false;
1223 void
1224 nsHostResolver::PrepareRecordExpiration(nsHostRecord* rec) const
1226 MOZ_ASSERT(((bool)rec->addr_info) != rec->negative);
1227 if (!rec->addr_info) {
1228 rec->SetExpiration(TimeStamp::NowLoRes(),
1229 NEGATIVE_RECORD_LIFETIME, 0);
1230 LOG(("Caching host [%s%s%s] negative record for %u seconds.\n",
1231 LOG_HOST(rec->host, rec->netInterface),
1232 NEGATIVE_RECORD_LIFETIME));
1233 return;
1236 unsigned int lifetime = mDefaultCacheLifetime;
1237 unsigned int grace = mDefaultGracePeriod;
1238 #if TTL_AVAILABLE
1239 unsigned int ttl = mDefaultCacheLifetime;
1240 if (sGetTtlEnabled) {
1241 MutexAutoLock lock(rec->addr_info_lock);
1242 if (rec->addr_info && rec->addr_info->ttl != AddrInfo::NO_TTL_DATA) {
1243 ttl = rec->addr_info->ttl;
1245 lifetime = ttl;
1246 grace = 0;
1248 #endif
1250 rec->SetExpiration(TimeStamp::NowLoRes(), lifetime, grace);
1251 LOG(("Caching host [%s%s%s] record for %u seconds (grace %d).",
1252 LOG_HOST(rec->host, rec->netInterface), lifetime, grace));
1256 // OnLookupComplete() checks if the resolving should be redone and if so it
1257 // returns LOOKUP_RESOLVEAGAIN, but only if 'status' is not NS_ERROR_ABORT.
1260 nsHostResolver::LookupStatus
1261 nsHostResolver::OnLookupComplete(nsHostRecord* rec, nsresult status, AddrInfo* result)
1263 // get the list of pending callbacks for this lookup, and notify
1264 // them that the lookup is complete.
1265 PRCList cbs;
1266 PR_INIT_CLIST(&cbs);
1268 MutexAutoLock lock(mLock);
1270 if (rec->mResolveAgain && (status != NS_ERROR_ABORT)) {
1271 rec->mResolveAgain = false;
1272 return LOOKUP_RESOLVEAGAIN;
1275 // grab list of callbacks to notify
1276 MoveCList(rec->callbacks, cbs);
1278 // update record fields. We might have a rec->addr_info already if a
1279 // previous lookup result expired and we're reresolving it..
1280 AddrInfo *old_addr_info;
1282 MutexAutoLock lock(rec->addr_info_lock);
1283 old_addr_info = rec->addr_info;
1284 rec->addr_info = result;
1285 rec->addr_info_gencnt++;
1287 delete old_addr_info;
1289 rec->negative = !rec->addr_info;
1290 PrepareRecordExpiration(rec);
1291 rec->resolving = false;
1293 if (rec->usingAnyThread) {
1294 mActiveAnyThreadCount--;
1295 rec->usingAnyThread = false;
1298 if (!mShutdown) {
1299 // add to mEvictionQ
1300 PR_APPEND_LINK(rec, &mEvictionQ);
1301 NS_ADDREF(rec);
1302 if (mEvictionQSize < mMaxCacheEntries)
1303 mEvictionQSize++;
1304 else {
1305 // remove first element on mEvictionQ
1306 nsHostRecord *head =
1307 static_cast<nsHostRecord *>(PR_LIST_HEAD(&mEvictionQ));
1308 PR_REMOVE_AND_INIT_LINK(head);
1309 PL_DHashTableRemove(&mDB, (nsHostKey *) head);
1311 if (!head->negative) {
1312 // record the age of the entry upon eviction.
1313 TimeDuration age = TimeStamp::NowLoRes() - head->mValidStart;
1314 Telemetry::Accumulate(Telemetry::DNS_CLEANUP_AGE,
1315 static_cast<uint32_t>(age.ToSeconds() / 60));
1318 // release reference to rec owned by mEvictionQ
1319 NS_RELEASE(head);
1321 #if TTL_AVAILABLE
1322 if (!rec->mGetTtl && !rec->resolving && sGetTtlEnabled) {
1323 LOG(("Issuing second async lookup for TTL for host [%s%s%s].",
1324 LOG_HOST(rec->host, rec->netInterface)));
1325 rec->flags =
1326 (rec->flags & ~RES_PRIORITY_MEDIUM) | RES_PRIORITY_LOW;
1327 DebugOnly<nsresult> rv = IssueLookup(rec);
1328 NS_WARN_IF_FALSE(NS_SUCCEEDED(rv),
1329 "Could not issue second async lookup for TTL.");
1331 #endif
1335 MOZ_EVENT_TRACER_DONE(rec, "net::dns::resolve");
1337 if (!PR_CLIST_IS_EMPTY(&cbs)) {
1338 PRCList *node = cbs.next;
1339 while (node != &cbs) {
1340 nsResolveHostCallback *callback =
1341 static_cast<nsResolveHostCallback *>(node);
1342 node = node->next;
1343 callback->OnLookupComplete(this, rec, status);
1344 // NOTE: callback must not be dereferenced after this point!!
1348 NS_RELEASE(rec);
1350 return LOOKUP_OK;
1353 void
1354 nsHostResolver::CancelAsyncRequest(const char *host,
1355 uint16_t flags,
1356 uint16_t af,
1357 const char *netInterface,
1358 nsIDNSListener *aListener,
1359 nsresult status)
1362 MutexAutoLock lock(mLock);
1364 // Lookup the host record associated with host, flags & address family
1365 nsHostKey key = { host, flags, af, netInterface };
1366 nsHostDBEnt *he = static_cast<nsHostDBEnt *>
1367 (PL_DHashTableLookup(&mDB, &key));
1368 if (he && he->rec) {
1369 nsHostRecord* recPtr = nullptr;
1370 PRCList *node = he->rec->callbacks.next;
1371 // Remove the first nsDNSAsyncRequest callback which matches the
1372 // supplied listener object
1373 while (node != &he->rec->callbacks) {
1374 nsResolveHostCallback *callback
1375 = static_cast<nsResolveHostCallback *>(node);
1376 if (callback && (callback->EqualsAsyncListener(aListener))) {
1377 // Remove from the list of callbacks
1378 PR_REMOVE_LINK(callback);
1379 recPtr = he->rec;
1380 callback->OnLookupComplete(this, recPtr, status);
1381 break;
1383 node = node->next;
1386 // If there are no more callbacks, remove the hash table entry
1387 if (recPtr && PR_CLIST_IS_EMPTY(&recPtr->callbacks)) {
1388 PL_DHashTableRemove(&mDB, (nsHostKey *)recPtr);
1389 // If record is on a Queue, remove it and then deref it
1390 if (recPtr->next != recPtr) {
1391 PR_REMOVE_LINK(recPtr);
1392 NS_RELEASE(recPtr);
1398 static size_t
1399 SizeOfHostDBEntExcludingThis(PLDHashEntryHdr* hdr, MallocSizeOf mallocSizeOf,
1400 void*)
1402 nsHostDBEnt* ent = static_cast<nsHostDBEnt*>(hdr);
1403 return ent->rec->SizeOfIncludingThis(mallocSizeOf);
1406 size_t
1407 nsHostResolver::SizeOfIncludingThis(MallocSizeOf mallocSizeOf) const
1409 MutexAutoLock lock(mLock);
1411 size_t n = mallocSizeOf(this);
1412 n += PL_DHashTableSizeOfExcludingThis(&mDB, SizeOfHostDBEntExcludingThis,
1413 mallocSizeOf);
1415 // The following fields aren't measured.
1416 // - mHighQ, mMediumQ, mLowQ, mEvictionQ, because they just point to
1417 // nsHostRecords that also pointed to by entries |mDB|, and measured when
1418 // |mDB| is measured.
1420 return n;
1423 void
1424 nsHostResolver::ThreadFunc(void *arg)
1426 LOG(("DNS lookup thread - starting execution.\n"));
1428 static nsThreadPoolNaming naming;
1429 naming.SetThreadPoolName(NS_LITERAL_CSTRING("DNS Resolver"));
1431 #if defined(RES_RETRY_ON_FAILURE)
1432 nsResState rs;
1433 #endif
1434 nsHostResolver *resolver = (nsHostResolver *)arg;
1435 nsHostRecord *rec = nullptr;
1436 AddrInfo *ai = nullptr;
1438 while (rec || resolver->GetHostToLookup(&rec)) {
1439 LOG(("DNS lookup thread - Calling getaddrinfo for host [%s%s%s].\n",
1440 LOG_HOST(rec->host, rec->netInterface)));
1442 TimeStamp startTime = TimeStamp::Now();
1443 MOZ_EVENT_TRACER_EXEC(rec, "net::dns::resolve");
1445 #if TTL_AVAILABLE
1446 bool getTtl = rec->mGetTtl;
1447 #else
1448 bool getTtl = false;
1449 #endif
1451 // We need to remove IPv4 records manually
1452 // because PR_GetAddrInfoByName doesn't support PR_AF_INET6.
1453 bool disableIPv4 = rec->af == PR_AF_INET6;
1454 uint16_t af = disableIPv4 ? PR_AF_UNSPEC : rec->af;
1455 nsresult status = GetAddrInfo(rec->host, af, rec->flags, rec->netInterface,
1456 &ai, getTtl);
1457 #if defined(RES_RETRY_ON_FAILURE)
1458 if (NS_FAILED(status) && rs.Reset()) {
1459 status = GetAddrInfo(rec->host, af, rec->flags, rec->netInterface, &ai,
1460 getTtl);
1462 #endif
1464 TimeDuration elapsed = TimeStamp::Now() - startTime;
1465 uint32_t millis = static_cast<uint32_t>(elapsed.ToMilliseconds());
1467 if (NS_SUCCEEDED(status)) {
1468 Telemetry::ID histogramID;
1469 if (!rec->addr_info_gencnt) {
1470 // Time for initial lookup.
1471 histogramID = Telemetry::DNS_LOOKUP_TIME;
1472 } else if (!getTtl) {
1473 // Time for renewal; categorized by expiration strategy.
1474 histogramID = Telemetry::DNS_RENEWAL_TIME;
1475 } else {
1476 // Time to get TTL; categorized by expiration strategy.
1477 histogramID = Telemetry::DNS_RENEWAL_TIME_FOR_TTL;
1479 Telemetry::Accumulate(histogramID, millis);
1480 } else {
1481 Telemetry::Accumulate(Telemetry::DNS_FAILED_LOOKUP_TIME, millis);
1484 // OnLookupComplete may release "rec", long before we lose it.
1485 LOG(("DNS lookup thread - lookup completed for host [%s%s%s]: %s.\n",
1486 LOG_HOST(rec->host, rec->netInterface),
1487 ai ? "success" : "failure: unknown host"));
1489 if (LOOKUP_RESOLVEAGAIN == resolver->OnLookupComplete(rec, status, ai)) {
1490 // leave 'rec' assigned and loop to make a renewed host resolve
1491 LOG(("DNS lookup thread - Re-resolving host [%s%s%s].\n",
1492 LOG_HOST(rec->host, rec->netInterface)));
1493 } else {
1494 rec = nullptr;
1497 NS_RELEASE(resolver);
1498 LOG(("DNS lookup thread - queue empty, thread finished.\n"));
1501 nsresult
1502 nsHostResolver::Create(uint32_t maxCacheEntries,
1503 uint32_t defaultCacheEntryLifetime,
1504 uint32_t defaultGracePeriod,
1505 nsHostResolver **result)
1507 #if defined(PR_LOGGING)
1508 if (!gHostResolverLog)
1509 gHostResolverLog = PR_NewLogModule("nsHostResolver");
1510 #endif
1512 nsHostResolver *res = new nsHostResolver(maxCacheEntries, defaultCacheEntryLifetime,
1513 defaultGracePeriod);
1514 if (!res)
1515 return NS_ERROR_OUT_OF_MEMORY;
1516 NS_ADDREF(res);
1518 nsresult rv = res->Init();
1519 if (NS_FAILED(rv))
1520 NS_RELEASE(res);
1522 *result = res;
1523 return rv;
1526 PLDHashOperator
1527 CacheEntryEnumerator(PLDHashTable *table, PLDHashEntryHdr *entry,
1528 uint32_t number, void *arg)
1530 // We don't pay attention to address literals, only resolved domains.
1531 // Also require a host.
1532 nsHostRecord *rec = static_cast<nsHostDBEnt*>(entry)->rec;
1533 MOZ_ASSERT(rec, "rec should never be null here!");
1534 if (!rec || !rec->addr_info || !rec->host) {
1535 return PL_DHASH_NEXT;
1538 DNSCacheEntries info;
1539 info.hostname = rec->host;
1540 info.family = rec->af;
1541 info.netInterface = rec->netInterface;
1542 info.expiration =
1543 (int64_t)(rec->mValidEnd - TimeStamp::NowLoRes()).ToSeconds();
1544 if (info.expiration <= 0) {
1545 // We only need valid DNS cache entries
1546 return PL_DHASH_NEXT;
1550 MutexAutoLock lock(rec->addr_info_lock);
1552 NetAddr *addr = nullptr;
1553 NetAddrElement *addrElement = rec->addr_info->mAddresses.getFirst();
1554 if (addrElement) {
1555 addr = &addrElement->mAddress;
1557 while (addr) {
1558 char buf[kIPv6CStrBufSize];
1559 if (NetAddrToString(addr, buf, sizeof(buf))) {
1560 info.hostaddr.AppendElement(buf);
1562 addr = nullptr;
1563 addrElement = addrElement->getNext();
1564 if (addrElement) {
1565 addr = &addrElement->mAddress;
1570 nsTArray<DNSCacheEntries> *args = static_cast<nsTArray<DNSCacheEntries> *>(arg);
1571 args->AppendElement(info);
1573 return PL_DHASH_NEXT;
1576 void
1577 nsHostResolver::GetDNSCacheEntries(nsTArray<DNSCacheEntries> *args)
1579 PL_DHashTableEnumerate(&mDB, CacheEntryEnumerator, args);