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)
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>
16 #define RES_RETRY_ON_FAILURE
20 #include "nsHostResolver.h"
22 #include "nsISupportsBase.h"
23 #include "nsISupportsUtils.h"
24 #include "nsAutoPtr.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
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
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)
74 //----------------------------------------------------------------------------
77 MoveCList(PRCList
&from
, PRCList
&to
)
79 if (!PR_CLIST_IS_EMPTY(&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.
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())
116 // reset no more than once per second
117 if (PR_IntervalToSeconds(PR_IntervalNow() - mLastReset
) < 1)
120 LOG(("Calling 'res_ninit'.\n"));
122 mLastReset
= PR_IntervalNow();
123 return (res_ninit(&_res
) == 0);
127 PRIntervalTime mLastReset
;
130 #endif // RES_RETRY_ON_FAILURE
132 //----------------------------------------------------------------------------
135 IsHighPriority(uint16_t flags
)
137 return !(flags
& (nsHostResolver::RES_PRIORITY_LOW
| nsHostResolver::RES_PRIORITY_MEDIUM
));
141 IsMediumPriority(uint16_t flags
)
143 return flags
& nsHostResolver::RES_PRIORITY_MEDIUM
;
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)
167 , usingAnyThread(false)
170 host
= ((char *) this) + sizeof(nsHostRecord
);
171 memcpy((char *) host
, key
->host
, strlen(key
->host
) + 1);
175 expiration
= TimeStamp::NowLoRes();
178 PR_INIT_CLIST(&callbacks
);
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
);
193 MOZ_EVENT_TRACER_NAME_OBJECT(*result
, key
->host
);
198 nsHostRecord::~nsHostRecord()
205 nsHostRecord::Blacklisted(NetAddr
*aQuery
)
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()) {
215 char buf
[kIPv6CStrBufSize
];
216 if (!NetAddrToString(aQuery
, buf
, sizeof(buf
))) {
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
));
232 nsHostRecord::ReportUnusable(NetAddr
*aAddress
)
235 LOG(("Adding address to blacklist for host [%s], host record [%p].\n", host
, this));
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
));
248 nsHostRecord::ResetBlacklist()
251 LOG(("Resetting blacklist for host [%s], host record [%p].\n", host
, this));
252 mBlacklistedItems
.Clear();
256 nsHostRecord::HasUsableResult(uint16_t queryFlags
) const
261 // don't use cached negative results for high priority queries.
262 if (negative
&& IsHighPriority(queryFlags
))
265 return addr_info
|| addr
|| negative
;
269 SizeOfResolveHostCallbackListExcludingHead(const PRCList
*head
,
270 MallocSizeOf mallocSizeOf
)
273 PRCList
*curr
= head
->next
;
274 while (curr
!= head
) {
275 nsResolveHostCallback
*callback
=
276 static_cast<nsResolveHostCallback
*>(curr
);
277 n
+= callback
->SizeOfIncludingThis(mallocSizeOf
);
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
);
304 //----------------------------------------------------------------------------
306 struct nsHostDBEnt
: PLDHashEntryHdr
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
);
319 HostDB_MatchEntry(PLDHashTable
*table
,
320 const PLDHashEntryHdr
*entry
,
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
;
332 HostDB_MoveEntry(PLDHashTable
*table
,
333 const PLDHashEntryHdr
*from
,
336 static_cast<nsHostDBEnt
*>(to
)->rec
=
337 static_cast<const nsHostDBEnt
*>(from
)->rec
;
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
));
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
];
364 addrElement
= hr
->addr_info
->mAddresses
.getFirst();
366 addrElement
= addrElement
->getNext();
370 NetAddrToString(&addrElement
->mAddress
, buf
, sizeof(buf
));
371 LOG((" [%s]\n", buf
));
382 HostDB_InitEntry(PLDHashTable
*table
,
383 PLDHashEntryHdr
*entry
,
386 nsHostDBEnt
*he
= static_cast<nsHostDBEnt
*>(entry
);
387 nsHostRecord::Create(static_cast<const nsHostKey
*>(key
), &he
->rec
);
391 static const PLDHashTableOps gHostDB_ops
=
399 PL_DHashFinalizeStub
,
403 static PLDHashOperator
404 HostDB_RemoveEntry(PLDHashTable
*table
,
405 PLDHashEntryHdr
*hdr
,
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")
424 , mActiveAnyThreadCount(0)
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
);
445 nsHostResolver::Init()
447 PL_DHashTableInit(&mDB
, &gHostDB_ops
, nullptr, sizeof(nsHostDBEnt
), 0);
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"));
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
);
475 OnLookupComplete(rec
, NS_ERROR_ABORT
, nullptr);
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
);
496 MoveCList(mHighQ
, pendingQHigh
);
497 MoveCList(mMediumQ
, pendingQMed
);
498 MoveCList(mLowQ
, pendingQLow
);
499 MoveCList(mEvictionQ
, evictionQ
);
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
);
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
)
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
);
549 nsHostResolver::ResolveHost(const char *host
,
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
);
572 rv
= NS_ERROR_NOT_INITIALIZED
;
574 // Used to try to parse to an IP address literal.
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...
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
,
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
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
,
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 "
644 IsMediumPriority(flags
) ? "medium" : "low", host
));
645 Telemetry::Accumulate(Telemetry::DNS_LOOKUP_METHOD2
,
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
) &&
667 "Valid host entries should contain a record");
668 if (PL_DHASH_ENTRY_IS_BUSY(unspecHe
) &&
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
686 NetAddrElement
*addrIter
=
687 unspecHe
->rec
->addr_info
->mAddresses
.getFirst();
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
)) {
704 Telemetry::Accumulate(Telemetry::DNS_LOOKUP_METHOD2
,
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
712 else if (af
== PR_AF_INET6
) {
713 LOG((" No AF_INET6 in AF_UNSPEC entry: "
714 "[%s] unknown host", host
));
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.
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
);
734 PR_REMOVE_AND_INIT_LINK(callback
);
737 LOG((" DNS lookup for host [%s] blocking pending "
738 "'getaddrinfo' query: callback [%p]",
744 LOG((" Host [%s] is being resolved. Appending callback [%p].",
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();
773 callback
->OnLookupComplete(this, result
, status
);
778 nsHostResolver::DetachCallback(const char *host
,
781 nsResolveHostCallback
*callback
,
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
));
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
);
806 // complete callback with the given status code; this would only be done if
807 // the record was in the process of being resolved.
809 callback
->OnLookupComplete(this, rec
, status
);
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
825 PRThread
*thr
= PR_CreateThread(PR_SYSTEM_THREAD
,
830 PR_UNJOINABLE_THREAD
,
835 return NS_ERROR_OUT_OF_MEMORY
;
838 #if defined(PR_LOGGING)
840 LOG((" Unable to find a thread for looking up host [%s].\n", rec
->host
));
846 nsHostResolver::IssueLookup(nsHostRecord
*rec
)
848 MOZ_EVENT_TRACER_WAIT(rec
, "net::dns::resolve");
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
)
863 if (IsHighPriority(rec
->flags
))
864 PR_APPEND_LINK(rec
, &mHighQ
);
865 else if (IsMediumPriority(rec
->flags
))
866 PR_APPEND_LINK(rec
, &mMediumQ
);
868 PR_APPEND_LINK(rec
, &mLowQ
);
871 rec
->resolving
= true;
874 rv
= ConditionallyCreateThread(rec
);
876 LOG ((" DNS thread counters: total=%d any-live=%d idle=%d pending=%d\n",
878 mActiveAnyThreadCount
,
886 nsHostResolver::ConditionallyRefreshRecord(nsHostRecord
*rec
, const char *host
)
888 if (((TimeStamp::NowLoRes() > rec
->expiration
) || rec
->negative
) &&
890 LOG((" Using %s cache entry for host [%s] but starting async renewal.",
891 rec
->negative
? "negative" :"positive", host
));
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
,
905 nsHostResolver::DeQueue(PRCList
&aQ
, nsHostRecord
**aResult
)
907 *aResult
= static_cast<nsHostRecord
*>(aQ
.next
);
908 PR_REMOVE_AND_INIT_LINK(*aResult
);
910 (*aResult
)->onQueue
= false;
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();
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
);
932 if (mActiveAnyThreadCount
< HighThreadThreshold
) {
933 if (!PR_CLIST_IS_EMPTY(&mMediumQ
)) {
934 DeQueue (mMediumQ
, result
);
935 mActiveAnyThreadCount
++;
936 (*result
)->usingAnyThread
= true;
940 if (!PR_CLIST_IS_EMPTY(&mLowQ
)) {
941 DeQueue (mLowQ
, result
);
942 mActiveAnyThreadCount
++;
943 (*result
)->usingAnyThread
= true;
948 // Determining timeout is racy, so allow one cycle through checking the queues
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
959 mIdleThreadCV
.Wait(timeout
);
962 now
= PR_IntervalNow();
964 if ((PRIntervalTime
)(now
- epoch
) >= timeout
)
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
);
975 // tell thread to exit...
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.
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();
1006 rec
->expiration
+= mMaxCacheLifetime
;
1007 rec
->negative
= false;
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;
1021 // add to mEvictionQ
1022 PR_APPEND_LINK(rec
, &mEvictionQ
);
1024 if (mEvictionQSize
< mMaxCacheEntries
)
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
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
);
1055 callback
->OnLookupComplete(this, rec
, status
);
1056 // NOTE: callback must not be dereferenced after this point!!
1064 nsHostResolver::CancelAsyncRequest(const char *host
,
1067 nsIDNSListener
*aListener
,
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
);
1089 callback
->OnLookupComplete(this, recPtr
, status
);
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
);
1108 SizeOfHostDBEntExcludingThis(PLDHashEntryHdr
* hdr
, MallocSizeOf mallocSizeOf
,
1111 nsHostDBEnt
* ent
= static_cast<nsHostDBEnt
*>(hdr
);
1112 return ent
->rec
->SizeOfIncludingThis(mallocSizeOf
);
1116 nsHostResolver::SizeOfIncludingThis(MallocSizeOf mallocSizeOf
) const
1118 MutexAutoLock
lock(mLock
);
1120 size_t n
= mallocSizeOf(this);
1121 n
+= PL_DHashTableSizeOfExcludingThis(&mDB
, SizeOfHostDBEntExcludingThis
,
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.
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)
1143 nsHostResolver
*resolver
= (nsHostResolver
*)arg
;
1145 PRAddrInfo
*prai
= nullptr;
1146 while (resolver
->GetHostToLookup(&rec
)) {
1147 LOG(("DNS lookup thread - Calling getaddrinfo for host [%s].\n",
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
);
1167 TimeDuration elapsed
= TimeStamp::Now() - startTime
;
1168 uint32_t millis
= static_cast<uint32_t>(elapsed
.ToMilliseconds());
1170 // convert error code to nsresult
1172 AddrInfo
*ai
= nullptr;
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()) {
1187 Telemetry::Accumulate(!rec
->addr_info_gencnt
?
1188 Telemetry::DNS_LOOKUP_TIME
:
1189 Telemetry::DNS_RENEWAL_TIME
,
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"));
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");
1217 nsHostResolver
*res
= new nsHostResolver(maxCacheEntries
,
1219 lifetimeGracePeriod
);
1221 return NS_ERROR_OUT_OF_MEMORY
;
1224 nsresult rv
= res
->Init();
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();
1259 addr
= &addrElement
->mAddress
;
1262 char buf
[kIPv6CStrBufSize
];
1263 if (NetAddrToString(addr
, buf
, sizeof(buf
))) {
1264 info
.hostaddr
.AppendElement(buf
);
1267 addrElement
= addrElement
->getNext();
1269 addr
= &addrElement
->mAddress
;
1274 nsTArray
<DNSCacheEntries
> *args
= static_cast<nsTArray
<DNSCacheEntries
> *>(arg
);
1275 args
->AppendElement(info
);
1277 return PL_DHASH_NEXT
;
1281 nsHostResolver::GetDNSCacheEntries(nsTArray
<DNSCacheEntries
> *args
)
1283 PL_DHashTableEnumerate(&mDB
, CacheEntryEnumerator
, args
);