Bumping manifests a=b2g-bump
[gecko.git] / netwerk / dns / nsHostResolver.cpp
blob1808fd1e3352d164643d619d0c29eb6d8384ba96
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(MOZ_LOGGING)
7 #define FORCE_PR_LOG
8 #endif
10 #if defined(HAVE_RES_NINIT)
11 #include <sys/types.h>
12 #include <netinet/in.h>
13 #include <arpa/inet.h>
14 #include <arpa/nameser.h>
15 #include <resolv.h>
16 #define RES_RETRY_ON_FAILURE
17 #endif
19 #include <stdlib.h>
20 #include "nsHostResolver.h"
21 #include "nsError.h"
22 #include "nsISupportsBase.h"
23 #include "nsISupportsUtils.h"
24 #include "nsAutoPtr.h"
25 #include "prthread.h"
26 #include "prerror.h"
27 #include "prtime.h"
28 #include "prlog.h"
29 #include "pldhash.h"
30 #include "plstr.h"
31 #include "nsURLHelper.h"
32 #include "nsThreadUtils.h"
34 #include "mozilla/HashFunctions.h"
35 #include "mozilla/TimeStamp.h"
36 #include "mozilla/Telemetry.h"
37 #include "mozilla/VisualEventTracer.h"
39 using namespace mozilla;
40 using namespace mozilla::net;
42 //----------------------------------------------------------------------------
44 // Use a persistent thread pool in order to avoid spinning up new threads all the time.
45 // In particular, thread creation results in a res_init() call from libc which is
46 // quite expensive.
48 // The pool dynamically grows between 0 and MAX_RESOLVER_THREADS in size. New requests
49 // go first to an idle thread. If that cannot be found and there are fewer than MAX_RESOLVER_THREADS
50 // currently in the pool a new thread is created for high priority requests. If
51 // the new request is at a lower priority a new thread will only be created if
52 // there are fewer than HighThreadThreshold currently outstanding. If a thread cannot be
53 // created or an idle thread located for the request it is queued.
55 // When the pool is greater than HighThreadThreshold in size a thread will be destroyed after
56 // ShortIdleTimeoutSeconds of idle time. Smaller pools use LongIdleTimeoutSeconds for a
57 // timeout period.
59 #define HighThreadThreshold MAX_RESOLVER_THREADS_FOR_ANY_PRIORITY
60 #define LongIdleTimeoutSeconds 300 // for threads 1 -> HighThreadThreshold
61 #define ShortIdleTimeoutSeconds 60 // for threads HighThreadThreshold+1 -> MAX_RESOLVER_THREADS
63 PR_STATIC_ASSERT (HighThreadThreshold <= MAX_RESOLVER_THREADS);
65 //----------------------------------------------------------------------------
67 #if defined(PR_LOGGING)
68 static PRLogModuleInfo *gHostResolverLog = nullptr;
69 #define LOG(args) PR_LOG(gHostResolverLog, PR_LOG_DEBUG, args)
70 #else
71 #define LOG(args)
72 #endif
74 //----------------------------------------------------------------------------
76 static inline void
77 MoveCList(PRCList &from, PRCList &to)
79 if (!PR_CLIST_IS_EMPTY(&from)) {
80 to.next = from.next;
81 to.prev = from.prev;
82 to.next->prev = &to;
83 to.prev->next = &to;
84 PR_INIT_CLIST(&from);
88 //----------------------------------------------------------------------------
90 #if defined(RES_RETRY_ON_FAILURE)
92 // this class represents the resolver state for a given thread. if we
93 // encounter a lookup failure, then we can invoke the Reset method on an
94 // instance of this class to reset the resolver (in case /etc/resolv.conf
95 // for example changed). this is mainly an issue on GNU systems since glibc
96 // only reads in /etc/resolv.conf once per thread. it may be an issue on
97 // other systems as well.
99 class nsResState
101 public:
102 nsResState()
103 // initialize mLastReset to the time when this object
104 // is created. this means that a reset will not occur
105 // if a thread is too young. the alternative would be
106 // to initialize this to the beginning of time, so that
107 // the first failure would cause a reset, but since the
108 // thread would have just started up, it likely would
109 // already have current /etc/resolv.conf info.
110 : mLastReset(PR_IntervalNow())
114 bool Reset()
116 // reset no more than once per second
117 if (PR_IntervalToSeconds(PR_IntervalNow() - mLastReset) < 1)
118 return false;
120 LOG(("Calling 'res_ninit'.\n"));
122 mLastReset = PR_IntervalNow();
123 return (res_ninit(&_res) == 0);
126 private:
127 PRIntervalTime mLastReset;
130 #endif // RES_RETRY_ON_FAILURE
132 //----------------------------------------------------------------------------
134 static inline bool
135 IsHighPriority(uint16_t flags)
137 return !(flags & (nsHostResolver::RES_PRIORITY_LOW | nsHostResolver::RES_PRIORITY_MEDIUM));
140 static inline bool
141 IsMediumPriority(uint16_t flags)
143 return flags & nsHostResolver::RES_PRIORITY_MEDIUM;
146 static inline bool
147 IsLowPriority(uint16_t flags)
149 return flags & nsHostResolver::RES_PRIORITY_LOW;
152 //----------------------------------------------------------------------------
154 // this macro filters out any flags that are not used when constructing the
155 // host key. the significant flags are those that would affect the resulting
156 // host record (i.e., the flags that are passed down to PR_GetAddrInfoByName).
157 #define RES_KEY_FLAGS(_f) ((_f) & nsHostResolver::RES_CANON_NAME)
159 nsHostRecord::nsHostRecord(const nsHostKey *key)
160 : addr_info_lock("nsHostRecord.addr_info_lock")
161 , addr_info_gencnt(0)
162 , addr_info(nullptr)
163 , addr(nullptr)
164 , negative(false)
165 , resolving(false)
166 , onQueue(false)
167 , usingAnyThread(false)
168 , mDoomed(false)
170 host = ((char *) this) + sizeof(nsHostRecord);
171 memcpy((char *) host, key->host, strlen(key->host) + 1);
172 flags = key->flags;
173 af = key->af;
175 expiration = TimeStamp::NowLoRes();
177 PR_INIT_CLIST(this);
178 PR_INIT_CLIST(&callbacks);
181 nsresult
182 nsHostRecord::Create(const nsHostKey *key, nsHostRecord **result)
184 size_t hostLen = strlen(key->host) + 1;
185 size_t size = hostLen + sizeof(nsHostRecord);
187 // Use placement new to create the object with room for the hostname
188 // allocated after it.
189 void *place = ::operator new(size);
190 *result = new(place) nsHostRecord(key);
191 NS_ADDREF(*result);
193 MOZ_EVENT_TRACER_NAME_OBJECT(*result, key->host);
195 return NS_OK;
198 nsHostRecord::~nsHostRecord()
200 delete addr_info;
201 delete addr;
204 bool
205 nsHostRecord::Blacklisted(NetAddr *aQuery)
207 // must call locked
208 LOG(("Checking blacklist for host [%s], host record [%p].\n", host, this));
210 // skip the string conversion for the common case of no blacklist
211 if (!mBlacklistedItems.Length()) {
212 return false;
215 char buf[kIPv6CStrBufSize];
216 if (!NetAddrToString(aQuery, buf, sizeof(buf))) {
217 return false;
219 nsDependentCString strQuery(buf);
221 for (uint32_t i = 0; i < mBlacklistedItems.Length(); i++) {
222 if (mBlacklistedItems.ElementAt(i).Equals(strQuery)) {
223 LOG(("Address [%s] is blacklisted for host [%s].\n", buf, host));
224 return true;
228 return false;
231 void
232 nsHostRecord::ReportUnusable(NetAddr *aAddress)
234 // must call locked
235 LOG(("Adding address to blacklist for host [%s], host record [%p].\n", host, this));
237 if (negative)
238 mDoomed = true;
240 char buf[kIPv6CStrBufSize];
241 if (NetAddrToString(aAddress, buf, sizeof(buf))) {
242 LOG(("Successfully adding address [%s] to blacklist for host [%s].\n", buf, host));
243 mBlacklistedItems.AppendElement(nsCString(buf));
247 void
248 nsHostRecord::ResetBlacklist()
250 // must call locked
251 LOG(("Resetting blacklist for host [%s], host record [%p].\n", host, this));
252 mBlacklistedItems.Clear();
255 bool
256 nsHostRecord::HasUsableResult(uint16_t queryFlags) const
258 if (mDoomed)
259 return false;
261 // don't use cached negative results for high priority queries.
262 if (negative && IsHighPriority(queryFlags))
263 return false;
265 return addr_info || addr || negative;
268 static size_t
269 SizeOfResolveHostCallbackListExcludingHead(const PRCList *head,
270 MallocSizeOf mallocSizeOf)
272 size_t n = 0;
273 PRCList *curr = head->next;
274 while (curr != head) {
275 nsResolveHostCallback *callback =
276 static_cast<nsResolveHostCallback*>(curr);
277 n += callback->SizeOfIncludingThis(mallocSizeOf);
278 curr = curr->next;
280 return n;
283 size_t
284 nsHostRecord::SizeOfIncludingThis(MallocSizeOf mallocSizeOf) const
286 size_t n = mallocSizeOf(this);
288 // The |host| field (inherited from nsHostKey) actually points to extra
289 // memory that is allocated beyond the end of the nsHostRecord (see
290 // nsHostRecord::Create()). So it will be included in the
291 // |mallocSizeOf(this)| call above.
293 n += SizeOfResolveHostCallbackListExcludingHead(&callbacks, mallocSizeOf);
294 n += addr_info ? addr_info->SizeOfIncludingThis(mallocSizeOf) : 0;
295 n += mallocSizeOf(addr);
297 n += mBlacklistedItems.SizeOfExcludingThis(mallocSizeOf);
298 for (size_t i = 0; i < mBlacklistedItems.Length(); i++) {
299 n += mBlacklistedItems[i].SizeOfExcludingThisMustBeUnshared(mallocSizeOf);
301 return n;
304 //----------------------------------------------------------------------------
306 struct nsHostDBEnt : PLDHashEntryHdr
308 nsHostRecord *rec;
311 static PLDHashNumber
312 HostDB_HashKey(PLDHashTable *table, const void *key)
314 const nsHostKey *hk = static_cast<const nsHostKey *>(key);
315 return AddToHash(HashString(hk->host), RES_KEY_FLAGS(hk->flags), hk->af);
318 static bool
319 HostDB_MatchEntry(PLDHashTable *table,
320 const PLDHashEntryHdr *entry,
321 const void *key)
323 const nsHostDBEnt *he = static_cast<const nsHostDBEnt *>(entry);
324 const nsHostKey *hk = static_cast<const nsHostKey *>(key);
326 return !strcmp(he->rec->host, hk->host) &&
327 RES_KEY_FLAGS (he->rec->flags) == RES_KEY_FLAGS(hk->flags) &&
328 he->rec->af == hk->af;
331 static void
332 HostDB_MoveEntry(PLDHashTable *table,
333 const PLDHashEntryHdr *from,
334 PLDHashEntryHdr *to)
336 static_cast<nsHostDBEnt *>(to)->rec =
337 static_cast<const nsHostDBEnt *>(from)->rec;
340 static void
341 HostDB_ClearEntry(PLDHashTable *table,
342 PLDHashEntryHdr *entry)
344 nsHostDBEnt *he = static_cast<nsHostDBEnt*>(entry);
345 MOZ_ASSERT(he, "nsHostDBEnt is null!");
347 nsHostRecord *hr = he->rec;
348 MOZ_ASSERT(hr, "nsHostDBEnt has null host record!");
350 LOG(("Clearing cache db entry for host [%s].\n", hr->host));
351 #if defined(DEBUG) && defined(PR_LOGGING)
353 MutexAutoLock lock(hr->addr_info_lock);
354 if (!hr->addr_info) {
355 LOG(("No address info for host [%s].\n", hr->host));
356 } else {
357 TimeDuration diff = hr->expiration - TimeStamp::NowLoRes();
358 LOG(("Record for [%s] expires in %f seconds.\n", hr->host, diff.ToSeconds()));
360 NetAddrElement *addrElement = nullptr;
361 char buf[kIPv6CStrBufSize];
362 do {
363 if (!addrElement) {
364 addrElement = hr->addr_info->mAddresses.getFirst();
365 } else {
366 addrElement = addrElement->getNext();
369 if (addrElement) {
370 NetAddrToString(&addrElement->mAddress, buf, sizeof(buf));
371 LOG((" [%s]\n", buf));
374 while (addrElement);
377 #endif
378 NS_RELEASE(he->rec);
381 static bool
382 HostDB_InitEntry(PLDHashTable *table,
383 PLDHashEntryHdr *entry,
384 const void *key)
386 nsHostDBEnt *he = static_cast<nsHostDBEnt *>(entry);
387 nsHostRecord::Create(static_cast<const nsHostKey *>(key), &he->rec);
388 return true;
391 static const PLDHashTableOps gHostDB_ops =
393 PL_DHashAllocTable,
394 PL_DHashFreeTable,
395 HostDB_HashKey,
396 HostDB_MatchEntry,
397 HostDB_MoveEntry,
398 HostDB_ClearEntry,
399 PL_DHashFinalizeStub,
400 HostDB_InitEntry,
403 static PLDHashOperator
404 HostDB_RemoveEntry(PLDHashTable *table,
405 PLDHashEntryHdr *hdr,
406 uint32_t number,
407 void *arg)
409 return PL_DHASH_REMOVE;
412 //----------------------------------------------------------------------------
414 nsHostResolver::nsHostResolver(uint32_t maxCacheEntries,
415 uint32_t maxCacheLifetime,
416 uint32_t lifetimeGracePeriod)
417 : mMaxCacheEntries(maxCacheEntries)
418 , mMaxCacheLifetime(TimeDuration::FromSeconds(maxCacheLifetime))
419 , mGracePeriod(TimeDuration::FromSeconds(lifetimeGracePeriod))
420 , mLock("nsHostResolver.mLock")
421 , mIdleThreadCV(mLock, "nsHostResolver.mIdleThreadCV")
422 , mNumIdleThreads(0)
423 , mThreadCount(0)
424 , mActiveAnyThreadCount(0)
425 , mEvictionQSize(0)
426 , mPendingCount(0)
427 , mShutdown(true)
429 mCreationTime = PR_Now();
430 PR_INIT_CLIST(&mHighQ);
431 PR_INIT_CLIST(&mMediumQ);
432 PR_INIT_CLIST(&mLowQ);
433 PR_INIT_CLIST(&mEvictionQ);
435 mLongIdleTimeout = PR_SecondsToInterval(LongIdleTimeoutSeconds);
436 mShortIdleTimeout = PR_SecondsToInterval(ShortIdleTimeoutSeconds);
439 nsHostResolver::~nsHostResolver()
441 PL_DHashTableFinish(&mDB);
444 nsresult
445 nsHostResolver::Init()
447 PL_DHashTableInit(&mDB, &gHostDB_ops, nullptr, sizeof(nsHostDBEnt), 0);
449 mShutdown = false;
451 #if defined(HAVE_RES_NINIT)
452 // We want to make sure the system is using the correct resolver settings,
453 // so we force it to reload those settings whenever we startup a subsequent
454 // nsHostResolver instance. We assume that there is no reason to do this
455 // for the first nsHostResolver instance since that is usually created
456 // during application startup.
457 static int initCount = 0;
458 if (initCount++ > 0) {
459 LOG(("Calling 'res_ninit'.\n"));
460 res_ninit(&_res);
462 #endif
463 return NS_OK;
466 void
467 nsHostResolver::ClearPendingQueue(PRCList *aPendingQ)
469 // loop through pending queue, erroring out pending lookups.
470 if (!PR_CLIST_IS_EMPTY(aPendingQ)) {
471 PRCList *node = aPendingQ->next;
472 while (node != aPendingQ) {
473 nsHostRecord *rec = static_cast<nsHostRecord *>(node);
474 node = node->next;
475 OnLookupComplete(rec, NS_ERROR_ABORT, nullptr);
480 void
481 nsHostResolver::Shutdown()
483 LOG(("Shutting down host resolver.\n"));
485 PRCList pendingQHigh, pendingQMed, pendingQLow, evictionQ;
486 PR_INIT_CLIST(&pendingQHigh);
487 PR_INIT_CLIST(&pendingQMed);
488 PR_INIT_CLIST(&pendingQLow);
489 PR_INIT_CLIST(&evictionQ);
492 MutexAutoLock lock(mLock);
494 mShutdown = true;
496 MoveCList(mHighQ, pendingQHigh);
497 MoveCList(mMediumQ, pendingQMed);
498 MoveCList(mLowQ, pendingQLow);
499 MoveCList(mEvictionQ, evictionQ);
500 mEvictionQSize = 0;
501 mPendingCount = 0;
503 if (mNumIdleThreads)
504 mIdleThreadCV.NotifyAll();
506 // empty host database
507 PL_DHashTableEnumerate(&mDB, HostDB_RemoveEntry, nullptr);
510 ClearPendingQueue(&pendingQHigh);
511 ClearPendingQueue(&pendingQMed);
512 ClearPendingQueue(&pendingQLow);
514 if (!PR_CLIST_IS_EMPTY(&evictionQ)) {
515 PRCList *node = evictionQ.next;
516 while (node != &evictionQ) {
517 nsHostRecord *rec = static_cast<nsHostRecord *>(node);
518 node = node->next;
519 NS_RELEASE(rec);
523 #ifdef NS_BUILD_REFCNT_LOGGING
525 // Logically join the outstanding worker threads with a timeout.
526 // Use this approach instead of PR_JoinThread() because that does
527 // not allow a timeout which may be necessary for a semi-responsive
528 // shutdown if the thread is blocked on a very slow DNS resolution.
529 // mThreadCount is read outside of mLock, but the worst case
530 // scenario for that race is one extra 25ms sleep.
532 PRIntervalTime delay = PR_MillisecondsToInterval(25);
533 PRIntervalTime stopTime = PR_IntervalNow() + PR_SecondsToInterval(20);
534 while (mThreadCount && PR_IntervalNow() < stopTime)
535 PR_Sleep(delay);
536 #endif
539 void
540 nsHostResolver::MoveQueue(nsHostRecord *aRec, PRCList &aDestQ)
542 NS_ASSERTION(aRec->onQueue, "Moving Host Record Not Currently Queued");
544 PR_REMOVE_LINK(aRec);
545 PR_APPEND_LINK(aRec, &aDestQ);
548 nsresult
549 nsHostResolver::ResolveHost(const char *host,
550 uint16_t flags,
551 uint16_t af,
552 nsResolveHostCallback *callback)
554 NS_ENSURE_TRUE(host && *host, NS_ERROR_UNEXPECTED);
556 LOG(("Resolving host [%s]%s.\n",
557 host, flags & RES_BYPASS_CACHE ? " - bypassing cache" : ""));
559 // ensure that we are working with a valid hostname before proceeding. see
560 // bug 304904 for details.
561 if (!net_IsValidHostName(nsDependentCString(host)))
562 return NS_ERROR_UNKNOWN_HOST;
564 // if result is set inside the lock, then we need to issue the
565 // callback before returning.
566 nsRefPtr<nsHostRecord> result;
567 nsresult status = NS_OK, rv = NS_OK;
569 MutexAutoLock lock(mLock);
571 if (mShutdown)
572 rv = NS_ERROR_NOT_INITIALIZED;
573 else {
574 // Used to try to parse to an IP address literal.
575 PRNetAddr tempAddr;
576 // Unfortunately, PR_StringToNetAddr does not properly initialize
577 // the output buffer in the case of IPv6 input. See bug 223145.
578 memset(&tempAddr, 0, sizeof(PRNetAddr));
580 // check to see if there is already an entry for this |host|
581 // in the hash table. if so, then check to see if we can't
582 // just reuse the lookup result. otherwise, if there are
583 // any pending callbacks, then add to pending callbacks queue,
584 // and return. otherwise, add ourselves as first pending
585 // callback, and proceed to do the lookup.
587 nsHostKey key = { host, flags, af };
588 nsHostDBEnt *he = static_cast<nsHostDBEnt *>
589 (PL_DHashTableOperate(&mDB, &key, PL_DHASH_ADD));
591 // if the record is null, then HostDB_InitEntry failed.
592 if (!he || !he->rec) {
593 LOG((" Out of memory: no cache entry for [%s].\n", host));
594 rv = NS_ERROR_OUT_OF_MEMORY;
596 // do we have a cached result that we can reuse?
597 else if (!(flags & RES_BYPASS_CACHE) &&
598 he->rec->HasUsableResult(flags) &&
599 TimeStamp::NowLoRes() <= (he->rec->expiration + mGracePeriod)) {
600 LOG((" Using cached record for host [%s].\n", host));
601 // put reference to host record on stack...
602 result = he->rec;
603 Telemetry::Accumulate(Telemetry::DNS_LOOKUP_METHOD2, METHOD_HIT);
605 // For entries that are in the grace period
606 // or all cached negative entries, use the cache but start a new
607 // lookup in the background
608 ConditionallyRefreshRecord(he->rec, host);
610 if (he->rec->negative) {
611 LOG((" Negative cache entry for[%s].\n", host));
612 Telemetry::Accumulate(Telemetry::DNS_LOOKUP_METHOD2,
613 METHOD_NEGATIVE_HIT);
614 status = NS_ERROR_UNKNOWN_HOST;
617 // if the host name is an IP address literal and has been parsed,
618 // go ahead and use it.
619 else if (he->rec->addr) {
620 LOG((" Using cached address for IP Literal [%s].\n", host));
621 Telemetry::Accumulate(Telemetry::DNS_LOOKUP_METHOD2,
622 METHOD_LITERAL);
623 result = he->rec;
625 // try parsing the host name as an IP address literal to short
626 // circuit full host resolution. (this is necessary on some
627 // platforms like Win9x. see bug 219376 for more details.)
628 else if (PR_StringToNetAddr(host, &tempAddr) == PR_SUCCESS) {
629 LOG((" Host is IP Literal [%s].\n", host));
630 // ok, just copy the result into the host record, and be done
631 // with it! ;-)
632 he->rec->addr = new NetAddr();
633 PRNetAddrToNetAddr(&tempAddr, he->rec->addr);
634 // put reference to host record on stack...
635 Telemetry::Accumulate(Telemetry::DNS_LOOKUP_METHOD2,
636 METHOD_LITERAL);
637 result = he->rec;
639 else if (mPendingCount >= MAX_NON_PRIORITY_REQUESTS &&
640 !IsHighPriority(flags) &&
641 !he->rec->resolving) {
642 LOG((" Lookup queue full: dropping %s priority request for "
643 "[%s].\n",
644 IsMediumPriority(flags) ? "medium" : "low", host));
645 Telemetry::Accumulate(Telemetry::DNS_LOOKUP_METHOD2,
646 METHOD_OVERFLOW);
647 // This is a lower priority request and we are swamped, so refuse it.
648 rv = NS_ERROR_DNS_LOOKUP_QUEUE_FULL;
650 else if (flags & RES_OFFLINE) {
651 LOG((" Offline request for [%s]; ignoring.\n", host));
652 rv = NS_ERROR_OFFLINE;
655 // If this is an IPV4 or IPV6 specific request, check if there is
656 // an AF_UNSPEC entry we can use. Otherwise, hit the resolver...
657 else if (!he->rec->resolving) {
658 if (!(flags & RES_BYPASS_CACHE) &&
659 ((af == PR_AF_INET) || (af == PR_AF_INET6))) {
660 // First, search for an entry with AF_UNSPEC
661 const nsHostKey unspecKey = { host, flags, PR_AF_UNSPEC };
662 nsHostDBEnt *unspecHe = static_cast<nsHostDBEnt *>
663 (PL_DHashTableOperate(&mDB, &unspecKey, PL_DHASH_LOOKUP));
664 NS_ASSERTION(PL_DHASH_ENTRY_IS_FREE(unspecHe) ||
665 (PL_DHASH_ENTRY_IS_BUSY(unspecHe) &&
666 unspecHe->rec),
667 "Valid host entries should contain a record");
668 if (PL_DHASH_ENTRY_IS_BUSY(unspecHe) &&
669 unspecHe->rec &&
670 unspecHe->rec->HasUsableResult(flags) &&
671 TimeStamp::NowLoRes() <= (he->rec->expiration + mGracePeriod)) {
673 MOZ_ASSERT(unspecHe->rec->addr_info || unspecHe->rec->negative,
674 "Entry should be resolved or negative.");
676 LOG((" Trying AF_UNSPEC entry for [%s] af: %s.\n",
677 host, (af == PR_AF_INET) ? "AF_INET" : "AF_INET6"));
679 he->rec->addr_info = nullptr;
680 if (unspecHe->rec->negative) {
681 he->rec->negative = unspecHe->rec->negative;
682 } else if (unspecHe->rec->addr_info) {
683 // Search for any valid address in the AF_UNSPEC entry
684 // in the cache (not blacklisted and from the right
685 // family).
686 NetAddrElement *addrIter =
687 unspecHe->rec->addr_info->mAddresses.getFirst();
688 while (addrIter) {
689 if ((af == addrIter->mAddress.inet.family) &&
690 !unspecHe->rec->Blacklisted(&addrIter->mAddress)) {
691 if (!he->rec->addr_info) {
692 he->rec->addr_info = new AddrInfo(
693 unspecHe->rec->addr_info->mHostName,
694 unspecHe->rec->addr_info->mCanonicalName);
696 he->rec->addr_info->AddAddress(
697 new NetAddrElement(*addrIter));
699 addrIter = addrIter->getNext();
702 if (he->rec->HasUsableResult(flags)) {
703 result = he->rec;
704 Telemetry::Accumulate(Telemetry::DNS_LOOKUP_METHOD2,
705 METHOD_HIT);
706 ConditionallyRefreshRecord(he->rec, host);
708 // For AF_INET6, a new lookup means another AF_UNSPEC
709 // lookup. We have already iterated through the
710 // AF_UNSPEC addresses, so we mark this record as
711 // negative.
712 else if (af == PR_AF_INET6) {
713 LOG((" No AF_INET6 in AF_UNSPEC entry: "
714 "[%s] unknown host", host));
715 result = he->rec;
716 he->rec->negative = true;
717 status = NS_ERROR_UNKNOWN_HOST;
718 Telemetry::Accumulate(Telemetry::DNS_LOOKUP_METHOD2,
719 METHOD_NEGATIVE_HIT);
723 // If no valid address was found in the cache or this is an
724 // AF_UNSPEC request, then start a new lookup.
725 if (!result) {
726 LOG((" No usable address in cache for [%s]", host));
727 // Add callback to the list of pending callbacks.
728 PR_APPEND_LINK(callback, &he->rec->callbacks);
729 he->rec->flags = flags;
730 rv = IssueLookup(he->rec);
731 Telemetry::Accumulate(Telemetry::DNS_LOOKUP_METHOD2,
732 METHOD_NETWORK_FIRST);
733 if (NS_FAILED(rv)) {
734 PR_REMOVE_AND_INIT_LINK(callback);
736 else {
737 LOG((" DNS lookup for host [%s] blocking pending "
738 "'getaddrinfo' query: callback [%p]",
739 host, callback));
743 else {
744 LOG((" Host [%s] is being resolved. Appending callback [%p].",
745 host, callback));
746 PR_APPEND_LINK(callback, &he->rec->callbacks);
747 if (he->rec->onQueue) {
748 Telemetry::Accumulate(Telemetry::DNS_LOOKUP_METHOD2,
749 METHOD_NETWORK_SHARED);
751 // Consider the case where we are on a pending queue of
752 // lower priority than the request is being made at.
753 // In that case we should upgrade to the higher queue.
755 if (IsHighPriority(flags) &&
756 !IsHighPriority(he->rec->flags)) {
757 // Move from (low|med) to high.
758 MoveQueue(he->rec, mHighQ);
759 he->rec->flags = flags;
760 ConditionallyCreateThread(he->rec);
761 } else if (IsMediumPriority(flags) &&
762 IsLowPriority(he->rec->flags)) {
763 // Move from low to med.
764 MoveQueue(he->rec, mMediumQ);
765 he->rec->flags = flags;
766 mIdleThreadCV.Notify();
772 if (result)
773 callback->OnLookupComplete(this, result, status);
774 return rv;
777 void
778 nsHostResolver::DetachCallback(const char *host,
779 uint16_t flags,
780 uint16_t af,
781 nsResolveHostCallback *callback,
782 nsresult status)
784 nsRefPtr<nsHostRecord> rec;
786 MutexAutoLock lock(mLock);
788 nsHostKey key = { host, flags, af };
789 nsHostDBEnt *he = static_cast<nsHostDBEnt *>
790 (PL_DHashTableOperate(&mDB, &key, PL_DHASH_LOOKUP));
791 if (he && he->rec) {
792 // walk list looking for |callback|... we cannot assume
793 // that it will be there!
794 PRCList *node = he->rec->callbacks.next;
795 while (node != &he->rec->callbacks) {
796 if (static_cast<nsResolveHostCallback *>(node) == callback) {
797 PR_REMOVE_LINK(callback);
798 rec = he->rec;
799 break;
801 node = node->next;
806 // complete callback with the given status code; this would only be done if
807 // the record was in the process of being resolved.
808 if (rec)
809 callback->OnLookupComplete(this, rec, status);
812 nsresult
813 nsHostResolver::ConditionallyCreateThread(nsHostRecord *rec)
815 if (mNumIdleThreads) {
816 // wake up idle thread to process this lookup
817 mIdleThreadCV.Notify();
819 else if ((mThreadCount < HighThreadThreshold) ||
820 (IsHighPriority(rec->flags) && mThreadCount < MAX_RESOLVER_THREADS)) {
821 // dispatch new worker thread
822 NS_ADDREF_THIS(); // owning reference passed to thread
824 mThreadCount++;
825 PRThread *thr = PR_CreateThread(PR_SYSTEM_THREAD,
826 ThreadFunc,
827 this,
828 PR_PRIORITY_NORMAL,
829 PR_GLOBAL_THREAD,
830 PR_UNJOINABLE_THREAD,
832 if (!thr) {
833 mThreadCount--;
834 NS_RELEASE_THIS();
835 return NS_ERROR_OUT_OF_MEMORY;
838 #if defined(PR_LOGGING)
839 else
840 LOG((" Unable to find a thread for looking up host [%s].\n", rec->host));
841 #endif
842 return NS_OK;
845 nsresult
846 nsHostResolver::IssueLookup(nsHostRecord *rec)
848 MOZ_EVENT_TRACER_WAIT(rec, "net::dns::resolve");
850 nsresult rv = NS_OK;
851 NS_ASSERTION(!rec->resolving, "record is already being resolved");
853 // Add rec to one of the pending queues, possibly removing it from mEvictionQ.
854 // If rec is on mEvictionQ, then we can just move the owning
855 // reference over to the new active queue.
856 if (rec->next == rec)
857 NS_ADDREF(rec);
858 else {
859 PR_REMOVE_LINK(rec);
860 mEvictionQSize--;
863 if (IsHighPriority(rec->flags))
864 PR_APPEND_LINK(rec, &mHighQ);
865 else if (IsMediumPriority(rec->flags))
866 PR_APPEND_LINK(rec, &mMediumQ);
867 else
868 PR_APPEND_LINK(rec, &mLowQ);
869 mPendingCount++;
871 rec->resolving = true;
872 rec->onQueue = true;
874 rv = ConditionallyCreateThread(rec);
876 LOG ((" DNS thread counters: total=%d any-live=%d idle=%d pending=%d\n",
877 mThreadCount,
878 mActiveAnyThreadCount,
879 mNumIdleThreads,
880 mPendingCount));
882 return rv;
885 nsresult
886 nsHostResolver::ConditionallyRefreshRecord(nsHostRecord *rec, const char *host)
888 if (((TimeStamp::NowLoRes() > rec->expiration) || rec->negative) &&
889 !rec->resolving) {
890 LOG((" Using %s cache entry for host [%s] but starting async renewal.",
891 rec->negative ? "negative" :"positive", host));
892 IssueLookup(rec);
894 if (!rec->negative) {
895 // negative entries are constantly being refreshed, only
896 // track positive grace period induced renewals
897 Telemetry::Accumulate(Telemetry::DNS_LOOKUP_METHOD2,
898 METHOD_RENEWAL);
901 return NS_OK;
904 void
905 nsHostResolver::DeQueue(PRCList &aQ, nsHostRecord **aResult)
907 *aResult = static_cast<nsHostRecord *>(aQ.next);
908 PR_REMOVE_AND_INIT_LINK(*aResult);
909 mPendingCount--;
910 (*aResult)->onQueue = false;
913 bool
914 nsHostResolver::GetHostToLookup(nsHostRecord **result)
916 bool timedOut = false;
917 PRIntervalTime epoch, now, timeout;
919 MutexAutoLock lock(mLock);
921 timeout = (mNumIdleThreads >= HighThreadThreshold) ? mShortIdleTimeout : mLongIdleTimeout;
922 epoch = PR_IntervalNow();
924 while (!mShutdown) {
925 // remove next record from Q; hand over owning reference. Check high, then med, then low
927 if (!PR_CLIST_IS_EMPTY(&mHighQ)) {
928 DeQueue (mHighQ, result);
929 return true;
932 if (mActiveAnyThreadCount < HighThreadThreshold) {
933 if (!PR_CLIST_IS_EMPTY(&mMediumQ)) {
934 DeQueue (mMediumQ, result);
935 mActiveAnyThreadCount++;
936 (*result)->usingAnyThread = true;
937 return true;
940 if (!PR_CLIST_IS_EMPTY(&mLowQ)) {
941 DeQueue (mLowQ, result);
942 mActiveAnyThreadCount++;
943 (*result)->usingAnyThread = true;
944 return true;
948 // Determining timeout is racy, so allow one cycle through checking the queues
949 // before exiting.
950 if (timedOut)
951 break;
953 // wait for one or more of the following to occur:
954 // (1) the pending queue has a host record to process
955 // (2) the shutdown flag has been set
956 // (3) the thread has been idle for too long
958 mNumIdleThreads++;
959 mIdleThreadCV.Wait(timeout);
960 mNumIdleThreads--;
962 now = PR_IntervalNow();
964 if ((PRIntervalTime)(now - epoch) >= timeout)
965 timedOut = true;
966 else {
967 // It is possible that PR_WaitCondVar() was interrupted and returned early,
968 // in which case we will loop back and re-enter it. In that case we want to
969 // do so with the new timeout reduced to reflect time already spent waiting.
970 timeout -= (PRIntervalTime)(now - epoch);
971 epoch = now;
975 // tell thread to exit...
976 mThreadCount--;
977 return false;
980 void
981 nsHostResolver::OnLookupComplete(nsHostRecord *rec, nsresult status, AddrInfo *result)
983 // get the list of pending callbacks for this lookup, and notify
984 // them that the lookup is complete.
985 PRCList cbs;
986 PR_INIT_CLIST(&cbs);
988 MutexAutoLock lock(mLock);
990 // grab list of callbacks to notify
991 MoveCList(rec->callbacks, cbs);
993 // update record fields. We might have a rec->addr_info already if a
994 // previous lookup result expired and we're reresolving it..
995 AddrInfo *old_addr_info;
997 MutexAutoLock lock(rec->addr_info_lock);
998 old_addr_info = rec->addr_info;
999 rec->addr_info = result;
1000 rec->addr_info_gencnt++;
1002 delete old_addr_info;
1004 rec->expiration = TimeStamp::NowLoRes();
1005 if (result) {
1006 rec->expiration += mMaxCacheLifetime;
1007 rec->negative = false;
1009 else {
1010 rec->expiration += TimeDuration::FromSeconds(60); /* one minute for negative cache */
1011 rec->negative = true;
1013 rec->resolving = false;
1015 if (rec->usingAnyThread) {
1016 mActiveAnyThreadCount--;
1017 rec->usingAnyThread = false;
1020 if (!mShutdown) {
1021 // add to mEvictionQ
1022 PR_APPEND_LINK(rec, &mEvictionQ);
1023 NS_ADDREF(rec);
1024 if (mEvictionQSize < mMaxCacheEntries)
1025 mEvictionQSize++;
1026 else {
1027 // remove first element on mEvictionQ
1028 nsHostRecord *head =
1029 static_cast<nsHostRecord *>(PR_LIST_HEAD(&mEvictionQ));
1030 PR_REMOVE_AND_INIT_LINK(head);
1031 PL_DHashTableOperate(&mDB, (nsHostKey *) head, PL_DHASH_REMOVE);
1033 if (!head->negative) {
1034 // record the age of the entry upon eviction.
1035 TimeDuration age = TimeStamp::NowLoRes() -
1036 (head->expiration - mMaxCacheLifetime);
1037 Telemetry::Accumulate(Telemetry::DNS_CLEANUP_AGE,
1038 static_cast<uint32_t>(age.ToSeconds() / 60));
1041 // release reference to rec owned by mEvictionQ
1042 NS_RELEASE(head);
1047 MOZ_EVENT_TRACER_DONE(rec, "net::dns::resolve");
1049 if (!PR_CLIST_IS_EMPTY(&cbs)) {
1050 PRCList *node = cbs.next;
1051 while (node != &cbs) {
1052 nsResolveHostCallback *callback =
1053 static_cast<nsResolveHostCallback *>(node);
1054 node = node->next;
1055 callback->OnLookupComplete(this, rec, status);
1056 // NOTE: callback must not be dereferenced after this point!!
1060 NS_RELEASE(rec);
1063 void
1064 nsHostResolver::CancelAsyncRequest(const char *host,
1065 uint16_t flags,
1066 uint16_t af,
1067 nsIDNSListener *aListener,
1068 nsresult status)
1071 MutexAutoLock lock(mLock);
1073 // Lookup the host record associated with host, flags & address family
1074 nsHostKey key = { host, flags, af };
1075 nsHostDBEnt *he = static_cast<nsHostDBEnt *>
1076 (PL_DHashTableOperate(&mDB, &key, PL_DHASH_LOOKUP));
1077 if (he && he->rec) {
1078 nsHostRecord* recPtr = nullptr;
1079 PRCList *node = he->rec->callbacks.next;
1080 // Remove the first nsDNSAsyncRequest callback which matches the
1081 // supplied listener object
1082 while (node != &he->rec->callbacks) {
1083 nsResolveHostCallback *callback
1084 = static_cast<nsResolveHostCallback *>(node);
1085 if (callback && (callback->EqualsAsyncListener(aListener))) {
1086 // Remove from the list of callbacks
1087 PR_REMOVE_LINK(callback);
1088 recPtr = he->rec;
1089 callback->OnLookupComplete(this, recPtr, status);
1090 break;
1092 node = node->next;
1095 // If there are no more callbacks, remove the hash table entry
1096 if (recPtr && PR_CLIST_IS_EMPTY(&recPtr->callbacks)) {
1097 PL_DHashTableOperate(&mDB, (nsHostKey *)recPtr, PL_DHASH_REMOVE);
1098 // If record is on a Queue, remove it and then deref it
1099 if (recPtr->next != recPtr) {
1100 PR_REMOVE_LINK(recPtr);
1101 NS_RELEASE(recPtr);
1107 static size_t
1108 SizeOfHostDBEntExcludingThis(PLDHashEntryHdr* hdr, MallocSizeOf mallocSizeOf,
1109 void*)
1111 nsHostDBEnt* ent = static_cast<nsHostDBEnt*>(hdr);
1112 return ent->rec->SizeOfIncludingThis(mallocSizeOf);
1115 size_t
1116 nsHostResolver::SizeOfIncludingThis(MallocSizeOf mallocSizeOf) const
1118 MutexAutoLock lock(mLock);
1120 size_t n = mallocSizeOf(this);
1121 n += PL_DHashTableSizeOfExcludingThis(&mDB, SizeOfHostDBEntExcludingThis,
1122 mallocSizeOf);
1124 // The following fields aren't measured.
1125 // - mHighQ, mMediumQ, mLowQ, mEvictionQ, because they just point to
1126 // nsHostRecords that also pointed to by entries |mDB|, and measured when
1127 // |mDB| is measured.
1129 return n;
1132 void
1133 nsHostResolver::ThreadFunc(void *arg)
1135 LOG(("DNS lookup thread - starting execution.\n"));
1137 static nsThreadPoolNaming naming;
1138 naming.SetThreadPoolName(NS_LITERAL_CSTRING("DNS Resolver"));
1140 #if defined(RES_RETRY_ON_FAILURE)
1141 nsResState rs;
1142 #endif
1143 nsHostResolver *resolver = (nsHostResolver *)arg;
1144 nsHostRecord *rec;
1145 PRAddrInfo *prai = nullptr;
1146 while (resolver->GetHostToLookup(&rec)) {
1147 LOG(("DNS lookup thread - Calling getaddrinfo for host [%s].\n",
1148 rec->host));
1150 int flags = PR_AI_ADDRCONFIG;
1151 if (!(rec->flags & RES_CANON_NAME))
1152 flags |= PR_AI_NOCANONNAME;
1154 TimeStamp startTime = TimeStamp::Now();
1155 MOZ_EVENT_TRACER_EXEC(rec, "net::dns::resolve");
1157 // We need to remove IPv4 records manually
1158 // because PR_GetAddrInfoByName doesn't support PR_AF_INET6.
1159 bool disableIPv4 = rec->af == PR_AF_INET6;
1160 uint16_t af = disableIPv4 ? PR_AF_UNSPEC : rec->af;
1161 prai = PR_GetAddrInfoByName(rec->host, af, flags);
1162 #if defined(RES_RETRY_ON_FAILURE)
1163 if (!prai && rs.Reset())
1164 prai = PR_GetAddrInfoByName(rec->host, af, flags);
1165 #endif
1167 TimeDuration elapsed = TimeStamp::Now() - startTime;
1168 uint32_t millis = static_cast<uint32_t>(elapsed.ToMilliseconds());
1170 // convert error code to nsresult
1171 nsresult status;
1172 AddrInfo *ai = nullptr;
1173 if (prai) {
1174 const char *cname = nullptr;
1175 if (rec->flags & RES_CANON_NAME)
1176 cname = PR_GetCanonNameFromAddrInfo(prai);
1177 ai = new AddrInfo(rec->host, prai, disableIPv4, cname);
1178 PR_FreeAddrInfo(prai);
1179 if (ai->mAddresses.isEmpty()) {
1180 delete ai;
1181 ai = nullptr;
1184 if (ai) {
1185 status = NS_OK;
1187 Telemetry::Accumulate(!rec->addr_info_gencnt ?
1188 Telemetry::DNS_LOOKUP_TIME :
1189 Telemetry::DNS_RENEWAL_TIME,
1190 millis);
1192 else {
1193 status = NS_ERROR_UNKNOWN_HOST;
1194 Telemetry::Accumulate(Telemetry::DNS_FAILED_LOOKUP_TIME, millis);
1197 // OnLookupComplete may release "rec", log before we lose it.
1198 LOG(("DNS lookup thread - lookup completed for host [%s]: %s.\n",
1199 rec->host, ai ? "success" : "failure: unknown host"));
1200 resolver->OnLookupComplete(rec, status, ai);
1202 NS_RELEASE(resolver);
1203 LOG(("DNS lookup thread - queue empty, thread finished.\n"));
1206 nsresult
1207 nsHostResolver::Create(uint32_t maxCacheEntries,
1208 uint32_t maxCacheLifetime,
1209 uint32_t lifetimeGracePeriod,
1210 nsHostResolver **result)
1212 #if defined(PR_LOGGING)
1213 if (!gHostResolverLog)
1214 gHostResolverLog = PR_NewLogModule("nsHostResolver");
1215 #endif
1217 nsHostResolver *res = new nsHostResolver(maxCacheEntries,
1218 maxCacheLifetime,
1219 lifetimeGracePeriod);
1220 if (!res)
1221 return NS_ERROR_OUT_OF_MEMORY;
1222 NS_ADDREF(res);
1224 nsresult rv = res->Init();
1225 if (NS_FAILED(rv))
1226 NS_RELEASE(res);
1228 *result = res;
1229 return rv;
1232 PLDHashOperator
1233 CacheEntryEnumerator(PLDHashTable *table, PLDHashEntryHdr *entry,
1234 uint32_t number, void *arg)
1236 // We don't pay attention to address literals, only resolved domains.
1237 // Also require a host.
1238 nsHostRecord *rec = static_cast<nsHostDBEnt*>(entry)->rec;
1239 MOZ_ASSERT(rec, "rec should never be null here!");
1240 if (!rec || !rec->addr_info || !rec->host) {
1241 return PL_DHASH_NEXT;
1244 DNSCacheEntries info;
1245 info.hostname = rec->host;
1246 info.family = rec->af;
1247 info.expiration = (int64_t)(rec->expiration - TimeStamp::NowLoRes()).ToSeconds();
1248 if (info.expiration <= 0) {
1249 // We only need valid DNS cache entries
1250 return PL_DHASH_NEXT;
1254 MutexAutoLock lock(rec->addr_info_lock);
1256 NetAddr *addr = nullptr;
1257 NetAddrElement *addrElement = rec->addr_info->mAddresses.getFirst();
1258 if (addrElement) {
1259 addr = &addrElement->mAddress;
1261 while (addr) {
1262 char buf[kIPv6CStrBufSize];
1263 if (NetAddrToString(addr, buf, sizeof(buf))) {
1264 info.hostaddr.AppendElement(buf);
1266 addr = nullptr;
1267 addrElement = addrElement->getNext();
1268 if (addrElement) {
1269 addr = &addrElement->mAddress;
1274 nsTArray<DNSCacheEntries> *args = static_cast<nsTArray<DNSCacheEntries> *>(arg);
1275 args->AppendElement(info);
1277 return PL_DHASH_NEXT;
1280 void
1281 nsHostResolver::GetDNSCacheEntries(nsTArray<DNSCacheEntries> *args)
1283 PL_DHashTableEnumerate(&mDB, CacheEntryEnumerator, args);