diff options
Diffstat (limited to 'netwerk/dns/nsHostResolver.cpp')
-rw-r--r-- | netwerk/dns/nsHostResolver.cpp | 2325 |
1 files changed, 2325 insertions, 0 deletions
diff --git a/netwerk/dns/nsHostResolver.cpp b/netwerk/dns/nsHostResolver.cpp new file mode 100644 index 0000000000..268e2ec38b --- /dev/null +++ b/netwerk/dns/nsHostResolver.cpp @@ -0,0 +1,2325 @@ +/* vim:set ts=4 sw=2 sts=2 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(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 "nsIThreadManager.h" +#include "nsComponentManagerUtils.h" +#include "nsNetUtil.h" +#include "nsPrintfCString.h" +#include "nsXPCOMCIDInternal.h" +#include "prthread.h" +#include "prerror.h" +#include "prtime.h" +#include "mozilla/Logging.h" +#include "PLDHashTable.h" +#include "plstr.h" +#include "nsQueryObject.h" +#include "nsURLHelper.h" +#include "nsThreadUtils.h" +#include "nsThreadPool.h" +#include "GetAddrInfo.h" +#include "GeckoProfiler.h" +#include "TRR.h" +#include "TRRQuery.h" +#include "TRRService.h" + +#include "mozilla/Atomics.h" +#include "mozilla/HashFunctions.h" +#include "mozilla/TimeStamp.h" +#include "mozilla/Telemetry.h" +#include "mozilla/DebugOnly.h" +#include "mozilla/Preferences.h" +#include "mozilla/StaticPrefs_network.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 + +static_assert( + HighThreadThreshold <= MAX_RESOLVER_THREADS, + "High Thread Threshold should be less equal Maximum allowed thread"); + +//---------------------------------------------------------------------------- + +namespace mozilla::net { +LazyLogModule gHostResolverLog("nsHostResolver"); +#define LOG(args) \ + MOZ_LOG(mozilla::net::gHostResolverLog, mozilla::LogLevel::Debug, args) +#define LOG1(args) \ + MOZ_LOG(mozilla::net::gHostResolverLog, mozilla::LogLevel::Error, args) +#define LOG_ENABLED() \ + MOZ_LOG_TEST(mozilla::net::gHostResolverLog, mozilla::LogLevel::Debug) +} // namespace mozilla::net + +//---------------------------------------------------------------------------- + +#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; +} + +//---------------------------------------------------------------------------- +// 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 | nsHostResolver::RES_DISABLE_TRR | \ + nsIDNSService::RESOLVE_TRR_MODE_MASK | nsHostResolver::RES_IP_HINT)) + +#define IS_ADDR_TYPE(_type) ((_type) == nsIDNSService::RESOLVE_TYPE_DEFAULT) +#define IS_OTHER_TYPE(_type) ((_type) != nsIDNSService::RESOLVE_TYPE_DEFAULT) + +nsHostKey::nsHostKey(const nsACString& aHost, const nsACString& aTrrServer, + uint16_t aType, uint16_t aFlags, uint16_t aAf, bool aPb, + const nsACString& aOriginsuffix) + : host(aHost), + mTrrServer(aTrrServer), + type(aType), + flags(aFlags), + af(aAf), + pb(aPb), + originSuffix(aOriginsuffix) {} + +bool nsHostKey::operator==(const nsHostKey& other) const { + return host == other.host && mTrrServer == other.mTrrServer && + type == other.type && + RES_KEY_FLAGS(flags) == RES_KEY_FLAGS(other.flags) && af == other.af && + originSuffix == other.originSuffix; +} + +PLDHashNumber nsHostKey::Hash() const { + return AddToHash(HashString(host.get()), HashString(mTrrServer.get()), type, + RES_KEY_FLAGS(flags), af, HashString(originSuffix.get())); +} + +size_t nsHostKey::SizeOfExcludingThis( + mozilla::MallocSizeOf mallocSizeOf) const { + size_t n = 0; + n += host.SizeOfExcludingThisIfUnshared(mallocSizeOf); + n += mTrrServer.SizeOfExcludingThisIfUnshared(mallocSizeOf); + n += originSuffix.SizeOfExcludingThisIfUnshared(mallocSizeOf); + return n; +} + +NS_IMPL_ISUPPORTS0(nsHostRecord) + +nsHostRecord::nsHostRecord(const nsHostKey& key) + : nsHostKey(key), + mEffectiveTRRMode(nsIRequest::TRR_DEFAULT_MODE), + mTRRQuery("nsHostRecord.mTRRQuery"), + mResolving(0), + negative(false), + mDoomed(false) {} + +void nsHostRecord::Invalidate() { mDoomed = true; } + +void nsHostRecord::Cancel() { + RefPtr<TRRQuery> query; + { + auto lock = mTRRQuery.Lock(); + query.swap(lock.ref()); + } + + if (query) { + query->Cancel(); + } +} + +nsHostRecord::ExpirationStatus nsHostRecord::CheckExpiration( + const mozilla::TimeStamp& now) const { + if (!mGraceStart.IsNull() && now >= mGraceStart && !mValidEnd.IsNull() && + now < mValidEnd) { + return nsHostRecord::EXP_GRACE; + } + if (!mValidEnd.IsNull() && now < mValidEnd) { + return nsHostRecord::EXP_VALID; + } + + return nsHostRecord::EXP_EXPIRED; +} + +void nsHostRecord::SetExpiration(const mozilla::TimeStamp& now, + unsigned int valid, unsigned int grace) { + mValidStart = now; + if ((valid + grace) < 60) { + grace = 60 - valid; + LOG(("SetExpiration: artificially bumped grace to %d\n", grace)); + } + mGraceStart = now + TimeDuration::FromSeconds(valid); + mValidEnd = now + TimeDuration::FromSeconds(valid + grace); +} + +void nsHostRecord::CopyExpirationTimesAndFlagsFrom( + const nsHostRecord* aFromHostRecord) { + // This is used to copy information from a cache entry to a record. All + // information necessary for HasUsableRecord needs to be copied. + mValidStart = aFromHostRecord->mValidStart; + mValidEnd = aFromHostRecord->mValidEnd; + mGraceStart = aFromHostRecord->mGraceStart; + mDoomed = aFromHostRecord->mDoomed; +} + +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; + } + + if (negative) { + return true; + } + + return HasUsableResultInternal(); +} + +static size_t SizeOfResolveHostCallbackListExcludingHead( + const mozilla::LinkedList<RefPtr<nsResolveHostCallback>>& aCallbacks, + MallocSizeOf mallocSizeOf) { + size_t n = aCallbacks.sizeOfExcludingThis(mallocSizeOf); + + for (const nsResolveHostCallback* t = aCallbacks.getFirst(); t; + t = t->getNext()) { + n += t->SizeOfIncludingThis(mallocSizeOf); + } + + return n; +} + +NS_IMPL_ISUPPORTS_INHERITED(AddrHostRecord, nsHostRecord, AddrHostRecord) + +AddrHostRecord::AddrHostRecord(const nsHostKey& key) + : nsHostRecord(key), + addr_info_lock("AddrHostRecord.addr_info_lock"), + addr_info_gencnt(0), + addr_info(nullptr), + addr(nullptr), + mTRRUsed(false), + mTRRSuccess(0), + mNativeSuccess(0) {} + +AddrHostRecord::~AddrHostRecord() { + mCallbacks.clear(); + Telemetry::Accumulate(Telemetry::DNS_BLACKLIST_COUNT, mUnusableCount); +} + +bool AddrHostRecord::Blocklisted(const NetAddr* aQuery) { + addr_info_lock.AssertCurrentThreadOwns(); + LOG(("Checking unusable list for host [%s], host record [%p].\n", host.get(), + this)); + + // skip the string conversion for the common case of no blocklist + if (!mUnusableItems.Length()) { + return false; + } + + char buf[kIPv6CStrBufSize]; + if (!aQuery->ToStringBuffer(buf, sizeof(buf))) { + return false; + } + nsDependentCString strQuery(buf); + + for (uint32_t i = 0; i < mUnusableItems.Length(); i++) { + if (mUnusableItems.ElementAt(i).Equals(strQuery)) { + LOG(("Address [%s] is blocklisted for host [%s].\n", buf, host.get())); + return true; + } + } + + return false; +} + +void AddrHostRecord::ReportUnusable(const NetAddr* aAddress) { + addr_info_lock.AssertCurrentThreadOwns(); + LOG( + ("Adding address to blocklist for host [%s], host record [%p]." + "used trr=%d\n", + host.get(), this, mTRRSuccess)); + + ++mUnusableCount; + + char buf[kIPv6CStrBufSize]; + if (aAddress->ToStringBuffer(buf, sizeof(buf))) { + LOG( + ("Successfully adding address [%s] to blocklist for host " + "[%s].\n", + buf, host.get())); + mUnusableItems.AppendElement(nsCString(buf)); + } +} + +void AddrHostRecord::ResetBlocklist() { + addr_info_lock.AssertCurrentThreadOwns(); + LOG(("Resetting blocklist for host [%s], host record [%p].\n", host.get(), + this)); + mUnusableItems.Clear(); +} + +size_t AddrHostRecord::SizeOfIncludingThis(MallocSizeOf mallocSizeOf) const { + size_t n = mallocSizeOf(this); + + n += nsHostKey::SizeOfExcludingThis(mallocSizeOf); + n += SizeOfResolveHostCallbackListExcludingHead(mCallbacks, mallocSizeOf); + + n += addr_info ? addr_info->SizeOfIncludingThis(mallocSizeOf) : 0; + n += mallocSizeOf(addr.get()); + + n += mUnusableItems.ShallowSizeOfExcludingThis(mallocSizeOf); + for (size_t i = 0; i < mUnusableItems.Length(); i++) { + n += mUnusableItems[i].SizeOfExcludingThisIfUnshared(mallocSizeOf); + } + return n; +} + +bool AddrHostRecord::HasUsableResultInternal() const { + return addr_info || addr; +} + +// Returns true if the entry can be removed, or false if it should be left. +// Sets ResolveAgain true for entries being resolved right now. +bool AddrHostRecord::RemoveOrRefresh(bool aTrrToo) { + // no need to flush TRRed names, they're not resolved "locally" + MutexAutoLock lock(addr_info_lock); + if (addr_info && !aTrrToo && addr_info->IsTRR()) { + return false; + } + if (LoadNative()) { + if (!onQueue()) { + // 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. + StoreResolveAgain(true); + } + // if onQueue is true, the host entry is already added to the cache + // but is still pending to get resolved: just leave it in hash. + return false; + } + // Already resolved; not in a pending state; remove from cache + return true; +} + +void AddrHostRecord::ResolveComplete() { + if (LoadNativeUsed()) { + if (mNativeSuccess) { + uint32_t millis = static_cast<uint32_t>(mNativeDuration.ToMilliseconds()); + Telemetry::Accumulate(Telemetry::DNS_NATIVE_LOOKUP_TIME, millis); + } + AccumulateCategoricalKeyed( + TRRService::AutoDetectedKey(), + mNativeSuccess ? Telemetry::LABELS_DNS_LOOKUP_DISPOSITION2::osOK + : Telemetry::LABELS_DNS_LOOKUP_DISPOSITION2::osFail); + } + + if (mTRRUsed) { + if (mTRRSuccess) { + uint32_t millis = static_cast<uint32_t>(mTrrDuration.ToMilliseconds()); + Telemetry::Accumulate(Telemetry::DNS_TRR_LOOKUP_TIME2, + TRRService::AutoDetectedKey(), millis); + } + AccumulateCategoricalKeyed( + TRRService::AutoDetectedKey(), + mTRRSuccess ? Telemetry::LABELS_DNS_LOOKUP_DISPOSITION2::trrOK + : Telemetry::LABELS_DNS_LOOKUP_DISPOSITION2::trrFail); + } + + if (nsHostResolver::Mode() == MODE_TRRFIRST) { + Telemetry::Accumulate(Telemetry::TRR_SKIP_REASON_TRR_FIRST, + mTRRTRRSkippedReason); + + if (mNativeSuccess) { + Telemetry::Accumulate(Telemetry::TRR_SKIP_REASON_DNS_WORKED, + mTRRTRRSkippedReason); + } + } + + if (mEffectiveTRRMode == nsIRequest::TRR_FIRST_MODE) { + if (flags & nsIDNSService::RESOLVE_DISABLE_TRR) { + // TRR is disabled on request, which is a next-level back-off method. + Telemetry::Accumulate(Telemetry::DNS_TRR_DISABLED2, + TRRService::AutoDetectedKey(), mNativeSuccess); + } else { + if (mTRRSuccess) { + AccumulateCategoricalKeyed(TRRService::AutoDetectedKey(), + Telemetry::LABELS_DNS_TRR_FIRST3::TRR); + } else if (mNativeSuccess) { + if (mTRRUsed) { + AccumulateCategoricalKeyed( + TRRService::AutoDetectedKey(), + Telemetry::LABELS_DNS_TRR_FIRST3::NativeAfterTRR); + } else { + AccumulateCategoricalKeyed(TRRService::AutoDetectedKey(), + Telemetry::LABELS_DNS_TRR_FIRST3::Native); + } + } else { + AccumulateCategoricalKeyed( + TRRService::AutoDetectedKey(), + Telemetry::LABELS_DNS_TRR_FIRST3::BothFailed); + } + } + } + + switch (mEffectiveTRRMode) { + case nsIRequest::TRR_DISABLED_MODE: + AccumulateCategorical(Telemetry::LABELS_DNS_LOOKUP_ALGORITHM::nativeOnly); + break; + case nsIRequest::TRR_FIRST_MODE: + AccumulateCategorical(Telemetry::LABELS_DNS_LOOKUP_ALGORITHM::trrFirst); + break; + case nsIRequest::TRR_ONLY_MODE: + AccumulateCategorical(Telemetry::LABELS_DNS_LOOKUP_ALGORITHM::trrOnly); + break; + case nsIRequest::TRR_DEFAULT_MODE: + MOZ_ASSERT_UNREACHABLE("We should not have a default value here"); + break; + } + + if (mTRRUsed && !mTRRSuccess && mNativeSuccess && gTRRService) { + gTRRService->AddToBlocklist(nsCString(host), originSuffix, pb, true); + } +} + +AddrHostRecord::DnsPriority AddrHostRecord::GetPriority(uint16_t aFlags) { + if (IsHighPriority(aFlags)) { + return AddrHostRecord::DNS_PRIORITY_HIGH; + } + if (IsMediumPriority(aFlags)) { + return AddrHostRecord::DNS_PRIORITY_MEDIUM; + } + + return AddrHostRecord::DNS_PRIORITY_LOW; +} + +NS_IMPL_ISUPPORTS_INHERITED(TypeHostRecord, nsHostRecord, TypeHostRecord, + nsIDNSTXTRecord, nsIDNSHTTPSSVCRecord) + +TypeHostRecord::TypeHostRecord(const nsHostKey& key) + : nsHostRecord(key), + DNSHTTPSSVCRecordBase(key.host), + mResultsLock("TypeHostRecord.mResultsLock"), + mAllRecordsExcluded(false) {} + +TypeHostRecord::~TypeHostRecord() { mCallbacks.clear(); } + +bool TypeHostRecord::HasUsableResultInternal() const { + return !mResults.is<Nothing>(); +} + +NS_IMETHODIMP TypeHostRecord::GetRecords(CopyableTArray<nsCString>& aRecords) { + // deep copy + MutexAutoLock lock(mResultsLock); + + if (!mResults.is<TypeRecordTxt>()) { + return NS_ERROR_NOT_AVAILABLE; + } + aRecords = mResults.as<CopyableTArray<nsCString>>(); + return NS_OK; +} + +NS_IMETHODIMP TypeHostRecord::GetRecordsAsOneString(nsACString& aRecords) { + // deep copy + MutexAutoLock lock(mResultsLock); + + if (!mResults.is<TypeRecordTxt>()) { + return NS_ERROR_NOT_AVAILABLE; + } + auto& results = mResults.as<CopyableTArray<nsCString>>(); + for (uint32_t i = 0; i < results.Length(); i++) { + aRecords.Append(results[i]); + } + return NS_OK; +} + +size_t TypeHostRecord::SizeOfIncludingThis(MallocSizeOf mallocSizeOf) const { + size_t n = mallocSizeOf(this); + + n += nsHostKey::SizeOfExcludingThis(mallocSizeOf); + n += SizeOfResolveHostCallbackListExcludingHead(mCallbacks, mallocSizeOf); + + return n; +} + +uint32_t TypeHostRecord::GetType() { + MutexAutoLock lock(mResultsLock); + + return mResults.match( + [](TypeRecordEmpty&) { + MOZ_ASSERT(false, "This should never be the case"); + return nsIDNSService::RESOLVE_TYPE_DEFAULT; + }, + [](TypeRecordTxt&) { return nsIDNSService::RESOLVE_TYPE_TXT; }, + [](TypeRecordHTTPSSVC&) { return nsIDNSService::RESOLVE_TYPE_HTTPSSVC; }); +} + +TypeRecordResultType TypeHostRecord::GetResults() { + MutexAutoLock lock(mResultsLock); + return mResults; +} + +NS_IMETHODIMP +TypeHostRecord::GetRecords(nsTArray<RefPtr<nsISVCBRecord>>& aRecords) { + MutexAutoLock lock(mResultsLock); + if (!mResults.is<TypeRecordHTTPSSVC>()) { + return NS_ERROR_NOT_AVAILABLE; + } + + auto& results = mResults.as<TypeRecordHTTPSSVC>(); + + for (const SVCB& r : results) { + RefPtr<nsISVCBRecord> rec = new mozilla::net::SVCBRecord(r); + aRecords.AppendElement(rec); + } + + return NS_OK; +} + +NS_IMETHODIMP +TypeHostRecord::GetServiceModeRecord(bool aNoHttp2, bool aNoHttp3, + nsISVCBRecord** aRecord) { + MutexAutoLock lock(mResultsLock); + if (!mResults.is<TypeRecordHTTPSSVC>()) { + return NS_ERROR_NOT_AVAILABLE; + } + + auto& results = mResults.as<TypeRecordHTTPSSVC>(); + nsCOMPtr<nsISVCBRecord> result = GetServiceModeRecordInternal( + aNoHttp2, aNoHttp3, results, mAllRecordsExcluded); + if (!result) { + return NS_ERROR_NOT_AVAILABLE; + } + + result.forget(aRecord); + return NS_OK; +} + +NS_IMETHODIMP +TypeHostRecord::GetAllRecordsWithEchConfig( + bool aNoHttp2, bool aNoHttp3, bool* aAllRecordsHaveEchConfig, + bool* aAllRecordsInH3ExcludedList, + nsTArray<RefPtr<nsISVCBRecord>>& aResult) { + MutexAutoLock lock(mResultsLock); + if (!mResults.is<TypeRecordHTTPSSVC>()) { + return NS_ERROR_NOT_AVAILABLE; + } + + auto& records = mResults.as<TypeRecordHTTPSSVC>(); + GetAllRecordsWithEchConfigInternal(aNoHttp2, aNoHttp3, records, + aAllRecordsHaveEchConfig, + aAllRecordsInH3ExcludedList, aResult); + return NS_OK; +} + +NS_IMETHODIMP +TypeHostRecord::GetHasIPAddresses(bool* aResult) { + NS_ENSURE_ARG(aResult); + + if (!mResults.is<TypeRecordHTTPSSVC>()) { + return NS_ERROR_NOT_AVAILABLE; + } + + auto& results = mResults.as<TypeRecordHTTPSSVC>(); + *aResult = HasIPAddressesInternal(results); + return NS_OK; +} + +NS_IMETHODIMP +TypeHostRecord::GetAllRecordsExcluded(bool* aResult) { + NS_ENSURE_ARG(aResult); + + if (!mResults.is<TypeRecordHTTPSSVC>()) { + return NS_ERROR_NOT_AVAILABLE; + } + + *aResult = mAllRecordsExcluded; + return NS_OK; +} + +//---------------------------------------------------------------------------- + +static const char kPrefGetTtl[] = "network.dns.get-ttl"; +static const char kPrefNativeIsLocalhost[] = "network.dns.native-is-localhost"; +static const char kPrefThreadIdleTime[] = + "network.dns.resolver-thread-extra-idle-time-seconds"; +static bool sGetTtlEnabled = false; +mozilla::Atomic<bool, mozilla::Relaxed> gNativeIsLocalhost; + +static void DnsPrefChanged(const char* aPref, void* aSelf) { + MOZ_ASSERT(NS_IsMainThread(), + "Should be getting pref changed notification on main thread!"); + + MOZ_ASSERT(aSelf); + + if (!strcmp(aPref, kPrefGetTtl)) { +#ifdef DNSQUERY_AVAILABLE + sGetTtlEnabled = Preferences::GetBool(kPrefGetTtl); +#endif + } else if (!strcmp(aPref, kPrefNativeIsLocalhost)) { + gNativeIsLocalhost = Preferences::GetBool(kPrefNativeIsLocalhost); + } +} + +NS_IMPL_ISUPPORTS0(nsHostResolver) + +nsHostResolver::nsHostResolver(uint32_t maxCacheEntries, + uint32_t defaultCacheEntryLifetime, + uint32_t defaultGracePeriod) + : mMaxCacheEntries(maxCacheEntries), + mDefaultCacheLifetime(defaultCacheEntryLifetime), + mDefaultGracePeriod(defaultGracePeriod), + mLock("nsHostResolver.mLock"), + mIdleTaskCV(mLock, "nsHostResolver.mIdleTaskCV"), + mEvictionQSize(0), + mShutdown(true), + mNumIdleTasks(0), + mActiveTaskCount(0), + mActiveAnyThreadCount(0), + mPendingCount(0) { + mCreationTime = PR_Now(); + + mLongIdleTimeout = TimeDuration::FromSeconds(LongIdleTimeoutSeconds); + mShortIdleTimeout = TimeDuration::FromSeconds(ShortIdleTimeoutSeconds); +} + +nsHostResolver::~nsHostResolver() = default; + +nsresult nsHostResolver::Init() { + MOZ_ASSERT(NS_IsMainThread()); + if (NS_FAILED(GetAddrInfoInit())) { + return NS_ERROR_FAILURE; + } + + LOG(("nsHostResolver::Init this=%p", this)); + + mShutdown = false; + mNCS = NetworkConnectivityService::GetSingleton(); + + // 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 = Preferences::RegisterCallbackAndCall( + &DnsPrefChanged, kPrefGetTtl, this); + NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), + "Could not register DNS TTL pref callback."); + rv = Preferences::RegisterCallbackAndCall(&DnsPrefChanged, + kPrefNativeIsLocalhost, this); + NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), + "Could not register DNS pref callback."); + } + +#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 + + // We can configure the threadpool to keep threads alive for a while after + // the last ThreadFunc task has been executed. + int32_t poolTimeoutSecs = Preferences::GetInt(kPrefThreadIdleTime, 60); + uint32_t poolTimeoutMs; + if (poolTimeoutSecs < 0) { + // This means never shut down the idle threads + poolTimeoutMs = UINT32_MAX; + } else { + // We clamp down the idle time between 0 and one hour. + poolTimeoutMs = + mozilla::clamped<uint32_t>(poolTimeoutSecs * 1000, 0, 3600 * 1000); + } + + nsCOMPtr<nsIThreadPool> threadPool = new nsThreadPool(); + MOZ_ALWAYS_SUCCEEDS(threadPool->SetThreadLimit(MAX_RESOLVER_THREADS)); + MOZ_ALWAYS_SUCCEEDS(threadPool->SetIdleThreadLimit(MAX_RESOLVER_THREADS)); + MOZ_ALWAYS_SUCCEEDS(threadPool->SetIdleThreadTimeout(poolTimeoutMs)); + MOZ_ALWAYS_SUCCEEDS( + threadPool->SetThreadStackSize(nsIThreadManager::kThreadPoolStackSize)); + MOZ_ALWAYS_SUCCEEDS(threadPool->SetName("DNS Resolver"_ns)); + mResolverThreads = ToRefPtr(std::move(threadPool)); + + return NS_OK; +} + +void nsHostResolver::ClearPendingQueue( + LinkedList<RefPtr<nsHostRecord>>& aPendingQ) { + // loop through pending queue, erroring out pending lookups. + if (!aPendingQ.isEmpty()) { + for (const RefPtr<nsHostRecord>& rec : aPendingQ) { + rec->Cancel(); + if (rec->IsAddrRecord()) { + CompleteLookup(rec, NS_ERROR_ABORT, nullptr, rec->pb, rec->originSuffix, + rec->mTRRTRRSkippedReason); + } else { + mozilla::net::TypeRecordResultType empty(Nothing{}); + CompleteLookupByType(rec, NS_ERROR_ABORT, empty, 0, rec->pb); + } + } + } +} + +// +// 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(bool aTrrToo) { + MutexAutoLock lock(mLock); + mEvictionQSize = 0; + + // Clear the evictionQ and remove all its corresponding entries from + // the cache first + if (!mEvictionQ.isEmpty()) { + for (const RefPtr<nsHostRecord>& rec : mEvictionQ) { + rec->Cancel(); + mRecordDB.Remove(*static_cast<nsHostKey*>(rec)); + } + mEvictionQ.clear(); + } + + // Refresh the cache entries that are resolving RIGHT now, remove the rest. + for (auto iter = mRecordDB.Iter(); !iter.Done(); iter.Next()) { + nsHostRecord* record = iter.UserData(); + // Try to remove the record, or mark it for refresh. + // By-type records are from TRR. We do not need to flush those entry + // when the network has change, because they are not local. + if (record->IsAddrRecord()) { + RefPtr<AddrHostRecord> addrRec = do_QueryObject(record); + MOZ_ASSERT(addrRec); + if (addrRec->RemoveOrRefresh(aTrrToo)) { + if (record->isInList()) { + record->remove(); + } + iter.Remove(); + } + } + } +} + +void nsHostResolver::Shutdown() { + LOG(("Shutting down host resolver.\n")); + + { + DebugOnly<nsresult> rv = + Preferences::UnregisterCallback(&DnsPrefChanged, kPrefGetTtl, this); + NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), + "Could not unregister DNS TTL pref callback."); + } + + LinkedList<RefPtr<nsHostRecord>> pendingQHigh, pendingQMed, pendingQLow, + evictionQ; + + { + MutexAutoLock lock(mLock); + + mShutdown = true; + + // Move queues to temporary lists. + pendingQHigh = std::move(mHighQ); + pendingQMed = std::move(mMediumQ); + pendingQLow = std::move(mLowQ); + evictionQ = std::move(mEvictionQ); + + mEvictionQSize = 0; + mPendingCount = 0; + + if (mNumIdleTasks) { + mIdleTaskCV.NotifyAll(); + } + + for (auto iter = mRecordDB.Iter(); !iter.Done(); iter.Next()) { + iter.UserData()->Cancel(); + } + // empty host database + mRecordDB.Clear(); + + mNCS = nullptr; + } + + ClearPendingQueue(pendingQHigh); + ClearPendingQueue(pendingQMed); + ClearPendingQueue(pendingQLow); + + if (!evictionQ.isEmpty()) { + for (const RefPtr<nsHostRecord>& rec : evictionQ) { + rec->Cancel(); + } + } + + pendingQHigh.clear(); + pendingQMed.clear(); + pendingQLow.clear(); + evictionQ.clear(); + + // Shutdown the resolver threads, but with a timeout of 2 seconds (prefable). + // If the timeout is exceeded, any stuck threads will be leaked. + mResolverThreads->ShutdownWithTimeout( + StaticPrefs::network_dns_resolver_shutdown_timeout_ms()); + + { + mozilla::DebugOnly<nsresult> rv = GetAddrInfoShutdown(); + NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Failed to shutdown GetAddrInfo"); + } +} + +nsresult nsHostResolver::GetHostRecord(const nsACString& host, + const nsACString& aTrrServer, + uint16_t type, uint16_t flags, + uint16_t af, bool pb, + const nsCString& originSuffix, + nsHostRecord** result) { + MutexAutoLock lock(mLock); + nsHostKey key(host, aTrrServer, type, flags, af, pb, originSuffix); + + RefPtr<nsHostRecord>& entry = mRecordDB.GetOrInsert(key); + if (!entry) { + entry = InitRecord(key); + } + + RefPtr<nsHostRecord> rec = entry; + if (rec->IsAddrRecord()) { + RefPtr<AddrHostRecord> addrRec = do_QueryObject(rec); + if (addrRec->addr) { + return NS_ERROR_FAILURE; + } + } + + if (rec->mResolving) { + return NS_ERROR_FAILURE; + } + + *result = rec.forget().take(); + return NS_OK; +} + +nsHostRecord* nsHostResolver::InitRecord(const nsHostKey& key) { + if (IS_ADDR_TYPE(key.type)) { + return new AddrHostRecord(key); + } + return new TypeHostRecord(key); +} + +already_AddRefed<nsHostRecord> nsHostResolver::InitLoopbackRecord( + const nsHostKey& key, nsresult* aRv) { + MOZ_ASSERT(aRv); + MOZ_ASSERT(IS_ADDR_TYPE(key.type)); + + *aRv = NS_ERROR_FAILURE; + RefPtr<nsHostRecord> rec = InitRecord(key); + + nsTArray<NetAddr> addresses; + PRNetAddr prAddr; + memset(&prAddr, 0, sizeof(prAddr)); + if (key.af == PR_AF_INET || key.af == PR_AF_UNSPEC) { + MOZ_RELEASE_ASSERT(PR_StringToNetAddr("127.0.0.1", &prAddr) == PR_SUCCESS); + addresses.AppendElement(NetAddr(&prAddr)); + } + if (key.af == PR_AF_INET6 || key.af == PR_AF_UNSPEC) { + MOZ_RELEASE_ASSERT(PR_StringToNetAddr("::1", &prAddr) == PR_SUCCESS); + addresses.AppendElement(NetAddr(&prAddr)); + } + + RefPtr<AddrInfo> ai = new AddrInfo(rec->host, 0, std::move(addresses)); + + RefPtr<AddrHostRecord> addrRec = do_QueryObject(rec); + MutexAutoLock lock(addrRec->addr_info_lock); + addrRec->addr_info = ai; + addrRec->SetExpiration(TimeStamp::NowLoRes(), mDefaultCacheLifetime, + mDefaultGracePeriod); + addrRec->negative = false; + + *aRv = NS_OK; + return rec.forget(); +} + +nsresult nsHostResolver::ResolveHost(const nsACString& aHost, + const nsACString& aTrrServer, + uint16_t type, + const OriginAttributes& aOriginAttributes, + uint16_t flags, uint16_t af, + nsResolveHostCallback* aCallback) { + nsAutoCString host(aHost); + NS_ENSURE_TRUE(!host.IsEmpty(), NS_ERROR_UNEXPECTED); + + nsAutoCString originSuffix; + aOriginAttributes.CreateSuffix(originSuffix); + LOG(("Resolving host [%s]<%s>%s%s type %d. [this=%p]\n", host.get(), + originSuffix.get(), flags & RES_BYPASS_CACHE ? " - bypassing cache" : "", + flags & RES_REFRESH_CACHE ? " - refresh cache" : "", type, this)); + + // ensure that we are working with a valid hostname before proceeding. see + // bug 304904 for details. + if (!net_IsValidHostName(host)) { + return NS_ERROR_UNKNOWN_HOST; + } + + // By-Type requests use only TRR. If TRR is disabled we can return + // immediately. + if (IS_OTHER_TYPE(type) && Mode() == MODE_TRROFF) { + return NS_ERROR_UNKNOWN_HOST; + } + + // 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)); + + if (IS_OTHER_TYPE(type) && + (PR_StringToNetAddr(host.get(), &tempAddr) == PR_SUCCESS)) { + // For by-type queries the host cannot be IP literal. + return NS_ERROR_UNKNOWN_HOST; + } + memset(&tempAddr, 0, sizeof(PRNetAddr)); + + RefPtr<nsResolveHostCallback> callback(aCallback); + // if result is set inside the lock, then we need to issue the + // callback before returning. + RefPtr<nsHostRecord> result; + nsresult status = NS_OK, rv = NS_OK; + { + MutexAutoLock lock(mLock); + + if (mShutdown) { + return NS_ERROR_NOT_INITIALIZED; + } + + // 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. + + bool excludedFromTRR = false; + + if (gTRRService && gTRRService->IsExcludedFromTRR(host)) { + flags |= RES_DISABLE_TRR; + excludedFromTRR = true; + + if (!aTrrServer.IsEmpty()) { + return NS_ERROR_UNKNOWN_HOST; + } + } + + nsHostKey key(host, aTrrServer, type, flags, af, + (aOriginAttributes.mPrivateBrowsingId > 0), originSuffix); + + // Check if we have a localhost domain, if so hardcode to loopback + if (IS_ADDR_TYPE(type) && IsLoopbackHostname(host)) { + nsresult rv; + RefPtr<nsHostRecord> result = InitLoopbackRecord(key, &rv); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + MOZ_ASSERT(result); + aCallback->OnResolveHostComplete(this, result, NS_OK); + return NS_OK; + } + + RefPtr<nsHostRecord>& entry = mRecordDB.GetOrInsert(key); + if (!entry) { + entry = InitRecord(key); + } + + RefPtr<nsHostRecord> rec = entry; + RefPtr<AddrHostRecord> addrRec = do_QueryObject(rec); + MOZ_ASSERT(rec, "Record should not be null"); + MOZ_ASSERT((IS_ADDR_TYPE(type) && rec->IsAddrRecord() && addrRec) || + (IS_OTHER_TYPE(type) && !rec->IsAddrRecord())); + + if (excludedFromTRR) { + rec->RecordReason(nsHostRecord::TRR_EXCLUDED); + } + + if (!(flags & RES_BYPASS_CACHE) && + rec->HasUsableResult(TimeStamp::NowLoRes(), flags)) { + LOG((" Using cached record for host [%s].\n", host.get())); + // put reference to host record on stack... + result = rec; + if (IS_ADDR_TYPE(type)) { + 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(rec, host); + + if (rec->negative) { + LOG((" Negative cache entry for host [%s].\n", host.get())); + if (IS_ADDR_TYPE(type)) { + Telemetry::Accumulate(Telemetry::DNS_LOOKUP_METHOD2, + METHOD_NEGATIVE_HIT); + } + status = NS_ERROR_UNKNOWN_HOST; + } + + // Check whether host is a IP address for A/AAAA queries. + // For by-type records we have already checked at the beginning of + // this function. + } else if (addrRec && addrRec->addr) { + // if the host name is an IP address literal and has been + // parsed, go ahead and use it. + LOG((" Using cached address for IP Literal [%s].\n", host.get())); + Telemetry::Accumulate(Telemetry::DNS_LOOKUP_METHOD2, METHOD_LITERAL); + result = rec; + } else if (addrRec && + PR_StringToNetAddr(host.get(), &tempAddr) == PR_SUCCESS) { + // 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.) + LOG((" Host is IP Literal [%s].\n", host.get())); + + // ok, just copy the result into the host record, and be + // done with it! ;-) + addrRec->addr = MakeUnique<NetAddr>(); + PRNetAddrToNetAddr(&tempAddr, addrRec->addr.get()); + // put reference to host record on stack... + Telemetry::Accumulate(Telemetry::DNS_LOOKUP_METHOD2, METHOD_LITERAL); + result = rec; + + // Check if we have received too many requests. + } else if (mPendingCount >= MAX_NON_PRIORITY_REQUESTS && + !IsHighPriority(flags) && !rec->mResolving) { + LOG( + (" Lookup queue full: dropping %s priority request for " + "host [%s].\n", + IsMediumPriority(flags) ? "medium" : "low", host.get())); + if (IS_ADDR_TYPE(type)) { + 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; + + // Check if the offline flag is set. + } else if (flags & RES_OFFLINE) { + LOG((" Offline request for host [%s]; ignoring.\n", host.get())); + rv = NS_ERROR_OFFLINE; + + // We do not have a valid result till here. + // A/AAAA request can check for an alternative entry like AF_UNSPEC. + // Otherwise we need to start a new query. + } else if (!rec->mResolving) { + // If this is an IPV4 or IPV6 specific request, check if there is + // an AF_UNSPEC entry we can use. Otherwise, hit the resolver... + if (addrRec && !(flags & RES_BYPASS_CACHE) && + ((af == PR_AF_INET) || (af == PR_AF_INET6))) { + // Check for an AF_UNSPEC entry. + + const nsHostKey unspecKey( + host, aTrrServer, nsIDNSService::RESOLVE_TYPE_DEFAULT, flags, + PR_AF_UNSPEC, (aOriginAttributes.mPrivateBrowsingId > 0), + originSuffix); + RefPtr<nsHostRecord> unspecRec = mRecordDB.Get(unspecKey); + + TimeStamp now = TimeStamp::NowLoRes(); + if (unspecRec && unspecRec->HasUsableResult(now, flags)) { + MOZ_ASSERT(unspecRec->IsAddrRecord()); + + RefPtr<AddrHostRecord> addrUnspecRec = do_QueryObject(unspecRec); + MOZ_ASSERT(addrUnspecRec); + MOZ_ASSERT(addrUnspecRec->addr_info || addrUnspecRec->negative, + "Entry should be resolved or negative."); + + LOG((" Trying AF_UNSPEC entry for host [%s] af: %s.\n", host.get(), + (af == PR_AF_INET) ? "AF_INET" : "AF_INET6")); + + // We need to lock in case any other thread is reading + // addr_info. + MutexAutoLock lock(addrRec->addr_info_lock); + + addrRec->addr_info = nullptr; + addrRec->addr_info_gencnt++; + if (unspecRec->negative) { + rec->negative = unspecRec->negative; + rec->CopyExpirationTimesAndFlagsFrom(unspecRec); + } else if (addrUnspecRec->addr_info) { + MutexAutoLock lock(addrUnspecRec->addr_info_lock); + if (addrUnspecRec->addr_info) { + // Search for any valid address in the AF_UNSPEC entry + // in the cache (not blocklisted and from the right + // family). + nsTArray<NetAddr> addresses; + for (const auto& addr : addrUnspecRec->addr_info->Addresses()) { + if ((af == addr.inet.family) && + !addrUnspecRec->Blocklisted(&addr)) { + addresses.AppendElement(addr); + } + } + if (!addresses.IsEmpty()) { + addrRec->addr_info = new AddrInfo( + addrUnspecRec->addr_info->Hostname(), + addrUnspecRec->addr_info->CanonicalHostname(), + addrUnspecRec->addr_info->IsTRR(), std::move(addresses)); + addrRec->addr_info_gencnt++; + rec->CopyExpirationTimesAndFlagsFrom(unspecRec); + } + } + } + // Now check if we have a new record. + if (rec->HasUsableResult(now, flags)) { + result = rec; + if (rec->negative) { + status = NS_ERROR_UNKNOWN_HOST; + } + Telemetry::Accumulate(Telemetry::DNS_LOOKUP_METHOD2, METHOD_HIT); + ConditionallyRefreshRecord(rec, host); + } else if (af == PR_AF_INET6) { + // 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. + LOG( + (" No AF_INET6 in AF_UNSPEC entry: " + "host [%s] unknown host.", + host.get())); + result = rec; + rec->negative = true; + status = NS_ERROR_UNKNOWN_HOST; + Telemetry::Accumulate(Telemetry::DNS_LOOKUP_METHOD2, + METHOD_NEGATIVE_HIT); + } + } + } + + // If this is a by-type request or if no valid record was found + // in the cache or this is an AF_UNSPEC request, then start a + // new lookup. + if (!result) { + LOG((" No usable record in cache for host [%s] type %d.", host.get(), + type)); + + if (flags & RES_REFRESH_CACHE) { + rec->Invalidate(); + } + + // Add callback to the list of pending callbacks. + rec->mCallbacks.insertBack(callback); + rec->flags = flags; + rv = NameLookup(rec); + if (IS_ADDR_TYPE(type)) { + Telemetry::Accumulate(Telemetry::DNS_LOOKUP_METHOD2, + METHOD_NETWORK_FIRST); + } + if (NS_FAILED(rv) && callback->isInList()) { + callback->remove(); + } else { + LOG( + (" DNS lookup for host [%s] blocking " + "pending 'getaddrinfo' or trr query: " + "callback [%p]", + host.get(), callback.get())); + } + } + } else { + LOG( + (" Host [%s] is being resolved. Appending callback " + "[%p].", + host.get(), callback.get())); + + rec->mCallbacks.insertBack(callback); + + // Only A/AAAA records are place in a queue. The queues are for + // the native resolver, therefore by-type request are never put + // into a queue. + if (addrRec && addrRec->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(rec->flags)) { + // Move from (low|med) to high. + rec->remove(); + mHighQ.insertBack(rec); + rec->flags = flags; + ConditionallyCreateThread(rec); + } else if (IsMediumPriority(flags) && IsLowPriority(rec->flags)) { + // Move from low to med. + rec->remove(); + mMediumQ.insertBack(rec); + rec->flags = flags; + mIdleTaskCV.Notify(); + } + } + } + + if (result && callback->isInList()) { + callback->remove(); + } + } // lock + + if (result) { + callback->OnResolveHostComplete(this, result, status); + } + + return rv; +} + +void nsHostResolver::DetachCallback( + const nsACString& host, const nsACString& aTrrServer, uint16_t aType, + const OriginAttributes& aOriginAttributes, uint16_t flags, uint16_t af, + nsResolveHostCallback* aCallback, nsresult status) { + RefPtr<nsHostRecord> rec; + RefPtr<nsResolveHostCallback> callback(aCallback); + + { + MutexAutoLock lock(mLock); + + nsAutoCString originSuffix; + aOriginAttributes.CreateSuffix(originSuffix); + + nsHostKey key(host, aTrrServer, aType, flags, af, + (aOriginAttributes.mPrivateBrowsingId > 0), originSuffix); + RefPtr<nsHostRecord> entry = mRecordDB.Get(key); + if (entry) { + // walk list looking for |callback|... we cannot assume + // that it will be there! + + for (nsResolveHostCallback* c : entry->mCallbacks) { + if (c == callback) { + rec = entry; + c->remove(); + break; + } + } + } + } + + // 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->OnResolveHostComplete(this, rec, status); + } +} + +nsresult nsHostResolver::ConditionallyCreateThread(nsHostRecord* rec) { + if (mNumIdleTasks) { + // wake up idle tasks to process this lookup + mIdleTaskCV.Notify(); + } else if ((mActiveTaskCount < HighThreadThreshold) || + (IsHighPriority(rec->flags) && + mActiveTaskCount < MAX_RESOLVER_THREADS)) { + nsCOMPtr<nsIRunnable> event = mozilla::NewRunnableMethod( + "nsHostResolver::ThreadFunc", this, &nsHostResolver::ThreadFunc); + mActiveTaskCount++; + nsresult rv = + mResolverThreads->Dispatch(event, nsIEventTarget::DISPATCH_NORMAL); + if (NS_FAILED(rv)) { + mActiveTaskCount--; + } + } else { + LOG((" Unable to find a thread for looking up host [%s].\n", + rec->host.get())); + } + return NS_OK; +} + +nsresult nsHostResolver::TrrLookup_unlocked(nsHostRecord* rec, TRR* pushedTRR) { + MutexAutoLock lock(mLock); + return TrrLookup(rec, pushedTRR); +} + +void nsHostResolver::MaybeRenewHostRecord(nsHostRecord* aRec) { + MutexAutoLock lock(mLock); + MaybeRenewHostRecordLocked(aRec); +} + +void nsHostResolver::MaybeRenewHostRecordLocked(nsHostRecord* aRec) { + mLock.AssertCurrentThreadOwns(); + if (aRec->isInList()) { + // we're already on the eviction queue. This is a renewal + MOZ_ASSERT(mEvictionQSize); + AssertOnQ(aRec, mEvictionQ); + aRec->remove(); + mEvictionQSize--; + } +} + +// returns error if no TRR resolve is issued +// it is impt this is not called while a native lookup is going on +nsresult nsHostResolver::TrrLookup(nsHostRecord* aRec, TRR* pushedTRR) { + if (Mode() == MODE_TRROFF || StaticPrefs::network_dns_disabled()) { + return NS_ERROR_UNKNOWN_HOST; + } + LOG(("TrrLookup host:%s af:%" PRId16, aRec->host.get(), aRec->af)); + + RefPtr<nsHostRecord> rec(aRec); + mLock.AssertCurrentThreadOwns(); + + RefPtr<AddrHostRecord> addrRec; + RefPtr<TypeHostRecord> typeRec; + + if (rec->IsAddrRecord()) { + addrRec = do_QueryObject(rec); + MOZ_ASSERT(addrRec); + } else { + typeRec = do_QueryObject(rec); + MOZ_ASSERT(typeRec); + } + + MOZ_ASSERT(!rec->mResolving); + + auto hasConnectivity = [this]() -> bool { + if (!mNCS) { + return true; + } + nsINetworkConnectivityService::ConnectivityState ipv4 = mNCS->GetIPv4(); + nsINetworkConnectivityService::ConnectivityState ipv6 = mNCS->GetIPv6(); + + if (ipv4 == nsINetworkConnectivityService::OK || + ipv6 == nsINetworkConnectivityService::OK) { + return true; + } + + if (ipv4 == nsINetworkConnectivityService::UNKNOWN || + ipv6 == nsINetworkConnectivityService::UNKNOWN) { + // One of the checks hasn't completed yet. Optimistically assume we'll + // have network connectivity. + return true; + } + + return false; + }; + + nsIRequest::TRRMode reqMode = rec->mEffectiveTRRMode; + if (rec->mTrrServer.IsEmpty() && + (!gTRRService || !gTRRService->Enabled(reqMode))) { + if (NS_IsOffline()) { + // If we are in the NOT_CONFIRMED state _because_ we lack connectivity, + // then we should report that the browser is offline instead. + rec->RecordReason(nsHostRecord::TRR_IS_OFFLINE); + } + if (!hasConnectivity()) { + rec->RecordReason(nsHostRecord::TRR_NO_CONNECTIVITY); + } else { + rec->RecordReason(nsHostRecord::TRR_NOT_CONFIRMED); + } + + LOG(("TrrLookup:: %s service not enabled\n", rec->host.get())); + return NS_ERROR_UNKNOWN_HOST; + } + + MaybeRenewHostRecordLocked(rec); + + RefPtr<TRRQuery> query = new TRRQuery(this, rec); + nsresult rv = query->DispatchLookup(pushedTRR); + if (NS_FAILED(rv)) { + rec->RecordReason(nsHostRecord::TRR_DID_NOT_MAKE_QUERY); + return rv; + } + + { + auto lock = rec->mTRRQuery.Lock(); + MOZ_ASSERT(!lock.ref(), "TRR already in progress"); + lock.ref() = query; + } + + rec->mResolving++; + return NS_OK; +} + +void nsHostResolver::AssertOnQ(nsHostRecord* rec, + LinkedList<RefPtr<nsHostRecord>>& q) { +#ifdef DEBUG + MOZ_ASSERT(!q.isEmpty()); + MOZ_ASSERT(rec->isInList()); + for (const RefPtr<nsHostRecord>& r : q) { + if (rec == r) { + return; + } + } + MOZ_ASSERT(false, "Did not find element"); +#endif +} + +nsresult nsHostResolver::NativeLookup(nsHostRecord* aRec) { + if (StaticPrefs::network_dns_disabled()) { + return NS_ERROR_UNKNOWN_HOST; + } + LOG(("NativeLookup host:%s af:%" PRId16, aRec->host.get(), aRec->af)); + + // Only A/AAAA request are resolve natively. + MOZ_ASSERT(aRec->IsAddrRecord()); + mLock.AssertCurrentThreadOwns(); + + RefPtr<nsHostRecord> rec(aRec); + RefPtr<AddrHostRecord> addrRec; + addrRec = do_QueryObject(rec); + MOZ_ASSERT(addrRec); + + addrRec->mNativeStart = TimeStamp::Now(); + + // Add rec to one of the pending queues, possibly removing it from mEvictionQ. + MaybeRenewHostRecordLocked(rec); + + switch (AddrHostRecord::GetPriority(rec->flags)) { + case AddrHostRecord::DNS_PRIORITY_HIGH: + mHighQ.insertBack(rec); + break; + + case AddrHostRecord::DNS_PRIORITY_MEDIUM: + mMediumQ.insertBack(rec); + break; + + case AddrHostRecord::DNS_PRIORITY_LOW: + mLowQ.insertBack(rec); + break; + } + mPendingCount++; + + addrRec->StoreNative(true); + addrRec->StoreNativeUsed(true); + addrRec->mResolving++; + + nsresult rv = ConditionallyCreateThread(rec); + + LOG((" DNS thread counters: total=%d any-live=%d idle=%d pending=%d\n", + static_cast<uint32_t>(mActiveTaskCount), + static_cast<uint32_t>(mActiveAnyThreadCount), + static_cast<uint32_t>(mNumIdleTasks), + static_cast<uint32_t>(mPendingCount))); + + return rv; +} + +// static +ResolverMode nsHostResolver::Mode() { + if (gTRRService) { + return static_cast<ResolverMode>(gTRRService->Mode()); + } + + // If we don't have a TRR service just return MODE_TRROFF so we don't make + // any TRR requests by mistake. + return MODE_TRROFF; +} + +nsIRequest::TRRMode nsHostRecord::TRRMode() { + return nsIDNSService::GetTRRModeFromFlags(flags); +} + +// static +void nsHostResolver::ComputeEffectiveTRRMode(nsHostRecord* aRec) { + ResolverMode resolverMode = nsHostResolver::Mode(); + nsIRequest::TRRMode requestMode = aRec->TRRMode(); + + // For domains that are excluded from TRR or when parental control is enabled, + // we fallback to NativeLookup. This happens even in MODE_TRRONLY. By default + // localhost and local are excluded (so we cover *.local hosts) See the + // network.trr.excluded-domains pref. + + if (!gTRRService) { + aRec->RecordReason(nsHostRecord::TRR_NO_GSERVICE); + aRec->mEffectiveTRRMode = requestMode; + return; + } + + if (!aRec->mTrrServer.IsEmpty()) { + aRec->mEffectiveTRRMode = nsIRequest::TRR_ONLY_MODE; + return; + } + + if (gTRRService->IsExcludedFromTRR(aRec->host)) { + aRec->RecordReason(nsHostRecord::TRR_EXCLUDED); + aRec->mEffectiveTRRMode = nsIRequest::TRR_DISABLED_MODE; + return; + } + + if (StaticPrefs::network_dns_skipTRR_when_parental_control_enabled() && + gTRRService->ParentalControlEnabled()) { + aRec->RecordReason(nsHostRecord::TRR_PARENTAL_CONTROL); + aRec->mEffectiveTRRMode = nsIRequest::TRR_DISABLED_MODE; + return; + } + + if (resolverMode == MODE_TRROFF) { + aRec->RecordReason(nsHostRecord::TRR_OFF_EXPLICIT); + aRec->mEffectiveTRRMode = nsIRequest::TRR_DISABLED_MODE; + return; + } + + if (requestMode == nsIRequest::TRR_DISABLED_MODE) { + aRec->RecordReason(nsHostRecord::TRR_REQ_MODE_DISABLED); + aRec->mEffectiveTRRMode = nsIRequest::TRR_DISABLED_MODE; + return; + } + + if ((requestMode == nsIRequest::TRR_DEFAULT_MODE && + resolverMode == MODE_NATIVEONLY)) { + aRec->RecordReason(nsHostRecord::TRR_MODE_NOT_ENABLED); + aRec->mEffectiveTRRMode = nsIRequest::TRR_DISABLED_MODE; + return; + } + + if (requestMode == nsIRequest::TRR_DEFAULT_MODE && + resolverMode == MODE_TRRFIRST) { + aRec->mEffectiveTRRMode = nsIRequest::TRR_FIRST_MODE; + return; + } + + if (requestMode == nsIRequest::TRR_DEFAULT_MODE && + resolverMode == MODE_TRRONLY) { + aRec->mEffectiveTRRMode = nsIRequest::TRR_ONLY_MODE; + return; + } + + aRec->mEffectiveTRRMode = requestMode; +} + +// Kick-off a name resolve operation, using native resolver and/or TRR +nsresult nsHostResolver::NameLookup(nsHostRecord* rec) { + LOG(("NameLookup host:%s af:%" PRId16, rec->host.get(), rec->af)); + + if (rec->flags & RES_IP_HINT) { + LOG(("Skip lookup if RES_IP_HINT is set\n")); + return NS_ERROR_UNKNOWN_HOST; + } + + nsresult rv = NS_ERROR_UNKNOWN_HOST; + if (rec->mResolving) { + LOG(("NameLookup %s while already resolving\n", rec->host.get())); + return NS_OK; + } + + // Make sure we reset the reason each time we attempt to do a new lookup + // so we don't wronly report the reason for the previous one. + rec->mTRRTRRSkippedReason = nsHostRecord::TRR_UNSET; + + ComputeEffectiveTRRMode(rec); + + if (rec->IsAddrRecord()) { + RefPtr<AddrHostRecord> addrRec = do_QueryObject(rec); + MOZ_ASSERT(addrRec); + + addrRec->StoreNativeUsed(false); + addrRec->mTRRUsed = false; + addrRec->mNativeSuccess = false; + } + + if (!rec->mTrrServer.IsEmpty()) { + LOG(("NameLookup: %s use trr:%s", rec->host.get(), rec->mTrrServer.get())); + if (rec->mEffectiveTRRMode != nsIRequest::TRR_ONLY_MODE) { + return NS_ERROR_UNKNOWN_HOST; + } + + if (rec->flags & RES_DISABLE_TRR) { + LOG(("TRR with server and DISABLE_TRR flag. Returning error.")); + return NS_ERROR_UNKNOWN_HOST; + } + return TrrLookup(rec); + } + + LOG(("NameLookup: %s effectiveTRRmode: %d flags: %X", rec->host.get(), + rec->mEffectiveTRRMode, rec->flags)); + + if (rec->flags & RES_DISABLE_TRR) { + rec->RecordReason(nsHostRecord::TRR_DISABLED_FLAG); + } + + if (rec->mEffectiveTRRMode != nsIRequest::TRR_DISABLED_MODE && + !((rec->flags & RES_DISABLE_TRR))) { + rv = TrrLookup(rec); + } + + bool serviceNotReady = !gTRRService || !gTRRService->IsConfirmed(); + + if (rec->mEffectiveTRRMode == nsIRequest::TRR_DISABLED_MODE || + (rec->mEffectiveTRRMode == nsIRequest::TRR_FIRST_MODE && + (rec->flags & RES_DISABLE_TRR || serviceNotReady || NS_FAILED(rv)))) { + if (!rec->IsAddrRecord()) { + return rv; + } + +#ifdef DEBUG + // If we use this branch then the mTRRUsed flag should not be set + // Even if we did call TrrLookup above, the fact that it failed sync-ly + // means that we didn't actually succeed in opening the channel. + RefPtr<AddrHostRecord> addrRec = do_QueryObject(rec); + MOZ_ASSERT(addrRec && !addrRec->mTRRUsed); +#endif + + rv = NativeLookup(rec); + } + + return rv; +} + +nsresult nsHostResolver::ConditionallyRefreshRecord(nsHostRecord* rec, + const nsACString& host) { + if ((rec->CheckExpiration(TimeStamp::NowLoRes()) != nsHostRecord::EXP_VALID || + rec->negative) && + !rec->mResolving) { + LOG((" Using %s cache entry for host [%s] but starting async renewal.", + rec->negative ? "negative" : "positive", host.BeginReading())); + NameLookup(rec); + + if (rec->IsAddrRecord() && !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(LinkedList<RefPtr<nsHostRecord>>& aQ, + AddrHostRecord** aResult) { + RefPtr<nsHostRecord> rec = aQ.popFirst(); + mPendingCount--; + MOZ_ASSERT(rec->IsAddrRecord()); + RefPtr<AddrHostRecord> addrRec = do_QueryObject(rec); + MOZ_ASSERT(addrRec); + addrRec.forget(aResult); +} + +bool nsHostResolver::GetHostToLookup(AddrHostRecord** result) { + bool timedOut = false; + TimeDuration timeout; + TimeStamp epoch, now; + + MutexAutoLock lock(mLock); + + timeout = (mNumIdleTasks >= HighThreadThreshold) ? mShortIdleTimeout + : mLongIdleTimeout; + epoch = TimeStamp::Now(); + + while (!mShutdown) { + // remove next record from Q; hand over owning reference. Check high, then + // med, then low + +#define SET_GET_TTL(var, val) (var)->StoreGetTtl(sGetTtlEnabled && (val)) + + if (!mHighQ.isEmpty()) { + DeQueue(mHighQ, result); + SET_GET_TTL(*result, false); + return true; + } + + if (mActiveAnyThreadCount < HighThreadThreshold) { + if (!mMediumQ.isEmpty()) { + DeQueue(mMediumQ, result); + mActiveAnyThreadCount++; + (*result)->StoreUsingAnyThread(true); + SET_GET_TTL(*result, true); + return true; + } + + if (!mLowQ.isEmpty()) { + DeQueue(mLowQ, result); + mActiveAnyThreadCount++; + (*result)->StoreUsingAnyThread(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 + + mNumIdleTasks++; + mIdleTaskCV.Wait(timeout); + mNumIdleTasks--; + + now = TimeStamp::Now(); + + if (now - epoch >= timeout) { + timedOut = true; + } else { + // It is possible that CondVar::Wait() 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 -= now - epoch; + epoch = now; + } + } + + // tell thread to exit... + return false; +} + +void nsHostResolver::PrepareRecordExpirationAddrRecord( + AddrHostRecord* rec) const { + // NOTE: rec->addr_info_lock is already held by parent + MOZ_ASSERT(((bool)rec->addr_info) != rec->negative); + mLock.AssertCurrentThreadOwns(); + if (!rec->addr_info) { + rec->SetExpiration(TimeStamp::NowLoRes(), NEGATIVE_RECORD_LIFETIME, 0); + LOG(("Caching host [%s] negative record for %u seconds.\n", rec->host.get(), + NEGATIVE_RECORD_LIFETIME)); + return; + } + + unsigned int lifetime = mDefaultCacheLifetime; + unsigned int grace = mDefaultGracePeriod; + + unsigned int ttl = mDefaultCacheLifetime; + if (sGetTtlEnabled || rec->addr_info->IsTRR()) { + if (rec->addr_info && rec->addr_info->TTL() != AddrInfo::NO_TTL_DATA) { + ttl = rec->addr_info->TTL(); + } + lifetime = ttl; + grace = 0; + } + + rec->SetExpiration(TimeStamp::NowLoRes(), lifetime, grace); + LOG(("Caching host [%s] record for %u seconds (grace %d).", rec->host.get(), + lifetime, grace)); +} + +static bool different_rrset(AddrInfo* rrset1, AddrInfo* rrset2) { + if (!rrset1 || !rrset2) { + return true; + } + + LOG(("different_rrset %s\n", rrset1->Hostname().get())); + + if (rrset1->IsTRR() != rrset2->IsTRR()) { + return true; + } + + if (rrset1->Addresses().Length() != rrset2->Addresses().Length()) { + LOG(("different_rrset true due to length change\n")); + return true; + } + + nsTArray<NetAddr> orderedSet1 = rrset1->Addresses().Clone(); + nsTArray<NetAddr> orderedSet2 = rrset2->Addresses().Clone(); + orderedSet1.Sort(); + orderedSet2.Sort(); + + bool eq = orderedSet1 == orderedSet2; + if (!eq) { + LOG(("different_rrset true due to content change\n")); + } else { + LOG(("different_rrset false\n")); + } + return !eq; +} + +void nsHostResolver::AddToEvictionQ(nsHostRecord* rec) { + if (rec->isInList()) { + MOZ_DIAGNOSTIC_ASSERT(!mEvictionQ.contains(rec), + "Already in eviction queue"); + MOZ_DIAGNOSTIC_ASSERT(!mHighQ.contains(rec), "Already in high queue"); + MOZ_DIAGNOSTIC_ASSERT(!mMediumQ.contains(rec), "Already in med queue"); + MOZ_DIAGNOSTIC_ASSERT(!mLowQ.contains(rec), "Already in low queue"); + MOZ_DIAGNOSTIC_ASSERT(false, "Already on some other queue?"); + + // Bug 1678117 - it's not clear why this can happen, but let's fix it + // for release users. + rec->remove(); + } + mEvictionQ.insertBack(rec); + if (mEvictionQSize < mMaxCacheEntries) { + mEvictionQSize++; + } else { + // remove first element on mEvictionQ + RefPtr<nsHostRecord> head = mEvictionQ.popFirst(); + mRecordDB.Remove(*static_cast<nsHostKey*>(head.get())); + + if (!head->negative) { + // record the age of the entry upon eviction. + TimeDuration age = TimeStamp::NowLoRes() - head->mValidStart; + if (rec->IsAddrRecord()) { + Telemetry::Accumulate(Telemetry::DNS_CLEANUP_AGE, + static_cast<uint32_t>(age.ToSeconds() / 60)); + } else { + Telemetry::Accumulate(Telemetry::DNS_BY_TYPE_CLEANUP_AGE, + static_cast<uint32_t>(age.ToSeconds() / 60)); + } + if (head->CheckExpiration(TimeStamp::Now()) != + nsHostRecord::EXP_EXPIRED) { + if (rec->IsAddrRecord()) { + Telemetry::Accumulate(Telemetry::DNS_PREMATURE_EVICTION, + static_cast<uint32_t>(age.ToSeconds() / 60)); + } else { + Telemetry::Accumulate(Telemetry::DNS_BY_TYPE_PREMATURE_EVICTION, + static_cast<uint32_t>(age.ToSeconds() / 60)); + } + } + } + } +} + +// +// CompleteLookup() 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::CompleteLookup( + nsHostRecord* rec, nsresult status, AddrInfo* aNewRRSet, bool pb, + const nsACString& aOriginsuffix, nsHostRecord::TRRSkippedReason aReason) { + MutexAutoLock lock(mLock); + MOZ_ASSERT(rec); + MOZ_ASSERT(rec->pb == pb); + MOZ_ASSERT(rec->IsAddrRecord()); + + RefPtr<AddrHostRecord> addrRec = do_QueryObject(rec); + MOZ_ASSERT(addrRec); + + RefPtr<AddrInfo> newRRSet(aNewRRSet); + MOZ_ASSERT(NS_FAILED(status) || newRRSet->Addresses().Length() > 0); + + bool trrResult = newRRSet && newRRSet->IsTRR(); + if (NS_FAILED(status)) { + newRRSet = nullptr; + } + + if (addrRec->LoadResolveAgain() && (status != NS_ERROR_ABORT) && !trrResult) { + LOG(("nsHostResolver record %p resolve again due to flushcache\n", + addrRec.get())); + addrRec->StoreResolveAgain(false); + return LOOKUP_RESOLVEAGAIN; + } + + MOZ_ASSERT(addrRec->mResolving); + addrRec->mResolving--; + LOG(("nsHostResolver::CompleteLookup %s %p %X trr=%d stillResolving=%d\n", + addrRec->host.get(), aNewRRSet, (unsigned int)status, + aNewRRSet ? aNewRRSet->IsTRR() : 0, int(addrRec->mResolving))); + + if (trrResult) { + if (NS_FAILED(status) && status != NS_ERROR_UNKNOWN_HOST && + status != NS_ERROR_DEFINITIVE_UNKNOWN_HOST) { + // the errors are not failed resolves, that means + // something else failed, consider this as *TRR not used* + // for actually trying to resolve the host + addrRec->mTRRUsed = false; + } else { + addrRec->mTRRUsed = true; + } + + if (NS_FAILED(status)) { + if (aReason != nsHostRecord::TRR_UNSET) { + addrRec->RecordReason(aReason); + } else { + // Unknown failed reason. + addrRec->RecordReason(nsHostRecord::TRR_FAILED); + } + } else { + addrRec->mTRRSuccess++; + addrRec->RecordReason(nsHostRecord::TRR_OK); + } + + if (NS_FAILED(status) && + addrRec->mEffectiveTRRMode == nsIRequest::TRR_FIRST_MODE && + status != NS_ERROR_DEFINITIVE_UNKNOWN_HOST) { + MOZ_ASSERT(!addrRec->mResolving); + NativeLookup(addrRec); + MOZ_ASSERT(addrRec->mResolving); + return LOOKUP_OK; + } + + if (!addrRec->mTRRSuccess) { + // no TRR success + newRRSet = nullptr; + } + + if (NS_FAILED(status)) { + // This is the error that consumers expect. + status = NS_ERROR_UNKNOWN_HOST; + } + } else { // native resolve completed + if (addrRec->LoadUsingAnyThread()) { + mActiveAnyThreadCount--; + addrRec->StoreUsingAnyThread(false); + } + + addrRec->mNativeSuccess = static_cast<bool>(newRRSet); + if (addrRec->mNativeSuccess) { + addrRec->mNativeDuration = TimeStamp::Now() - addrRec->mNativeStart; + } + } + + // This should always be cleared when a request is completed. + addrRec->StoreNative(false); + + // update record fields. We might have a addrRec->addr_info already if a + // previous lookup result expired and we're reresolving it or we get + // a late second TRR response. + if (!mShutdown) { + MutexAutoLock lock(addrRec->addr_info_lock); + RefPtr<AddrInfo> old_addr_info; + if (different_rrset(addrRec->addr_info, newRRSet)) { + LOG(("nsHostResolver record %p new gencnt\n", addrRec.get())); + old_addr_info = addrRec->addr_info; + addrRec->addr_info = std::move(newRRSet); + addrRec->addr_info_gencnt++; + } else { + if (addrRec->addr_info && newRRSet) { + auto builder = addrRec->addr_info->Build(); + builder.SetTTL(newRRSet->TTL()); + // Update trr timings + builder.SetTrrFetchDuration(newRRSet->GetTrrFetchDuration()); + builder.SetTrrFetchDurationNetworkOnly( + newRRSet->GetTrrFetchDurationNetworkOnly()); + + addrRec->addr_info = builder.Finish(); + } + old_addr_info = std::move(newRRSet); + } + addrRec->negative = !addrRec->addr_info; + PrepareRecordExpirationAddrRecord(addrRec); + } + + if (LOG_ENABLED()) { + MutexAutoLock lock(addrRec->addr_info_lock); + if (addrRec->addr_info) { + for (const auto& elem : addrRec->addr_info->Addresses()) { + char buf[128]; + elem.ToStringBuffer(buf, sizeof(buf)); + LOG(("CompleteLookup: %s has %s\n", addrRec->host.get(), buf)); + } + } else { + LOG(("CompleteLookup: %s has NO address\n", addrRec->host.get())); + } + } + + // get the list of pending callbacks for this lookup, and notify + // them that the lookup is complete. + mozilla::LinkedList<RefPtr<nsResolveHostCallback>> cbs = + std::move(rec->mCallbacks); + + LOG(("nsHostResolver record %p calling back dns users status:%X\n", + addrRec.get(), int(status))); + + for (nsResolveHostCallback* c = cbs.getFirst(); c; + c = c->removeAndGetNext()) { + c->OnResolveHostComplete(this, rec, status); + } + + if (!addrRec->mResolving && !mShutdown) { + { + auto trrQuery = addrRec->mTRRQuery.Lock(); + if (trrQuery.ref()) { + addrRec->mTrrDuration = trrQuery.ref()->Duration(); + } + trrQuery.ref() = nullptr; + } + addrRec->ResolveComplete(); + + AddToEvictionQ(rec); + } + +#ifdef DNSQUERY_AVAILABLE + // Unless the result is from TRR, resolve again to get TTL + bool hasNativeResult = false; + { + MutexAutoLock lock(addrRec->addr_info_lock); + if (addrRec->addr_info && !addrRec->addr_info->IsTRR()) { + hasNativeResult = true; + } + } + if (hasNativeResult && !mShutdown && !addrRec->LoadGetTtl() && + !rec->mResolving && sGetTtlEnabled) { + LOG(("Issuing second async lookup for TTL for host [%s].", + addrRec->host.get())); + addrRec->flags = (addrRec->flags & ~RES_PRIORITY_MEDIUM) | RES_PRIORITY_LOW; + DebugOnly<nsresult> rv = NativeLookup(rec); + NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), + "Could not issue second async lookup for TTL."); + } +#endif + return LOOKUP_OK; +} + +nsHostResolver::LookupStatus nsHostResolver::CompleteLookupByType( + nsHostRecord* rec, nsresult status, + mozilla::net::TypeRecordResultType& aResult, uint32_t aTtl, bool pb) { + MutexAutoLock lock(mLock); + MOZ_ASSERT(rec); + MOZ_ASSERT(rec->pb == pb); + MOZ_ASSERT(!rec->IsAddrRecord()); + + RefPtr<TypeHostRecord> typeRec = do_QueryObject(rec); + MOZ_ASSERT(typeRec); + + MOZ_ASSERT(typeRec->mResolving); + typeRec->mResolving--; + + { + auto lock = rec->mTRRQuery.Lock(); + lock.ref() = nullptr; + } + + uint32_t duration = static_cast<uint32_t>( + (TimeStamp::Now() - typeRec->mStart).ToMilliseconds()); + + if (NS_FAILED(status)) { + LOG(("nsHostResolver::CompleteLookupByType record %p [%s] status %x\n", + typeRec.get(), typeRec->host.get(), (unsigned int)status)); + typeRec->SetExpiration(TimeStamp::NowLoRes(), NEGATIVE_RECORD_LIFETIME, 0); + MOZ_ASSERT(aResult.is<TypeRecordEmpty>()); + status = NS_ERROR_UNKNOWN_HOST; + typeRec->negative = true; + Telemetry::Accumulate(Telemetry::DNS_BY_TYPE_FAILED_LOOKUP_TIME, duration); + } else { + size_t recordCount = 0; + if (aResult.is<TypeRecordTxt>()) { + recordCount = aResult.as<TypeRecordTxt>().Length(); + } else if (aResult.is<TypeRecordHTTPSSVC>()) { + recordCount = aResult.as<TypeRecordHTTPSSVC>().Length(); + } + LOG( + ("nsHostResolver::CompleteLookupByType record %p [%s], number of " + "records %zu\n", + typeRec.get(), typeRec->host.get(), recordCount)); + MutexAutoLock typeLock(typeRec->mResultsLock); + typeRec->mResults = aResult; + typeRec->SetExpiration(TimeStamp::NowLoRes(), aTtl, mDefaultGracePeriod); + typeRec->negative = false; + Telemetry::Accumulate(Telemetry::DNS_BY_TYPE_SUCCEEDED_LOOKUP_TIME, + duration); + } + + mozilla::LinkedList<RefPtr<nsResolveHostCallback>> cbs = + std::move(typeRec->mCallbacks); + + LOG( + ("nsHostResolver::CompleteLookupByType record %p calling back dns " + "users\n", + typeRec.get())); + + for (nsResolveHostCallback* c = cbs.getFirst(); c; + c = c->removeAndGetNext()) { + c->OnResolveHostComplete(this, rec, status); + } + + AddToEvictionQ(rec); + return LOOKUP_OK; +} + +void nsHostResolver::CancelAsyncRequest( + const nsACString& host, const nsACString& aTrrServer, uint16_t aType, + const OriginAttributes& aOriginAttributes, uint16_t flags, uint16_t af, + nsIDNSListener* aListener, nsresult status) + +{ + MutexAutoLock lock(mLock); + + nsAutoCString originSuffix; + aOriginAttributes.CreateSuffix(originSuffix); + + // Lookup the host record associated with host, flags & address family + + nsHostKey key(host, aTrrServer, aType, flags, af, + (aOriginAttributes.mPrivateBrowsingId > 0), originSuffix); + RefPtr<nsHostRecord> rec = mRecordDB.Get(key); + if (rec) { + nsHostRecord* recPtr = nullptr; + + for (const RefPtr<nsResolveHostCallback>& c : rec->mCallbacks) { + if (c->EqualsAsyncListener(aListener)) { + c->remove(); + recPtr = rec; + c->OnResolveHostComplete(this, recPtr, status); + break; + } + } + + // If there are no more callbacks, remove the hash table entry + if (recPtr && recPtr->mCallbacks.isEmpty()) { + mRecordDB.Remove(*static_cast<nsHostKey*>(recPtr)); + // If record is on a Queue, remove it and then deref it + if (recPtr->isInList()) { + recPtr->remove(); + } + } + } +} + +size_t nsHostResolver::SizeOfIncludingThis(MallocSizeOf mallocSizeOf) const { + MutexAutoLock lock(mLock); + + size_t n = mallocSizeOf(this); + + n += mRecordDB.ShallowSizeOfExcludingThis(mallocSizeOf); + for (auto iter = mRecordDB.ConstIter(); !iter.Done(); iter.Next()) { + auto* entry = iter.UserData(); + n += entry->SizeOfIncludingThis(mallocSizeOf); + } + + // The following fields aren't measured. + // - mHighQ, mMediumQ, mLowQ, mEvictionQ, because they just point to + // nsHostRecords that also pointed to by entries |mRecordDB|, and + // measured when |mRecordDB| is measured. + + return n; +} + +void nsHostResolver::ThreadFunc() { + LOG(("DNS lookup thread - starting execution.\n")); + +#if defined(RES_RETRY_ON_FAILURE) + nsResState rs; +#endif + RefPtr<AddrHostRecord> rec; + RefPtr<AddrInfo> ai; + + do { + if (!rec) { + RefPtr<AddrHostRecord> tmpRec; + if (!GetHostToLookup(getter_AddRefs(tmpRec))) { + break; // thread shutdown signal + } + // GetHostToLookup() returns an owning reference + MOZ_ASSERT(tmpRec); + rec.swap(tmpRec); + } + + LOG1(("DNS lookup thread - Calling getaddrinfo for host [%s].\n", + rec->host.get())); + + TimeStamp startTime = TimeStamp::Now(); + bool getTtl = rec->LoadGetTtl(); + TimeDuration inQueue = startTime - rec->mNativeStart; + uint32_t ms = static_cast<uint32_t>(inQueue.ToMilliseconds()); + Telemetry::Accumulate(Telemetry::DNS_NATIVE_QUEUING, ms); + nsresult status = + GetAddrInfo(rec->host, rec->af, rec->flags, getter_AddRefs(ai), getTtl); +#if defined(RES_RETRY_ON_FAILURE) + if (NS_FAILED(status) && rs.Reset()) { + status = GetAddrInfo(rec->host, rec->af, rec->flags, getter_AddRefs(ai), + getTtl); + } +#endif + + { // obtain lock to check shutdown and manage inter-module telemetry + MutexAutoLock lock(mLock); + + if (!mShutdown) { + TimeDuration elapsed = TimeStamp::Now() - startTime; + uint32_t millis = static_cast<uint32_t>(elapsed.ToMilliseconds()); + + if (NS_SUCCEEDED(status)) { + Telemetry::HistogramID histogramID; + if (!rec->addr_info_gencnt) { + // Time for initial lookup. + histogramID = Telemetry::DNS_LOOKUP_TIME; + } else if (!getTtl) { + // Time for renewal; categorized by expiration strategy. + histogramID = Telemetry::DNS_RENEWAL_TIME; + } else { + // Time to get TTL; categorized by expiration strategy. + histogramID = Telemetry::DNS_RENEWAL_TIME_FOR_TTL; + } + Telemetry::Accumulate(histogramID, millis); + } else { + Telemetry::Accumulate(Telemetry::DNS_FAILED_LOOKUP_TIME, millis); + } + } + } + + LOG1(("DNS lookup thread - lookup completed for host [%s]: %s.\n", + rec->host.get(), ai ? "success" : "failure: unknown host")); + + if (LOOKUP_RESOLVEAGAIN == CompleteLookup(rec, status, ai, rec->pb, + rec->originSuffix, + rec->mTRRTRRSkippedReason)) { + // leave 'rec' assigned and loop to make a renewed host resolve + LOG(("DNS lookup thread - Re-resolving host [%s].\n", rec->host.get())); + } else { + rec = nullptr; + } + } while (true); + + mActiveTaskCount--; + LOG(("DNS lookup thread - queue empty, task finished.\n")); +} + +void nsHostResolver::SetCacheLimits(uint32_t aMaxCacheEntries, + uint32_t aDefaultCacheEntryLifetime, + uint32_t aDefaultGracePeriod) { + MutexAutoLock lock(mLock); + mMaxCacheEntries = aMaxCacheEntries; + mDefaultCacheLifetime = aDefaultCacheEntryLifetime; + mDefaultGracePeriod = aDefaultGracePeriod; +} + +nsresult nsHostResolver::Create(uint32_t maxCacheEntries, + uint32_t defaultCacheEntryLifetime, + uint32_t defaultGracePeriod, + nsHostResolver** result) { + RefPtr<nsHostResolver> res = new nsHostResolver( + maxCacheEntries, defaultCacheEntryLifetime, defaultGracePeriod); + + nsresult rv = res->Init(); + if (NS_FAILED(rv)) { + return rv; + } + + res.forget(result); + return NS_OK; +} + +void nsHostResolver::GetDNSCacheEntries(nsTArray<DNSCacheEntries>* args) { + MutexAutoLock lock(mLock); + for (auto iter = mRecordDB.Iter(); !iter.Done(); iter.Next()) { + // We don't pay attention to address literals, only resolved domains. + // Also require a host. + nsHostRecord* rec = iter.UserData(); + MOZ_ASSERT(rec, "rec should never be null here!"); + + if (!rec) { + continue; + } + + // For now we only show A/AAAA records. + if (!rec->IsAddrRecord()) { + continue; + } + + RefPtr<AddrHostRecord> addrRec = do_QueryObject(rec); + MOZ_ASSERT(addrRec); + if (!addrRec || !addrRec->addr_info) { + continue; + } + + 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 + continue; + } + + { + MutexAutoLock lock(addrRec->addr_info_lock); + for (const auto& addr : addrRec->addr_info->Addresses()) { + char buf[kIPv6CStrBufSize]; + if (addr.ToStringBuffer(buf, sizeof(buf))) { + info.hostaddr.AppendElement(buf); + } + } + info.TRR = addrRec->addr_info->IsTRR(); + } + + info.originAttributesSuffix = iter.Key().originSuffix; + + args->AppendElement(std::move(info)); + } +} + +#undef LOG +#undef LOG_ENABLED |