mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
5d7f3c7007
Backed out changeset 009ae35b0c67 (bug 806819) Backed out changeset 5a57f87f5061 (bug 806819) Backed out changeset f06cd735b5b3 (bug 806819) Backed out changeset e25a2a8d4af4 (bug 806819) Backed out changeset 70a167982c3f (bug 806819)
1721 lines
57 KiB
C++
1721 lines
57 KiB
C++
/* vim:set ts=4 sw=4 sts=4 et cin: */
|
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
#if defined(MOZ_LOGGING)
|
|
#define FORCE_PR_LOG
|
|
#endif
|
|
|
|
#if defined(HAVE_RES_NINIT)
|
|
#include <sys/types.h>
|
|
#include <netinet/in.h>
|
|
#include <arpa/inet.h>
|
|
#include <arpa/nameser.h>
|
|
#include <resolv.h>
|
|
#define RES_RETRY_ON_FAILURE
|
|
#endif
|
|
|
|
#include <stdlib.h>
|
|
#include <ctime>
|
|
#include "nsHostResolver.h"
|
|
#include "nsError.h"
|
|
#include "nsISupportsBase.h"
|
|
#include "nsISupportsUtils.h"
|
|
#include "nsAutoPtr.h"
|
|
#include "prthread.h"
|
|
#include "prerror.h"
|
|
#include "prtime.h"
|
|
#include "prlog.h"
|
|
#include "pldhash.h"
|
|
#include "plstr.h"
|
|
#include "nsURLHelper.h"
|
|
#include "nsThreadUtils.h"
|
|
#include "GetAddrInfo.h"
|
|
|
|
#include "mozilla/HashFunctions.h"
|
|
#include "mozilla/TimeStamp.h"
|
|
#include "mozilla/Telemetry.h"
|
|
#include "mozilla/VisualEventTracer.h"
|
|
#include "mozilla/DebugOnly.h"
|
|
#include "mozilla/Preferences.h"
|
|
|
|
using namespace mozilla;
|
|
using namespace mozilla::net;
|
|
|
|
// None of our implementations expose a TTL for negative responses, so we use a
|
|
// constant always.
|
|
static const unsigned int NEGATIVE_RECORD_LIFETIME = 60;
|
|
|
|
//----------------------------------------------------------------------------
|
|
|
|
// Use a persistent thread pool in order to avoid spinning up new threads all the time.
|
|
// In particular, thread creation results in a res_init() call from libc which is
|
|
// quite expensive.
|
|
//
|
|
// The pool dynamically grows between 0 and MAX_RESOLVER_THREADS in size. New requests
|
|
// go first to an idle thread. If that cannot be found and there are fewer than MAX_RESOLVER_THREADS
|
|
// currently in the pool a new thread is created for high priority requests. If
|
|
// the new request is at a lower priority a new thread will only be created if
|
|
// there are fewer than HighThreadThreshold currently outstanding. If a thread cannot be
|
|
// created or an idle thread located for the request it is queued.
|
|
//
|
|
// When the pool is greater than HighThreadThreshold in size a thread will be destroyed after
|
|
// ShortIdleTimeoutSeconds of idle time. Smaller pools use LongIdleTimeoutSeconds for a
|
|
// timeout period.
|
|
|
|
#define HighThreadThreshold MAX_RESOLVER_THREADS_FOR_ANY_PRIORITY
|
|
#define LongIdleTimeoutSeconds 300 // for threads 1 -> HighThreadThreshold
|
|
#define ShortIdleTimeoutSeconds 60 // for threads HighThreadThreshold+1 -> MAX_RESOLVER_THREADS
|
|
|
|
PR_STATIC_ASSERT (HighThreadThreshold <= MAX_RESOLVER_THREADS);
|
|
|
|
//----------------------------------------------------------------------------
|
|
|
|
#if defined(PR_LOGGING)
|
|
static PRLogModuleInfo *gHostResolverLog = nullptr;
|
|
#define LOG(args) PR_LOG(gHostResolverLog, PR_LOG_DEBUG, args)
|
|
#else
|
|
#define LOG(args)
|
|
#endif
|
|
|
|
//----------------------------------------------------------------------------
|
|
|
|
static inline void
|
|
MoveCList(PRCList &from, PRCList &to)
|
|
{
|
|
if (!PR_CLIST_IS_EMPTY(&from)) {
|
|
to.next = from.next;
|
|
to.prev = from.prev;
|
|
to.next->prev = &to;
|
|
to.prev->next = &to;
|
|
PR_INIT_CLIST(&from);
|
|
}
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
|
|
#if defined(RES_RETRY_ON_FAILURE)
|
|
|
|
// this class represents the resolver state for a given thread. if we
|
|
// encounter a lookup failure, then we can invoke the Reset method on an
|
|
// instance of this class to reset the resolver (in case /etc/resolv.conf
|
|
// for example changed). this is mainly an issue on GNU systems since glibc
|
|
// only reads in /etc/resolv.conf once per thread. it may be an issue on
|
|
// other systems as well.
|
|
|
|
class nsResState
|
|
{
|
|
public:
|
|
nsResState()
|
|
// initialize mLastReset to the time when this object
|
|
// is created. this means that a reset will not occur
|
|
// if a thread is too young. the alternative would be
|
|
// to initialize this to the beginning of time, so that
|
|
// the first failure would cause a reset, but since the
|
|
// thread would have just started up, it likely would
|
|
// already have current /etc/resolv.conf info.
|
|
: mLastReset(PR_IntervalNow())
|
|
{
|
|
}
|
|
|
|
bool Reset()
|
|
{
|
|
// reset no more than once per second
|
|
if (PR_IntervalToSeconds(PR_IntervalNow() - mLastReset) < 1)
|
|
return false;
|
|
|
|
LOG(("Calling 'res_ninit'.\n"));
|
|
|
|
mLastReset = PR_IntervalNow();
|
|
return (res_ninit(&_res) == 0);
|
|
}
|
|
|
|
private:
|
|
PRIntervalTime mLastReset;
|
|
};
|
|
|
|
#endif // RES_RETRY_ON_FAILURE
|
|
|
|
//----------------------------------------------------------------------------
|
|
|
|
static inline bool
|
|
IsHighPriority(uint16_t flags)
|
|
{
|
|
return !(flags & (nsHostResolver::RES_PRIORITY_LOW | nsHostResolver::RES_PRIORITY_MEDIUM));
|
|
}
|
|
|
|
static inline bool
|
|
IsMediumPriority(uint16_t flags)
|
|
{
|
|
return flags & nsHostResolver::RES_PRIORITY_MEDIUM;
|
|
}
|
|
|
|
static inline bool
|
|
IsLowPriority(uint16_t flags)
|
|
{
|
|
return flags & nsHostResolver::RES_PRIORITY_LOW;
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
|
|
enum DnsExpirationVariant {
|
|
DNS_EXP_VARIANT_UNSET = 0,
|
|
DNS_EXP_VARIANT_CONTROL = 1,
|
|
DNS_EXP_VARIANT_TTL_ONLY = 2,
|
|
DNS_EXP_VARIANT_TTL_PLUS_CONST_GRACE = 3,
|
|
DNS_EXP_VARIANT_MAX_VALUE = 3,
|
|
};
|
|
static DnsExpirationVariant sDnsVariant;
|
|
|
|
static mozilla::Telemetry::ID
|
|
GetCacheHitHistogram(DnsExpirationVariant aVariant,
|
|
nsHostRecord::DnsPriority aPriority)
|
|
{
|
|
using namespace mozilla::Telemetry;
|
|
|
|
switch (aVariant) {
|
|
case DNS_EXP_VARIANT_CONTROL:
|
|
switch (aPriority) {
|
|
case nsHostRecord::DNS_PRIORITY_HIGH:
|
|
return DNS_CACHE_HIT_VAR_CONTROL_HIGH;
|
|
|
|
case nsHostRecord::DNS_PRIORITY_MEDIUM:
|
|
return DNS_CACHE_HIT_VAR_CONTROL_MEDIUM;
|
|
|
|
case nsHostRecord::DNS_PRIORITY_LOW:
|
|
return DNS_CACHE_HIT_VAR_CONTROL_LOW;
|
|
}
|
|
|
|
case DNS_EXP_VARIANT_TTL_ONLY:
|
|
switch (aPriority) {
|
|
case nsHostRecord::DNS_PRIORITY_HIGH:
|
|
return DNS_CACHE_HIT_VAR_TTL_ONLY_HIGH;
|
|
|
|
case nsHostRecord::DNS_PRIORITY_MEDIUM:
|
|
return DNS_CACHE_HIT_VAR_TTL_ONLY_MEDIUM;
|
|
|
|
case nsHostRecord::DNS_PRIORITY_LOW:
|
|
return DNS_CACHE_HIT_VAR_TTL_ONLY_LOW;
|
|
}
|
|
|
|
case DNS_EXP_VARIANT_TTL_PLUS_CONST_GRACE:
|
|
switch (aPriority) {
|
|
case nsHostRecord::DNS_PRIORITY_HIGH:
|
|
return DNS_CACHE_HIT_VAR_TTL_PLUS_CONST_GRACE_HIGH;
|
|
|
|
case nsHostRecord::DNS_PRIORITY_MEDIUM:
|
|
return DNS_CACHE_HIT_VAR_TTL_PLUS_CONST_GRACE_MEDIUM;
|
|
|
|
case nsHostRecord::DNS_PRIORITY_LOW:
|
|
return DNS_CACHE_HIT_VAR_TTL_PLUS_CONST_GRACE_LOW;
|
|
}
|
|
|
|
case DNS_EXP_VARIANT_UNSET:
|
|
;
|
|
}
|
|
|
|
MOZ_ASSERT_UNREACHABLE("Invalid expiration variant.");
|
|
return DNS_CACHE_HIT_VAR_CONTROL_LOW;
|
|
}
|
|
|
|
static mozilla::Telemetry::ID
|
|
GetBlacklistCountHistogram(DnsExpirationVariant aVariant,
|
|
nsHostRecord::DnsPriority aPriority)
|
|
{
|
|
using namespace mozilla::Telemetry;
|
|
|
|
switch (aVariant) {
|
|
case DNS_EXP_VARIANT_CONTROL:
|
|
switch (aPriority) {
|
|
case nsHostRecord::DNS_PRIORITY_HIGH:
|
|
return DNS_BLACKLIST_COUNT_VAR_CONTROL_HIGH;
|
|
|
|
case nsHostRecord::DNS_PRIORITY_MEDIUM:
|
|
return DNS_BLACKLIST_COUNT_VAR_CONTROL_MEDIUM;
|
|
|
|
case nsHostRecord::DNS_PRIORITY_LOW:
|
|
return DNS_BLACKLIST_COUNT_VAR_CONTROL_LOW;
|
|
}
|
|
|
|
case DNS_EXP_VARIANT_TTL_ONLY:
|
|
switch (aPriority) {
|
|
case nsHostRecord::DNS_PRIORITY_HIGH:
|
|
return DNS_BLACKLIST_COUNT_VAR_TTL_ONLY_HIGH;
|
|
|
|
case nsHostRecord::DNS_PRIORITY_MEDIUM:
|
|
return DNS_BLACKLIST_COUNT_VAR_TTL_ONLY_MEDIUM;
|
|
|
|
case nsHostRecord::DNS_PRIORITY_LOW:
|
|
return DNS_BLACKLIST_COUNT_VAR_TTL_ONLY_LOW;
|
|
}
|
|
|
|
case DNS_EXP_VARIANT_TTL_PLUS_CONST_GRACE:
|
|
switch (aPriority) {
|
|
case nsHostRecord::DNS_PRIORITY_HIGH:
|
|
return DNS_BLACKLIST_COUNT_VAR_TTL_PLUS_CONST_GRACE_HIGH;
|
|
|
|
case nsHostRecord::DNS_PRIORITY_MEDIUM:
|
|
return DNS_BLACKLIST_COUNT_VAR_TTL_PLUS_CONST_GRACE_MEDIUM;
|
|
|
|
case nsHostRecord::DNS_PRIORITY_LOW:
|
|
return DNS_BLACKLIST_COUNT_VAR_TTL_PLUS_CONST_GRACE_LOW;
|
|
}
|
|
|
|
case DNS_EXP_VARIANT_UNSET:
|
|
; // Fall through
|
|
}
|
|
|
|
MOZ_ASSERT_UNREACHABLE("Invalid variant.");
|
|
return DNS_BLACKLIST_COUNT_VAR_CONTROL_LOW;
|
|
}
|
|
|
|
// this macro filters out any flags that are not used when constructing the
|
|
// host key. the significant flags are those that would affect the resulting
|
|
// host record (i.e., the flags that are passed down to PR_GetAddrInfoByName).
|
|
#define RES_KEY_FLAGS(_f) ((_f) & nsHostResolver::RES_CANON_NAME)
|
|
|
|
nsHostRecord::nsHostRecord(const nsHostKey *key)
|
|
: addr_info_lock("nsHostRecord.addr_info_lock")
|
|
, addr_info_gencnt(0)
|
|
, addr_info(nullptr)
|
|
, addr(nullptr)
|
|
, negative(false)
|
|
, resolving(false)
|
|
, onQueue(false)
|
|
, usingAnyThread(false)
|
|
, mDoomed(false)
|
|
#if TTL_AVAILABLE
|
|
, mGetTtl(false)
|
|
#endif
|
|
, mBlacklistedCount(0)
|
|
, mResolveAgain(false)
|
|
{
|
|
host = ((char *) this) + sizeof(nsHostRecord);
|
|
memcpy((char *) host, key->host, strlen(key->host) + 1);
|
|
flags = key->flags;
|
|
af = key->af;
|
|
|
|
PR_INIT_CLIST(this);
|
|
PR_INIT_CLIST(&callbacks);
|
|
}
|
|
|
|
nsresult
|
|
nsHostRecord::Create(const nsHostKey *key, nsHostRecord **result)
|
|
{
|
|
size_t hostLen = strlen(key->host) + 1;
|
|
size_t size = hostLen + sizeof(nsHostRecord);
|
|
|
|
// Use placement new to create the object with room for the hostname
|
|
// allocated after it.
|
|
void *place = ::operator new(size);
|
|
*result = new(place) nsHostRecord(key);
|
|
NS_ADDREF(*result);
|
|
|
|
MOZ_EVENT_TRACER_NAME_OBJECT(*result, key->host);
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
void
|
|
nsHostRecord::SetExpiration(const mozilla::TimeStamp& now, unsigned int valid, unsigned int grace)
|
|
{
|
|
mValidStart = now;
|
|
mGraceStart = now + TimeDuration::FromSeconds(valid);
|
|
mValidEnd = now + TimeDuration::FromSeconds(valid + grace);
|
|
}
|
|
|
|
nsHostRecord::~nsHostRecord()
|
|
{
|
|
Telemetry::Accumulate(
|
|
GetBlacklistCountHistogram(sDnsVariant,
|
|
nsHostRecord::GetPriority(flags)),
|
|
mBlacklistedCount);
|
|
delete addr_info;
|
|
delete addr;
|
|
}
|
|
|
|
bool
|
|
nsHostRecord::Blacklisted(NetAddr *aQuery)
|
|
{
|
|
// must call locked
|
|
LOG(("Checking blacklist for host [%s], host record [%p].\n", host, this));
|
|
|
|
// skip the string conversion for the common case of no blacklist
|
|
if (!mBlacklistedItems.Length()) {
|
|
return false;
|
|
}
|
|
|
|
char buf[kIPv6CStrBufSize];
|
|
if (!NetAddrToString(aQuery, buf, sizeof(buf))) {
|
|
return false;
|
|
}
|
|
nsDependentCString strQuery(buf);
|
|
|
|
for (uint32_t i = 0; i < mBlacklistedItems.Length(); i++) {
|
|
if (mBlacklistedItems.ElementAt(i).Equals(strQuery)) {
|
|
LOG(("Address [%s] is blacklisted for host [%s].\n", buf, host));
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void
|
|
nsHostRecord::ReportUnusable(NetAddr *aAddress)
|
|
{
|
|
// must call locked
|
|
LOG(("Adding address to blacklist for host [%s], host record [%p].\n", host, this));
|
|
|
|
++mBlacklistedCount;
|
|
|
|
if (negative)
|
|
mDoomed = true;
|
|
|
|
char buf[kIPv6CStrBufSize];
|
|
if (NetAddrToString(aAddress, buf, sizeof(buf))) {
|
|
LOG(("Successfully adding address [%s] to blacklist for host [%s].\n", buf, host));
|
|
mBlacklistedItems.AppendElement(nsCString(buf));
|
|
}
|
|
}
|
|
|
|
void
|
|
nsHostRecord::ResetBlacklist()
|
|
{
|
|
// must call locked
|
|
LOG(("Resetting blacklist for host [%s], host record [%p].\n", host, this));
|
|
mBlacklistedItems.Clear();
|
|
}
|
|
|
|
nsHostRecord::ExpirationStatus
|
|
nsHostRecord::CheckExpiration(const mozilla::TimeStamp& now) const {
|
|
if (!mGraceStart.IsNull() && now >= mGraceStart
|
|
&& !mValidEnd.IsNull() && now < mValidEnd) {
|
|
return nsHostRecord::EXP_GRACE;
|
|
} else if (!mValidEnd.IsNull() && now < mValidEnd) {
|
|
return nsHostRecord::EXP_VALID;
|
|
}
|
|
|
|
return nsHostRecord::EXP_EXPIRED;
|
|
}
|
|
|
|
|
|
bool
|
|
nsHostRecord::HasUsableResult(const mozilla::TimeStamp& now, uint16_t queryFlags) const
|
|
{
|
|
if (mDoomed) {
|
|
return false;
|
|
}
|
|
|
|
// don't use cached negative results for high priority queries.
|
|
if (negative && IsHighPriority(queryFlags)) {
|
|
return false;
|
|
}
|
|
|
|
if (CheckExpiration(now) == EXP_EXPIRED) {
|
|
return false;
|
|
}
|
|
|
|
return addr_info || addr || negative;
|
|
}
|
|
|
|
static size_t
|
|
SizeOfResolveHostCallbackListExcludingHead(const PRCList *head,
|
|
MallocSizeOf mallocSizeOf)
|
|
{
|
|
size_t n = 0;
|
|
PRCList *curr = head->next;
|
|
while (curr != head) {
|
|
nsResolveHostCallback *callback =
|
|
static_cast<nsResolveHostCallback*>(curr);
|
|
n += callback->SizeOfIncludingThis(mallocSizeOf);
|
|
curr = curr->next;
|
|
}
|
|
return n;
|
|
}
|
|
|
|
size_t
|
|
nsHostRecord::SizeOfIncludingThis(MallocSizeOf mallocSizeOf) const
|
|
{
|
|
size_t n = mallocSizeOf(this);
|
|
|
|
// The |host| field (inherited from nsHostKey) actually points to extra
|
|
// memory that is allocated beyond the end of the nsHostRecord (see
|
|
// nsHostRecord::Create()). So it will be included in the
|
|
// |mallocSizeOf(this)| call above.
|
|
|
|
n += SizeOfResolveHostCallbackListExcludingHead(&callbacks, mallocSizeOf);
|
|
n += addr_info ? addr_info->SizeOfIncludingThis(mallocSizeOf) : 0;
|
|
n += mallocSizeOf(addr);
|
|
|
|
n += mBlacklistedItems.SizeOfExcludingThis(mallocSizeOf);
|
|
for (size_t i = 0; i < mBlacklistedItems.Length(); i++) {
|
|
n += mBlacklistedItems[i].SizeOfExcludingThisMustBeUnshared(mallocSizeOf);
|
|
}
|
|
return n;
|
|
}
|
|
|
|
nsHostRecord::DnsPriority
|
|
nsHostRecord::GetPriority(uint16_t aFlags)
|
|
{
|
|
if (IsHighPriority(aFlags)){
|
|
return nsHostRecord::DNS_PRIORITY_HIGH;
|
|
} else if (IsMediumPriority(aFlags)) {
|
|
return nsHostRecord::DNS_PRIORITY_MEDIUM;
|
|
}
|
|
|
|
return nsHostRecord::DNS_PRIORITY_LOW;
|
|
}
|
|
|
|
// Returns true if the entry can be removed, or false if it was marked to get
|
|
// refreshed.
|
|
bool
|
|
nsHostRecord::RemoveOrRefresh()
|
|
{
|
|
// This condition implies that the request has been passed to the OS
|
|
// resolver. The resultant DNS record should be considered stale and not
|
|
// trusted; set a flag to ensure it is called again.
|
|
if (resolving && !onQueue) {
|
|
mResolveAgain = true;
|
|
return false;
|
|
}
|
|
return true; // can be removed now
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
|
|
struct nsHostDBEnt : PLDHashEntryHdr
|
|
{
|
|
nsHostRecord *rec;
|
|
};
|
|
|
|
static PLDHashNumber
|
|
HostDB_HashKey(PLDHashTable *table, const void *key)
|
|
{
|
|
const nsHostKey *hk = static_cast<const nsHostKey *>(key);
|
|
return AddToHash(HashString(hk->host), RES_KEY_FLAGS(hk->flags), hk->af);
|
|
}
|
|
|
|
static bool
|
|
HostDB_MatchEntry(PLDHashTable *table,
|
|
const PLDHashEntryHdr *entry,
|
|
const void *key)
|
|
{
|
|
const nsHostDBEnt *he = static_cast<const nsHostDBEnt *>(entry);
|
|
const nsHostKey *hk = static_cast<const nsHostKey *>(key);
|
|
|
|
return !strcmp(he->rec->host, hk->host) &&
|
|
RES_KEY_FLAGS (he->rec->flags) == RES_KEY_FLAGS(hk->flags) &&
|
|
he->rec->af == hk->af;
|
|
}
|
|
|
|
static void
|
|
HostDB_MoveEntry(PLDHashTable *table,
|
|
const PLDHashEntryHdr *from,
|
|
PLDHashEntryHdr *to)
|
|
{
|
|
static_cast<nsHostDBEnt *>(to)->rec =
|
|
static_cast<const nsHostDBEnt *>(from)->rec;
|
|
}
|
|
|
|
static void
|
|
HostDB_ClearEntry(PLDHashTable *table,
|
|
PLDHashEntryHdr *entry)
|
|
{
|
|
nsHostDBEnt *he = static_cast<nsHostDBEnt*>(entry);
|
|
MOZ_ASSERT(he, "nsHostDBEnt is null!");
|
|
|
|
nsHostRecord *hr = he->rec;
|
|
MOZ_ASSERT(hr, "nsHostDBEnt has null host record!");
|
|
|
|
LOG(("Clearing cache db entry for host [%s].\n", hr->host));
|
|
#if defined(DEBUG) && defined(PR_LOGGING)
|
|
{
|
|
MutexAutoLock lock(hr->addr_info_lock);
|
|
if (!hr->addr_info) {
|
|
LOG(("No address info for host [%s].\n", hr->host));
|
|
} else {
|
|
if (!hr->mValidEnd.IsNull()) {
|
|
TimeDuration diff = hr->mValidEnd - TimeStamp::NowLoRes();
|
|
LOG(("Record for [%s] expires in %f seconds.\n", hr->host,
|
|
diff.ToSeconds()));
|
|
} else {
|
|
LOG(("Record for [%s] not yet valid.\n", hr->host));
|
|
}
|
|
|
|
NetAddrElement *addrElement = nullptr;
|
|
char buf[kIPv6CStrBufSize];
|
|
do {
|
|
if (!addrElement) {
|
|
addrElement = hr->addr_info->mAddresses.getFirst();
|
|
} else {
|
|
addrElement = addrElement->getNext();
|
|
}
|
|
|
|
if (addrElement) {
|
|
NetAddrToString(&addrElement->mAddress, buf, sizeof(buf));
|
|
LOG((" [%s]\n", buf));
|
|
}
|
|
}
|
|
while (addrElement);
|
|
}
|
|
}
|
|
#endif
|
|
NS_RELEASE(he->rec);
|
|
}
|
|
|
|
static bool
|
|
HostDB_InitEntry(PLDHashTable *table,
|
|
PLDHashEntryHdr *entry,
|
|
const void *key)
|
|
{
|
|
nsHostDBEnt *he = static_cast<nsHostDBEnt *>(entry);
|
|
nsHostRecord::Create(static_cast<const nsHostKey *>(key), &he->rec);
|
|
return true;
|
|
}
|
|
|
|
static const PLDHashTableOps gHostDB_ops =
|
|
{
|
|
PL_DHashAllocTable,
|
|
PL_DHashFreeTable,
|
|
HostDB_HashKey,
|
|
HostDB_MatchEntry,
|
|
HostDB_MoveEntry,
|
|
HostDB_ClearEntry,
|
|
PL_DHashFinalizeStub,
|
|
HostDB_InitEntry,
|
|
};
|
|
|
|
static PLDHashOperator
|
|
HostDB_RemoveEntry(PLDHashTable *table,
|
|
PLDHashEntryHdr *hdr,
|
|
uint32_t number,
|
|
void *arg)
|
|
{
|
|
return PL_DHASH_REMOVE;
|
|
}
|
|
|
|
static PLDHashOperator
|
|
HostDB_PruneEntry(PLDHashTable *table,
|
|
PLDHashEntryHdr *hdr,
|
|
uint32_t number,
|
|
void *arg)
|
|
{
|
|
nsHostDBEnt* ent = static_cast<nsHostDBEnt *>(hdr);
|
|
|
|
// Try to remove the record, or mark it for refresh
|
|
if (ent->rec->RemoveOrRefresh()) {
|
|
return PL_DHASH_REMOVE;
|
|
}
|
|
return PL_DHASH_NEXT;
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
|
|
#if TTL_AVAILABLE
|
|
static const char kTtlExperiment[] = "dns.ttl-experiment.";
|
|
static const char kTtlExperimentEnabled[] = "dns.ttl-experiment.enabled";
|
|
static const char kNetworkExperimentsEnabled[] = "network.allow-experiments";
|
|
static const char kTtlExperimentVariant[] = "dns.ttl-experiment.variant";
|
|
|
|
void
|
|
nsHostResolver::DnsExperimentChangedInternal()
|
|
{
|
|
MOZ_ASSERT(NS_IsMainThread(), "Can only get prefs on main thread!");
|
|
|
|
if (!Preferences::GetBool(kTtlExperimentEnabled) ||
|
|
!Preferences::GetBool(kNetworkExperimentsEnabled)) {
|
|
sDnsVariant = DNS_EXP_VARIANT_CONTROL;
|
|
LOG(("DNS TTL experiment is disabled."));
|
|
return;
|
|
}
|
|
|
|
auto variant = static_cast<DnsExpirationVariant>(
|
|
Preferences::GetInt(kTtlExperimentVariant));
|
|
|
|
// Setup this profile to use a particular DNS expiration strategy
|
|
if (variant == DNS_EXP_VARIANT_UNSET) {
|
|
variant = static_cast<DnsExpirationVariant>(
|
|
rand() % DNS_EXP_VARIANT_MAX_VALUE + 1);
|
|
LOG(("No DNS TTL experiment variant saved. Randomly picked %d.",
|
|
variant));
|
|
|
|
DebugOnly<nsresult> rv = Preferences::SetInt(
|
|
kTtlExperimentVariant, variant);
|
|
NS_WARN_IF_FALSE(NS_SUCCEEDED(rv),
|
|
"Could not set experiment variant pref.");
|
|
} else {
|
|
LOG(("Using saved DNS TTL experiment %d.", variant));
|
|
}
|
|
|
|
sDnsVariant = variant;
|
|
}
|
|
|
|
void
|
|
nsHostResolver::DnsExperimentChanged(const char* aPref, void* aClosure)
|
|
{
|
|
MOZ_ASSERT(NS_IsMainThread(),
|
|
"Should be getting pref changed notification on main thread!");
|
|
|
|
if (strcmp(aPref, kNetworkExperimentsEnabled) != 0 &&
|
|
strncmp(aPref, kTtlExperiment, strlen(kTtlExperiment)) != 0) {
|
|
LOG(("DnsExperimentChanged ignoring pref \"%s\"", aPref));
|
|
return;
|
|
}
|
|
|
|
auto self = static_cast<nsHostResolver*>(aClosure);
|
|
MOZ_ASSERT(self);
|
|
|
|
// We can't set a pref in the context of a pref change callback, so
|
|
// dispatch DnsExperimentChangedInternal for async getting/setting.
|
|
DebugOnly<nsresult> rv = NS_DispatchToMainThread(
|
|
NS_NewRunnableMethod(self, &DnsExperimentChangedInternal));
|
|
NS_WARN_IF_FALSE(NS_SUCCEEDED(rv),
|
|
"Could not dispatch DnsExperimentChanged event.");
|
|
}
|
|
|
|
void
|
|
nsHostResolver::InitCRandom()
|
|
{
|
|
MOZ_ASSERT(NS_IsMainThread(), "Should be seeding rand() on main thread!");
|
|
|
|
srand(time(nullptr));
|
|
}
|
|
#endif
|
|
|
|
nsHostResolver::nsHostResolver(uint32_t maxCacheEntries,
|
|
uint32_t defaultCacheEntryLifetime,
|
|
uint32_t defaultGracePeriod)
|
|
: mMaxCacheEntries(maxCacheEntries)
|
|
, mDefaultCacheLifetime(defaultCacheEntryLifetime)
|
|
, mDefaultGracePeriod(defaultGracePeriod)
|
|
, mLock("nsHostResolver.mLock")
|
|
, mIdleThreadCV(mLock, "nsHostResolver.mIdleThreadCV")
|
|
, mNumIdleThreads(0)
|
|
, mThreadCount(0)
|
|
, mActiveAnyThreadCount(0)
|
|
, mEvictionQSize(0)
|
|
, mPendingCount(0)
|
|
, mShutdown(true)
|
|
{
|
|
mCreationTime = PR_Now();
|
|
PR_INIT_CLIST(&mHighQ);
|
|
PR_INIT_CLIST(&mMediumQ);
|
|
PR_INIT_CLIST(&mLowQ);
|
|
PR_INIT_CLIST(&mEvictionQ);
|
|
|
|
mLongIdleTimeout = PR_SecondsToInterval(LongIdleTimeoutSeconds);
|
|
mShortIdleTimeout = PR_SecondsToInterval(ShortIdleTimeoutSeconds);
|
|
}
|
|
|
|
nsHostResolver::~nsHostResolver()
|
|
{
|
|
PL_DHashTableFinish(&mDB);
|
|
}
|
|
|
|
nsresult
|
|
nsHostResolver::Init()
|
|
{
|
|
if (NS_FAILED(GetAddrInfoInit())) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
PL_DHashTableInit(&mDB, &gHostDB_ops, nullptr, sizeof(nsHostDBEnt), 0);
|
|
|
|
mShutdown = false;
|
|
|
|
sDnsVariant = DNS_EXP_VARIANT_CONTROL;
|
|
|
|
#if TTL_AVAILABLE
|
|
// The preferences probably haven't been loaded from the disk yet, so we
|
|
// need to register a callback that will set up the experiment once they
|
|
// are. We also need to explicitly set a value for the props otherwise the
|
|
// callback won't be called.
|
|
{
|
|
DebugOnly<nsresult> rv = NS_DispatchToMainThread(
|
|
NS_NewRunnableMethod(this, &nsHostResolver::InitCRandom));
|
|
NS_WARN_IF_FALSE(NS_SUCCEEDED(rv),
|
|
"Could not dispatch InitCRandom event.");
|
|
rv = Preferences::RegisterCallbackAndCall(
|
|
&DnsExperimentChanged, kTtlExperiment, this);
|
|
NS_WARN_IF_FALSE(NS_SUCCEEDED(rv),
|
|
"Could not register DNS experiment callback.");
|
|
rv = Preferences::RegisterCallback(
|
|
&DnsExperimentChanged, kNetworkExperimentsEnabled, this);
|
|
NS_WARN_IF_FALSE(NS_SUCCEEDED(rv),
|
|
"Could not register network experiment callback.");
|
|
}
|
|
#endif
|
|
|
|
#if defined(HAVE_RES_NINIT)
|
|
// We want to make sure the system is using the correct resolver settings,
|
|
// so we force it to reload those settings whenever we startup a subsequent
|
|
// nsHostResolver instance. We assume that there is no reason to do this
|
|
// for the first nsHostResolver instance since that is usually created
|
|
// during application startup.
|
|
static int initCount = 0;
|
|
if (initCount++ > 0) {
|
|
LOG(("Calling 'res_ninit'.\n"));
|
|
res_ninit(&_res);
|
|
}
|
|
#endif
|
|
return NS_OK;
|
|
}
|
|
|
|
void
|
|
nsHostResolver::ClearPendingQueue(PRCList *aPendingQ)
|
|
{
|
|
// loop through pending queue, erroring out pending lookups.
|
|
if (!PR_CLIST_IS_EMPTY(aPendingQ)) {
|
|
PRCList *node = aPendingQ->next;
|
|
while (node != aPendingQ) {
|
|
nsHostRecord *rec = static_cast<nsHostRecord *>(node);
|
|
node = node->next;
|
|
OnLookupComplete(rec, NS_ERROR_ABORT, nullptr);
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// FlushCache() is what we call when the network has changed. We must not
|
|
// trust names that were resolved before this change. They may resolve
|
|
// differently now.
|
|
//
|
|
// This function removes all existing resolved host entries from the hash.
|
|
// Names that are in the pending queues can be left there. Entries in the
|
|
// cache that have 'Resolve' set true but not 'onQueue' are being resolved
|
|
// right now, so we need to mark them to get re-resolved on completion!
|
|
|
|
void
|
|
nsHostResolver::FlushCache()
|
|
{
|
|
PRCList evictionQ;
|
|
PR_INIT_CLIST(&evictionQ);
|
|
|
|
{
|
|
MutexAutoLock lock(mLock);
|
|
MoveCList(mEvictionQ, evictionQ);
|
|
mEvictionQSize = 0;
|
|
|
|
// prune the hash from all hosts already resolved
|
|
PL_DHashTableEnumerate(&mDB, HostDB_PruneEntry, nullptr);
|
|
}
|
|
|
|
if (!PR_CLIST_IS_EMPTY(&evictionQ)) {
|
|
PRCList *node = evictionQ.next;
|
|
while (node != &evictionQ) {
|
|
nsHostRecord *rec = static_cast<nsHostRecord *>(node);
|
|
node = node->next;
|
|
NS_RELEASE(rec);
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
nsHostResolver::Shutdown()
|
|
{
|
|
LOG(("Shutting down host resolver.\n"));
|
|
|
|
#if TTL_AVAILABLE
|
|
{
|
|
DebugOnly<nsresult> rv = Preferences::UnregisterCallback(
|
|
&DnsExperimentChanged, kTtlExperiment, this);
|
|
NS_WARN_IF_FALSE(NS_SUCCEEDED(rv),
|
|
"Could not unregister TTL experiment callback.");
|
|
rv = Preferences::UnregisterCallback(
|
|
&DnsExperimentChanged, kNetworkExperimentsEnabled, this);
|
|
NS_WARN_IF_FALSE(NS_SUCCEEDED(rv),
|
|
"Could not unregister network experiment callback.");
|
|
}
|
|
#endif
|
|
|
|
PRCList pendingQHigh, pendingQMed, pendingQLow, evictionQ;
|
|
PR_INIT_CLIST(&pendingQHigh);
|
|
PR_INIT_CLIST(&pendingQMed);
|
|
PR_INIT_CLIST(&pendingQLow);
|
|
PR_INIT_CLIST(&evictionQ);
|
|
|
|
{
|
|
MutexAutoLock lock(mLock);
|
|
|
|
mShutdown = true;
|
|
|
|
MoveCList(mHighQ, pendingQHigh);
|
|
MoveCList(mMediumQ, pendingQMed);
|
|
MoveCList(mLowQ, pendingQLow);
|
|
MoveCList(mEvictionQ, evictionQ);
|
|
mEvictionQSize = 0;
|
|
mPendingCount = 0;
|
|
|
|
if (mNumIdleThreads)
|
|
mIdleThreadCV.NotifyAll();
|
|
|
|
// empty host database
|
|
PL_DHashTableEnumerate(&mDB, HostDB_RemoveEntry, nullptr);
|
|
}
|
|
|
|
ClearPendingQueue(&pendingQHigh);
|
|
ClearPendingQueue(&pendingQMed);
|
|
ClearPendingQueue(&pendingQLow);
|
|
|
|
if (!PR_CLIST_IS_EMPTY(&evictionQ)) {
|
|
PRCList *node = evictionQ.next;
|
|
while (node != &evictionQ) {
|
|
nsHostRecord *rec = static_cast<nsHostRecord *>(node);
|
|
node = node->next;
|
|
NS_RELEASE(rec);
|
|
}
|
|
}
|
|
|
|
#ifdef NS_BUILD_REFCNT_LOGGING
|
|
|
|
// Logically join the outstanding worker threads with a timeout.
|
|
// Use this approach instead of PR_JoinThread() because that does
|
|
// not allow a timeout which may be necessary for a semi-responsive
|
|
// shutdown if the thread is blocked on a very slow DNS resolution.
|
|
// mThreadCount is read outside of mLock, but the worst case
|
|
// scenario for that race is one extra 25ms sleep.
|
|
|
|
PRIntervalTime delay = PR_MillisecondsToInterval(25);
|
|
PRIntervalTime stopTime = PR_IntervalNow() + PR_SecondsToInterval(20);
|
|
while (mThreadCount && PR_IntervalNow() < stopTime)
|
|
PR_Sleep(delay);
|
|
#endif
|
|
|
|
{
|
|
mozilla::DebugOnly<nsresult> rv = GetAddrInfoShutdown();
|
|
NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "Failed to shutdown GetAddrInfo");
|
|
}
|
|
}
|
|
|
|
void
|
|
nsHostResolver::MoveQueue(nsHostRecord *aRec, PRCList &aDestQ)
|
|
{
|
|
NS_ASSERTION(aRec->onQueue, "Moving Host Record Not Currently Queued");
|
|
|
|
PR_REMOVE_LINK(aRec);
|
|
PR_APPEND_LINK(aRec, &aDestQ);
|
|
}
|
|
|
|
nsresult
|
|
nsHostResolver::ResolveHost(const char *host,
|
|
uint16_t flags,
|
|
uint16_t af,
|
|
nsResolveHostCallback *callback)
|
|
{
|
|
NS_ENSURE_TRUE(host && *host, NS_ERROR_UNEXPECTED);
|
|
|
|
LOG(("Resolving host [%s]%s.\n",
|
|
host, flags & RES_BYPASS_CACHE ? " - bypassing cache" : ""));
|
|
|
|
// ensure that we are working with a valid hostname before proceeding. see
|
|
// bug 304904 for details.
|
|
if (!net_IsValidHostName(nsDependentCString(host)))
|
|
return NS_ERROR_UNKNOWN_HOST;
|
|
|
|
// if result is set inside the lock, then we need to issue the
|
|
// callback before returning.
|
|
nsRefPtr<nsHostRecord> result;
|
|
nsresult status = NS_OK, rv = NS_OK;
|
|
{
|
|
MutexAutoLock lock(mLock);
|
|
|
|
if (mShutdown)
|
|
rv = NS_ERROR_NOT_INITIALIZED;
|
|
else {
|
|
// Used to try to parse to an IP address literal.
|
|
PRNetAddr tempAddr;
|
|
// Unfortunately, PR_StringToNetAddr does not properly initialize
|
|
// the output buffer in the case of IPv6 input. See bug 223145.
|
|
memset(&tempAddr, 0, sizeof(PRNetAddr));
|
|
|
|
// check to see if there is already an entry for this |host|
|
|
// in the hash table. if so, then check to see if we can't
|
|
// just reuse the lookup result. otherwise, if there are
|
|
// any pending callbacks, then add to pending callbacks queue,
|
|
// and return. otherwise, add ourselves as first pending
|
|
// callback, and proceed to do the lookup.
|
|
|
|
nsHostKey key = { host, flags, af };
|
|
nsHostDBEnt *he = static_cast<nsHostDBEnt *>
|
|
(PL_DHashTableOperate(&mDB, &key, PL_DHASH_ADD));
|
|
|
|
// if the record is null, then HostDB_InitEntry failed.
|
|
if (!he || !he->rec) {
|
|
LOG((" Out of memory: no cache entry for [%s].\n", host));
|
|
rv = NS_ERROR_OUT_OF_MEMORY;
|
|
}
|
|
// do we have a cached result that we can reuse?
|
|
else if (!(flags & RES_BYPASS_CACHE) &&
|
|
he->rec->HasUsableResult(TimeStamp::NowLoRes(), flags)) {
|
|
LOG((" Using cached record for host [%s].\n", host));
|
|
// put reference to host record on stack...
|
|
result = he->rec;
|
|
Telemetry::Accumulate(Telemetry::DNS_LOOKUP_METHOD2, METHOD_HIT);
|
|
|
|
// For entries that are in the grace period
|
|
// or all cached negative entries, use the cache but start a new
|
|
// lookup in the background
|
|
ConditionallyRefreshRecord(he->rec, host);
|
|
|
|
if (he->rec->negative) {
|
|
LOG((" Negative cache entry for [%s].\n", host));
|
|
Telemetry::Accumulate(Telemetry::DNS_LOOKUP_METHOD2,
|
|
METHOD_NEGATIVE_HIT);
|
|
status = NS_ERROR_UNKNOWN_HOST;
|
|
}
|
|
}
|
|
// if the host name is an IP address literal and has been parsed,
|
|
// go ahead and use it.
|
|
else if (he->rec->addr) {
|
|
LOG((" Using cached address for IP Literal [%s].\n", host));
|
|
Telemetry::Accumulate(Telemetry::DNS_LOOKUP_METHOD2,
|
|
METHOD_LITERAL);
|
|
result = he->rec;
|
|
}
|
|
// try parsing the host name as an IP address literal to short
|
|
// circuit full host resolution. (this is necessary on some
|
|
// platforms like Win9x. see bug 219376 for more details.)
|
|
else if (PR_StringToNetAddr(host, &tempAddr) == PR_SUCCESS) {
|
|
LOG((" Host is IP Literal [%s].\n", host));
|
|
// ok, just copy the result into the host record, and be done
|
|
// with it! ;-)
|
|
he->rec->addr = new NetAddr();
|
|
PRNetAddrToNetAddr(&tempAddr, he->rec->addr);
|
|
// put reference to host record on stack...
|
|
Telemetry::Accumulate(Telemetry::DNS_LOOKUP_METHOD2,
|
|
METHOD_LITERAL);
|
|
result = he->rec;
|
|
}
|
|
else if (mPendingCount >= MAX_NON_PRIORITY_REQUESTS &&
|
|
!IsHighPriority(flags) &&
|
|
!he->rec->resolving) {
|
|
LOG((" Lookup queue full: dropping %s priority request for "
|
|
"[%s].\n",
|
|
IsMediumPriority(flags) ? "medium" : "low", host));
|
|
Telemetry::Accumulate(Telemetry::DNS_LOOKUP_METHOD2,
|
|
METHOD_OVERFLOW);
|
|
// This is a lower priority request and we are swamped, so refuse it.
|
|
rv = NS_ERROR_DNS_LOOKUP_QUEUE_FULL;
|
|
}
|
|
else if (flags & RES_OFFLINE) {
|
|
LOG((" Offline request for [%s]; ignoring.\n", host));
|
|
rv = NS_ERROR_OFFLINE;
|
|
}
|
|
|
|
// If this is an IPV4 or IPV6 specific request, check if there is
|
|
// an AF_UNSPEC entry we can use. Otherwise, hit the resolver...
|
|
else if (!he->rec->resolving) {
|
|
if (!(flags & RES_BYPASS_CACHE) &&
|
|
((af == PR_AF_INET) || (af == PR_AF_INET6))) {
|
|
// First, search for an entry with AF_UNSPEC
|
|
const nsHostKey unspecKey = { host, flags, PR_AF_UNSPEC };
|
|
nsHostDBEnt *unspecHe = static_cast<nsHostDBEnt *>
|
|
(PL_DHashTableOperate(&mDB, &unspecKey, PL_DHASH_LOOKUP));
|
|
NS_ASSERTION(PL_DHASH_ENTRY_IS_FREE(unspecHe) ||
|
|
(PL_DHASH_ENTRY_IS_BUSY(unspecHe) &&
|
|
unspecHe->rec),
|
|
"Valid host entries should contain a record");
|
|
if (PL_DHASH_ENTRY_IS_BUSY(unspecHe) &&
|
|
unspecHe->rec &&
|
|
unspecHe->rec->HasUsableResult(TimeStamp::NowLoRes(), flags)) {
|
|
|
|
MOZ_ASSERT(unspecHe->rec->addr_info || unspecHe->rec->negative,
|
|
"Entry should be resolved or negative.");
|
|
|
|
LOG((" Trying AF_UNSPEC entry for [%s] af: %s.\n",
|
|
host, (af == PR_AF_INET) ? "AF_INET" : "AF_INET6"));
|
|
|
|
he->rec->addr_info = nullptr;
|
|
if (unspecHe->rec->negative) {
|
|
he->rec->negative = unspecHe->rec->negative;
|
|
} else if (unspecHe->rec->addr_info) {
|
|
// Search for any valid address in the AF_UNSPEC entry
|
|
// in the cache (not blacklisted and from the right
|
|
// family).
|
|
NetAddrElement *addrIter =
|
|
unspecHe->rec->addr_info->mAddresses.getFirst();
|
|
while (addrIter) {
|
|
if ((af == addrIter->mAddress.inet.family) &&
|
|
!unspecHe->rec->Blacklisted(&addrIter->mAddress)) {
|
|
if (!he->rec->addr_info) {
|
|
he->rec->addr_info = new AddrInfo(
|
|
unspecHe->rec->addr_info->mHostName,
|
|
unspecHe->rec->addr_info->mCanonicalName);
|
|
}
|
|
he->rec->addr_info->AddAddress(
|
|
new NetAddrElement(*addrIter));
|
|
}
|
|
addrIter = addrIter->getNext();
|
|
}
|
|
}
|
|
if (he->rec->HasUsableResult(TimeStamp::NowLoRes(), flags)) {
|
|
result = he->rec;
|
|
Telemetry::Accumulate(Telemetry::DNS_LOOKUP_METHOD2,
|
|
METHOD_HIT);
|
|
ConditionallyRefreshRecord(he->rec, host);
|
|
}
|
|
// For AF_INET6, a new lookup means another AF_UNSPEC
|
|
// lookup. We have already iterated through the
|
|
// AF_UNSPEC addresses, so we mark this record as
|
|
// negative.
|
|
else if (af == PR_AF_INET6) {
|
|
LOG((" No AF_INET6 in AF_UNSPEC entry: "
|
|
"[%s] unknown host", host));
|
|
result = he->rec;
|
|
he->rec->negative = true;
|
|
status = NS_ERROR_UNKNOWN_HOST;
|
|
Telemetry::Accumulate(Telemetry::DNS_LOOKUP_METHOD2,
|
|
METHOD_NEGATIVE_HIT);
|
|
}
|
|
}
|
|
}
|
|
// If no valid address was found in the cache or this is an
|
|
// AF_UNSPEC request, then start a new lookup.
|
|
if (!result) {
|
|
LOG((" No usable address in cache for [%s]", host));
|
|
// Add callback to the list of pending callbacks.
|
|
PR_APPEND_LINK(callback, &he->rec->callbacks);
|
|
he->rec->flags = flags;
|
|
rv = IssueLookup(he->rec);
|
|
Telemetry::Accumulate(Telemetry::DNS_LOOKUP_METHOD2,
|
|
METHOD_NETWORK_FIRST);
|
|
if (NS_FAILED(rv)) {
|
|
PR_REMOVE_AND_INIT_LINK(callback);
|
|
}
|
|
else {
|
|
LOG((" DNS lookup for host [%s] blocking pending "
|
|
"'getaddrinfo' query: callback [%p]",
|
|
host, callback));
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
LOG((" Host [%s] is being resolved. Appending callback [%p].",
|
|
host, callback));
|
|
PR_APPEND_LINK(callback, &he->rec->callbacks);
|
|
if (he->rec->onQueue) {
|
|
Telemetry::Accumulate(Telemetry::DNS_LOOKUP_METHOD2,
|
|
METHOD_NETWORK_SHARED);
|
|
|
|
// Consider the case where we are on a pending queue of
|
|
// lower priority than the request is being made at.
|
|
// In that case we should upgrade to the higher queue.
|
|
|
|
if (IsHighPriority(flags) &&
|
|
!IsHighPriority(he->rec->flags)) {
|
|
// Move from (low|med) to high.
|
|
MoveQueue(he->rec, mHighQ);
|
|
he->rec->flags = flags;
|
|
ConditionallyCreateThread(he->rec);
|
|
} else if (IsMediumPriority(flags) &&
|
|
IsLowPriority(he->rec->flags)) {
|
|
// Move from low to med.
|
|
MoveQueue(he->rec, mMediumQ);
|
|
he->rec->flags = flags;
|
|
mIdleThreadCV.Notify();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (result) {
|
|
callback->OnLookupComplete(this, result, status);
|
|
}
|
|
|
|
Telemetry::Accumulate(
|
|
GetCacheHitHistogram(sDnsVariant, nsHostRecord::GetPriority(flags)),
|
|
static_cast<bool>(result));
|
|
return rv;
|
|
}
|
|
|
|
void
|
|
nsHostResolver::DetachCallback(const char *host,
|
|
uint16_t flags,
|
|
uint16_t af,
|
|
nsResolveHostCallback *callback,
|
|
nsresult status)
|
|
{
|
|
nsRefPtr<nsHostRecord> rec;
|
|
{
|
|
MutexAutoLock lock(mLock);
|
|
|
|
nsHostKey key = { host, flags, af };
|
|
nsHostDBEnt *he = static_cast<nsHostDBEnt *>
|
|
(PL_DHashTableOperate(&mDB, &key, PL_DHASH_LOOKUP));
|
|
if (he && he->rec) {
|
|
// walk list looking for |callback|... we cannot assume
|
|
// that it will be there!
|
|
PRCList *node = he->rec->callbacks.next;
|
|
while (node != &he->rec->callbacks) {
|
|
if (static_cast<nsResolveHostCallback *>(node) == callback) {
|
|
PR_REMOVE_LINK(callback);
|
|
rec = he->rec;
|
|
break;
|
|
}
|
|
node = node->next;
|
|
}
|
|
}
|
|
}
|
|
|
|
// complete callback with the given status code; this would only be done if
|
|
// the record was in the process of being resolved.
|
|
if (rec)
|
|
callback->OnLookupComplete(this, rec, status);
|
|
}
|
|
|
|
nsresult
|
|
nsHostResolver::ConditionallyCreateThread(nsHostRecord *rec)
|
|
{
|
|
if (mNumIdleThreads) {
|
|
// wake up idle thread to process this lookup
|
|
mIdleThreadCV.Notify();
|
|
}
|
|
else if ((mThreadCount < HighThreadThreshold) ||
|
|
(IsHighPriority(rec->flags) && mThreadCount < MAX_RESOLVER_THREADS)) {
|
|
// dispatch new worker thread
|
|
NS_ADDREF_THIS(); // owning reference passed to thread
|
|
|
|
mThreadCount++;
|
|
PRThread *thr = PR_CreateThread(PR_SYSTEM_THREAD,
|
|
ThreadFunc,
|
|
this,
|
|
PR_PRIORITY_NORMAL,
|
|
PR_GLOBAL_THREAD,
|
|
PR_UNJOINABLE_THREAD,
|
|
0);
|
|
if (!thr) {
|
|
mThreadCount--;
|
|
NS_RELEASE_THIS();
|
|
return NS_ERROR_OUT_OF_MEMORY;
|
|
}
|
|
}
|
|
#if defined(PR_LOGGING)
|
|
else
|
|
LOG((" Unable to find a thread for looking up host [%s].\n", rec->host));
|
|
#endif
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
nsHostResolver::IssueLookup(nsHostRecord *rec)
|
|
{
|
|
MOZ_EVENT_TRACER_WAIT(rec, "net::dns::resolve");
|
|
|
|
nsresult rv = NS_OK;
|
|
NS_ASSERTION(!rec->resolving, "record is already being resolved");
|
|
|
|
// Add rec to one of the pending queues, possibly removing it from mEvictionQ.
|
|
// If rec is on mEvictionQ, then we can just move the owning
|
|
// reference over to the new active queue.
|
|
if (rec->next == rec)
|
|
NS_ADDREF(rec);
|
|
else {
|
|
PR_REMOVE_LINK(rec);
|
|
mEvictionQSize--;
|
|
}
|
|
|
|
switch (nsHostRecord::GetPriority(rec->flags)) {
|
|
case nsHostRecord::DNS_PRIORITY_HIGH:
|
|
PR_APPEND_LINK(rec, &mHighQ);
|
|
break;
|
|
|
|
case nsHostRecord::DNS_PRIORITY_MEDIUM:
|
|
PR_APPEND_LINK(rec, &mMediumQ);
|
|
break;
|
|
|
|
case nsHostRecord::DNS_PRIORITY_LOW:
|
|
PR_APPEND_LINK(rec, &mLowQ);
|
|
break;
|
|
}
|
|
mPendingCount++;
|
|
|
|
rec->resolving = true;
|
|
rec->onQueue = true;
|
|
|
|
rv = ConditionallyCreateThread(rec);
|
|
|
|
LOG ((" DNS thread counters: total=%d any-live=%d idle=%d pending=%d\n",
|
|
mThreadCount,
|
|
mActiveAnyThreadCount,
|
|
mNumIdleThreads,
|
|
mPendingCount));
|
|
|
|
return rv;
|
|
}
|
|
|
|
nsresult
|
|
nsHostResolver::ConditionallyRefreshRecord(nsHostRecord *rec, const char *host)
|
|
{
|
|
if ((rec->CheckExpiration(TimeStamp::NowLoRes()) != nsHostRecord::EXP_VALID
|
|
|| rec->negative) && !rec->resolving) {
|
|
LOG((" Using %s cache entry for host [%s] but starting async renewal.",
|
|
rec->negative ? "negative" :"positive", host));
|
|
IssueLookup(rec);
|
|
|
|
if (!rec->negative) {
|
|
// negative entries are constantly being refreshed, only
|
|
// track positive grace period induced renewals
|
|
Telemetry::Accumulate(Telemetry::DNS_LOOKUP_METHOD2,
|
|
METHOD_RENEWAL);
|
|
}
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
void
|
|
nsHostResolver::DeQueue(PRCList &aQ, nsHostRecord **aResult)
|
|
{
|
|
*aResult = static_cast<nsHostRecord *>(aQ.next);
|
|
PR_REMOVE_AND_INIT_LINK(*aResult);
|
|
mPendingCount--;
|
|
(*aResult)->onQueue = false;
|
|
}
|
|
|
|
bool
|
|
nsHostResolver::GetHostToLookup(nsHostRecord **result)
|
|
{
|
|
bool timedOut = false;
|
|
PRIntervalTime epoch, now, timeout;
|
|
|
|
MutexAutoLock lock(mLock);
|
|
|
|
timeout = (mNumIdleThreads >= HighThreadThreshold) ? mShortIdleTimeout : mLongIdleTimeout;
|
|
epoch = PR_IntervalNow();
|
|
|
|
while (!mShutdown) {
|
|
// remove next record from Q; hand over owning reference. Check high, then med, then low
|
|
|
|
#if TTL_AVAILABLE
|
|
#define SET_GET_TTL(var, val) \
|
|
(var)->mGetTtl = sDnsVariant != DNS_EXP_VARIANT_CONTROL && (val)
|
|
#else
|
|
#define SET_GET_TTL(var, val)
|
|
#endif
|
|
|
|
if (!PR_CLIST_IS_EMPTY(&mHighQ)) {
|
|
DeQueue (mHighQ, result);
|
|
SET_GET_TTL(*result, false);
|
|
return true;
|
|
}
|
|
|
|
if (mActiveAnyThreadCount < HighThreadThreshold) {
|
|
if (!PR_CLIST_IS_EMPTY(&mMediumQ)) {
|
|
DeQueue (mMediumQ, result);
|
|
mActiveAnyThreadCount++;
|
|
(*result)->usingAnyThread = true;
|
|
SET_GET_TTL(*result, true);
|
|
return true;
|
|
}
|
|
|
|
if (!PR_CLIST_IS_EMPTY(&mLowQ)) {
|
|
DeQueue (mLowQ, result);
|
|
mActiveAnyThreadCount++;
|
|
(*result)->usingAnyThread = true;
|
|
SET_GET_TTL(*result, true);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// Determining timeout is racy, so allow one cycle through checking the queues
|
|
// before exiting.
|
|
if (timedOut)
|
|
break;
|
|
|
|
// wait for one or more of the following to occur:
|
|
// (1) the pending queue has a host record to process
|
|
// (2) the shutdown flag has been set
|
|
// (3) the thread has been idle for too long
|
|
|
|
mNumIdleThreads++;
|
|
mIdleThreadCV.Wait(timeout);
|
|
mNumIdleThreads--;
|
|
|
|
now = PR_IntervalNow();
|
|
|
|
if ((PRIntervalTime)(now - epoch) >= timeout)
|
|
timedOut = true;
|
|
else {
|
|
// It is possible that PR_WaitCondVar() was interrupted and returned early,
|
|
// in which case we will loop back and re-enter it. In that case we want to
|
|
// do so with the new timeout reduced to reflect time already spent waiting.
|
|
timeout -= (PRIntervalTime)(now - epoch);
|
|
epoch = now;
|
|
}
|
|
}
|
|
|
|
// tell thread to exit...
|
|
mThreadCount--;
|
|
return false;
|
|
}
|
|
|
|
void
|
|
nsHostResolver::PrepareRecordExpiration(nsHostRecord* rec) const
|
|
{
|
|
MOZ_ASSERT(((bool)rec->addr_info) != rec->negative);
|
|
if (!rec->addr_info) {
|
|
rec->SetExpiration(TimeStamp::NowLoRes(),
|
|
NEGATIVE_RECORD_LIFETIME, 0);
|
|
LOG(("Caching [%s] negative record for %u seconds.\n", rec->host,
|
|
NEGATIVE_RECORD_LIFETIME));
|
|
return;
|
|
}
|
|
|
|
unsigned int ttl = mDefaultCacheLifetime;
|
|
#if TTL_AVAILABLE
|
|
if (sDnsVariant == DNS_EXP_VARIANT_TTL_ONLY
|
|
|| sDnsVariant == DNS_EXP_VARIANT_TTL_PLUS_CONST_GRACE) {
|
|
MutexAutoLock lock(rec->addr_info_lock);
|
|
if (rec->addr_info && rec->addr_info->ttl != AddrInfo::NO_TTL_DATA) {
|
|
ttl = rec->addr_info->ttl;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
unsigned int lifetime = 0;
|
|
unsigned int grace = 0;
|
|
switch (sDnsVariant) {
|
|
case DNS_EXP_VARIANT_TTL_ONLY:
|
|
lifetime = ttl;
|
|
grace = 0;
|
|
break;
|
|
|
|
case DNS_EXP_VARIANT_TTL_PLUS_CONST_GRACE:
|
|
lifetime = ttl;
|
|
grace = mDefaultGracePeriod;
|
|
break;
|
|
|
|
default:
|
|
lifetime = mDefaultCacheLifetime;
|
|
grace = mDefaultGracePeriod;
|
|
break;
|
|
}
|
|
|
|
rec->SetExpiration(TimeStamp::NowLoRes(), lifetime, grace);
|
|
LOG(("Caching [%s] record for %u seconds (grace %d) (sDnsVariant = %d).",
|
|
rec->host, lifetime, grace, sDnsVariant));
|
|
}
|
|
|
|
//
|
|
// OnLookupComplete() checks if the resolving should be redone and if so it
|
|
// returns LOOKUP_RESOLVEAGAIN, but only if 'status' is not NS_ERROR_ABORT.
|
|
//
|
|
|
|
nsHostResolver::LookupStatus
|
|
nsHostResolver::OnLookupComplete(nsHostRecord* rec, nsresult status, AddrInfo* result)
|
|
{
|
|
// get the list of pending callbacks for this lookup, and notify
|
|
// them that the lookup is complete.
|
|
PRCList cbs;
|
|
PR_INIT_CLIST(&cbs);
|
|
{
|
|
MutexAutoLock lock(mLock);
|
|
|
|
if (rec->mResolveAgain && (status != NS_ERROR_ABORT)) {
|
|
rec->mResolveAgain = false;
|
|
return LOOKUP_RESOLVEAGAIN;
|
|
}
|
|
|
|
// grab list of callbacks to notify
|
|
MoveCList(rec->callbacks, cbs);
|
|
|
|
// update record fields. We might have a rec->addr_info already if a
|
|
// previous lookup result expired and we're reresolving it..
|
|
AddrInfo *old_addr_info;
|
|
{
|
|
MutexAutoLock lock(rec->addr_info_lock);
|
|
old_addr_info = rec->addr_info;
|
|
rec->addr_info = result;
|
|
rec->addr_info_gencnt++;
|
|
}
|
|
delete old_addr_info;
|
|
|
|
rec->negative = !rec->addr_info;
|
|
PrepareRecordExpiration(rec);
|
|
rec->resolving = false;
|
|
|
|
if (rec->usingAnyThread) {
|
|
mActiveAnyThreadCount--;
|
|
rec->usingAnyThread = false;
|
|
}
|
|
|
|
if (!mShutdown) {
|
|
// add to mEvictionQ
|
|
PR_APPEND_LINK(rec, &mEvictionQ);
|
|
NS_ADDREF(rec);
|
|
if (mEvictionQSize < mMaxCacheEntries)
|
|
mEvictionQSize++;
|
|
else {
|
|
// remove first element on mEvictionQ
|
|
nsHostRecord *head =
|
|
static_cast<nsHostRecord *>(PR_LIST_HEAD(&mEvictionQ));
|
|
PR_REMOVE_AND_INIT_LINK(head);
|
|
PL_DHashTableOperate(&mDB, (nsHostKey *) head, PL_DHASH_REMOVE);
|
|
|
|
if (!head->negative) {
|
|
// record the age of the entry upon eviction.
|
|
TimeDuration age = TimeStamp::NowLoRes() - head->mValidStart;
|
|
Telemetry::Accumulate(Telemetry::DNS_CLEANUP_AGE,
|
|
static_cast<uint32_t>(age.ToSeconds() / 60));
|
|
}
|
|
|
|
// release reference to rec owned by mEvictionQ
|
|
NS_RELEASE(head);
|
|
}
|
|
}
|
|
}
|
|
|
|
MOZ_EVENT_TRACER_DONE(rec, "net::dns::resolve");
|
|
|
|
if (!PR_CLIST_IS_EMPTY(&cbs)) {
|
|
PRCList *node = cbs.next;
|
|
while (node != &cbs) {
|
|
nsResolveHostCallback *callback =
|
|
static_cast<nsResolveHostCallback *>(node);
|
|
node = node->next;
|
|
callback->OnLookupComplete(this, rec, status);
|
|
// NOTE: callback must not be dereferenced after this point!!
|
|
}
|
|
}
|
|
|
|
#if TTL_AVAILABLE
|
|
{
|
|
MutexAutoLock lock(mLock);
|
|
if (!mShutdown && !rec->mGetTtl
|
|
&& sDnsVariant != DNS_EXP_VARIANT_CONTROL && !rec->resolving) {
|
|
LOG(("Issuing second async lookup for TTL for %s.", rec->host));
|
|
rec->flags =
|
|
(rec->flags & ~RES_PRIORITY_MEDIUM) | RES_PRIORITY_LOW;
|
|
DebugOnly<nsresult> rv = IssueLookup(rec);
|
|
NS_WARN_IF_FALSE(NS_SUCCEEDED(rv),
|
|
"Could not issue second async lookup for TTL.");
|
|
}
|
|
}
|
|
#endif
|
|
|
|
NS_RELEASE(rec);
|
|
|
|
return LOOKUP_OK;
|
|
}
|
|
|
|
void
|
|
nsHostResolver::CancelAsyncRequest(const char *host,
|
|
uint16_t flags,
|
|
uint16_t af,
|
|
nsIDNSListener *aListener,
|
|
nsresult status)
|
|
|
|
{
|
|
MutexAutoLock lock(mLock);
|
|
|
|
// Lookup the host record associated with host, flags & address family
|
|
nsHostKey key = { host, flags, af };
|
|
nsHostDBEnt *he = static_cast<nsHostDBEnt *>
|
|
(PL_DHashTableOperate(&mDB, &key, PL_DHASH_LOOKUP));
|
|
if (he && he->rec) {
|
|
nsHostRecord* recPtr = nullptr;
|
|
PRCList *node = he->rec->callbacks.next;
|
|
// Remove the first nsDNSAsyncRequest callback which matches the
|
|
// supplied listener object
|
|
while (node != &he->rec->callbacks) {
|
|
nsResolveHostCallback *callback
|
|
= static_cast<nsResolveHostCallback *>(node);
|
|
if (callback && (callback->EqualsAsyncListener(aListener))) {
|
|
// Remove from the list of callbacks
|
|
PR_REMOVE_LINK(callback);
|
|
recPtr = he->rec;
|
|
callback->OnLookupComplete(this, recPtr, status);
|
|
break;
|
|
}
|
|
node = node->next;
|
|
}
|
|
|
|
// If there are no more callbacks, remove the hash table entry
|
|
if (recPtr && PR_CLIST_IS_EMPTY(&recPtr->callbacks)) {
|
|
PL_DHashTableOperate(&mDB, (nsHostKey *)recPtr, PL_DHASH_REMOVE);
|
|
// If record is on a Queue, remove it and then deref it
|
|
if (recPtr->next != recPtr) {
|
|
PR_REMOVE_LINK(recPtr);
|
|
NS_RELEASE(recPtr);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static size_t
|
|
SizeOfHostDBEntExcludingThis(PLDHashEntryHdr* hdr, MallocSizeOf mallocSizeOf,
|
|
void*)
|
|
{
|
|
nsHostDBEnt* ent = static_cast<nsHostDBEnt*>(hdr);
|
|
return ent->rec->SizeOfIncludingThis(mallocSizeOf);
|
|
}
|
|
|
|
size_t
|
|
nsHostResolver::SizeOfIncludingThis(MallocSizeOf mallocSizeOf) const
|
|
{
|
|
MutexAutoLock lock(mLock);
|
|
|
|
size_t n = mallocSizeOf(this);
|
|
n += PL_DHashTableSizeOfExcludingThis(&mDB, SizeOfHostDBEntExcludingThis,
|
|
mallocSizeOf);
|
|
|
|
// The following fields aren't measured.
|
|
// - mHighQ, mMediumQ, mLowQ, mEvictionQ, because they just point to
|
|
// nsHostRecords that also pointed to by entries |mDB|, and measured when
|
|
// |mDB| is measured.
|
|
|
|
return n;
|
|
}
|
|
|
|
void
|
|
nsHostResolver::ThreadFunc(void *arg)
|
|
{
|
|
LOG(("DNS lookup thread - starting execution.\n"));
|
|
|
|
static nsThreadPoolNaming naming;
|
|
naming.SetThreadPoolName(NS_LITERAL_CSTRING("DNS Resolver"));
|
|
|
|
#if defined(RES_RETRY_ON_FAILURE)
|
|
nsResState rs;
|
|
#endif
|
|
nsHostResolver *resolver = (nsHostResolver *)arg;
|
|
nsHostRecord *rec = nullptr;
|
|
AddrInfo *ai = nullptr;
|
|
|
|
while (rec || resolver->GetHostToLookup(&rec)) {
|
|
LOG(("DNS lookup thread - Calling getaddrinfo for host [%s].\n",
|
|
rec->host));
|
|
|
|
int flags = PR_AI_ADDRCONFIG;
|
|
if (!(rec->flags & RES_CANON_NAME))
|
|
flags |= PR_AI_NOCANONNAME;
|
|
|
|
TimeStamp startTime = TimeStamp::Now();
|
|
MOZ_EVENT_TRACER_EXEC(rec, "net::dns::resolve");
|
|
|
|
#if TTL_AVAILABLE
|
|
bool getTtl = rec->mGetTtl;
|
|
#else
|
|
bool getTtl = false;
|
|
#endif
|
|
|
|
// We need to remove IPv4 records manually
|
|
// because PR_GetAddrInfoByName doesn't support PR_AF_INET6.
|
|
bool disableIPv4 = rec->af == PR_AF_INET6;
|
|
uint16_t af = disableIPv4 ? PR_AF_UNSPEC : rec->af;
|
|
nsresult status = GetAddrInfo(rec->host, af, flags, &ai, getTtl);
|
|
#if defined(RES_RETRY_ON_FAILURE)
|
|
if (NS_FAILED(status) && rs.Reset()) {
|
|
status = GetAddrInfo(rec->host, af, flags, &ai, getTtl);
|
|
}
|
|
#endif
|
|
|
|
TimeDuration elapsed = TimeStamp::Now() - startTime;
|
|
uint32_t millis = static_cast<uint32_t>(elapsed.ToMilliseconds());
|
|
|
|
if (NS_SUCCEEDED(status)) {
|
|
Telemetry::Accumulate(!rec->addr_info_gencnt ?
|
|
Telemetry::DNS_LOOKUP_TIME :
|
|
Telemetry::DNS_RENEWAL_TIME,
|
|
millis);
|
|
}
|
|
else {
|
|
Telemetry::Accumulate(Telemetry::DNS_FAILED_LOOKUP_TIME, millis);
|
|
}
|
|
|
|
// OnLookupComplete may release "rec", long before we lose it.
|
|
LOG(("DNS lookup thread - lookup completed for host [%s]: %s.\n",
|
|
rec->host, ai ? "success" : "failure: unknown host"));
|
|
if (LOOKUP_RESOLVEAGAIN == resolver->OnLookupComplete(rec, status, ai)) {
|
|
// leave 'rec' assigned and loop to make a renewed host resolve
|
|
LOG(("DNS lookup thread - Re-resolving host [%s].\n",
|
|
rec->host));
|
|
} else {
|
|
rec = nullptr;
|
|
}
|
|
}
|
|
NS_RELEASE(resolver);
|
|
LOG(("DNS lookup thread - queue empty, thread finished.\n"));
|
|
}
|
|
|
|
nsresult
|
|
nsHostResolver::Create(uint32_t maxCacheEntries,
|
|
uint32_t defaultCacheEntryLifetime,
|
|
uint32_t defaultGracePeriod,
|
|
nsHostResolver **result)
|
|
{
|
|
#if defined(PR_LOGGING)
|
|
if (!gHostResolverLog)
|
|
gHostResolverLog = PR_NewLogModule("nsHostResolver");
|
|
#endif
|
|
|
|
nsHostResolver *res = new nsHostResolver(maxCacheEntries, defaultCacheEntryLifetime,
|
|
defaultGracePeriod);
|
|
if (!res)
|
|
return NS_ERROR_OUT_OF_MEMORY;
|
|
NS_ADDREF(res);
|
|
|
|
nsresult rv = res->Init();
|
|
if (NS_FAILED(rv))
|
|
NS_RELEASE(res);
|
|
|
|
*result = res;
|
|
return rv;
|
|
}
|
|
|
|
PLDHashOperator
|
|
CacheEntryEnumerator(PLDHashTable *table, PLDHashEntryHdr *entry,
|
|
uint32_t number, void *arg)
|
|
{
|
|
// We don't pay attention to address literals, only resolved domains.
|
|
// Also require a host.
|
|
nsHostRecord *rec = static_cast<nsHostDBEnt*>(entry)->rec;
|
|
MOZ_ASSERT(rec, "rec should never be null here!");
|
|
if (!rec || !rec->addr_info || !rec->host) {
|
|
return PL_DHASH_NEXT;
|
|
}
|
|
|
|
DNSCacheEntries info;
|
|
info.hostname = rec->host;
|
|
info.family = rec->af;
|
|
info.expiration =
|
|
(int64_t)(rec->mValidEnd - TimeStamp::NowLoRes()).ToSeconds();
|
|
if (info.expiration <= 0) {
|
|
// We only need valid DNS cache entries
|
|
return PL_DHASH_NEXT;
|
|
}
|
|
|
|
{
|
|
MutexAutoLock lock(rec->addr_info_lock);
|
|
|
|
NetAddr *addr = nullptr;
|
|
NetAddrElement *addrElement = rec->addr_info->mAddresses.getFirst();
|
|
if (addrElement) {
|
|
addr = &addrElement->mAddress;
|
|
}
|
|
while (addr) {
|
|
char buf[kIPv6CStrBufSize];
|
|
if (NetAddrToString(addr, buf, sizeof(buf))) {
|
|
info.hostaddr.AppendElement(buf);
|
|
}
|
|
addr = nullptr;
|
|
addrElement = addrElement->getNext();
|
|
if (addrElement) {
|
|
addr = &addrElement->mAddress;
|
|
}
|
|
}
|
|
}
|
|
|
|
nsTArray<DNSCacheEntries> *args = static_cast<nsTArray<DNSCacheEntries> *>(arg);
|
|
args->AppendElement(info);
|
|
|
|
return PL_DHASH_NEXT;
|
|
}
|
|
|
|
void
|
|
nsHostResolver::GetDNSCacheEntries(nsTArray<DNSCacheEntries> *args)
|
|
{
|
|
PL_DHashTableEnumerate(&mDB, CacheEntryEnumerator, args);
|
|
}
|