/* 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/. */ #ifndef nsHostResolver_h__ #define nsHostResolver_h__ #include "nscore.h" #include "prnetdb.h" #include "PLDHashTable.h" #include "mozilla/CondVar.h" #include "mozilla/DataMutex.h" #include "mozilla/Mutex.h" #include "nsISupportsImpl.h" #include "nsIDNSListener.h" #include "nsIDNSService.h" #include "nsTArray.h" #include "GetAddrInfo.h" #include "mozilla/net/DNS.h" #include "mozilla/net/DashboardTypes.h" #include "mozilla/AtomicBitfields.h" #include "mozilla/Atomics.h" #include "mozilla/LinkedList.h" #include "mozilla/TimeStamp.h" #include "mozilla/UniquePtr.h" #include "nsRefPtrHashtable.h" #include "nsIThreadPool.h" #include "mozilla/net/NetworkConnectivityService.h" #include "nsIDNSByTypeRecord.h" #include "mozilla/net/DNSByTypeRecord.h" #include "mozilla/Maybe.h" class nsHostResolver; class nsResolveHostCallback; namespace mozilla { namespace net { class TRR; class TRRQuery; enum ResolverMode { MODE_NATIVEONLY, // 0 - TRR OFF (by default) MODE_RESERVED1, // 1 - Reserved value. Used to be parallel resolve. MODE_TRRFIRST, // 2 - fallback to native on TRR failure MODE_TRRONLY, // 3 - don't even fallback MODE_RESERVED4, // 4 - Reserved value. Used to be race TRR with native. MODE_TRROFF // 5 - identical to MODE_NATIVEONLY but explicitly selected }; } // namespace net } // namespace mozilla #define TRR_DISABLED(x) (((x) == MODE_NATIVEONLY) || ((x) == MODE_TRROFF)) extern mozilla::Atomic gNativeIsLocalhost; #define MAX_RESOLVER_THREADS_FOR_ANY_PRIORITY 3 #define MAX_RESOLVER_THREADS_FOR_HIGH_PRIORITY 5 #define MAX_NON_PRIORITY_REQUESTS 150 #define MAX_RESOLVER_THREADS \ (MAX_RESOLVER_THREADS_FOR_ANY_PRIORITY + \ MAX_RESOLVER_THREADS_FOR_HIGH_PRIORITY) struct nsHostKey { const nsCString host; const nsCString mTrrServer; uint16_t type; uint16_t flags; uint16_t af; bool pb; const nsCString originSuffix; explicit nsHostKey(const nsACString& host, const nsACString& aTrrServer, uint16_t type, uint16_t flags, uint16_t af, bool pb, const nsACString& originSuffix); bool operator==(const nsHostKey& other) const; size_t SizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const; PLDHashNumber Hash() const; }; /** * nsHostRecord - ref counted object type stored in host resolver cache. */ class nsHostRecord : public mozilla::LinkedListElement>, public nsHostKey, public nsISupports { public: NS_DECL_THREADSAFE_ISUPPORTS virtual size_t SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const { return 0; } // Returns the TRR mode encoded by the flags nsIRequest::TRRMode TRRMode(); // IMPORTANT: when adding new values, always add them to the end, otherwise // it will mess up telemetry. enum TRRSkippedReason : uint32_t { TRR_UNSET = 0, TRR_OK = 1, // Only set when we actually got a positive TRR result TRR_NO_GSERVICE = 2, // no gService TRR_PARENTAL_CONTROL = 3, // parental control is on TRR_OFF_EXPLICIT = 4, // user has set mode5 TRR_REQ_MODE_DISABLED = 5, // request has disabled flags set TRR_MODE_NOT_ENABLED = 6, // mode0 TRR_FAILED = 7, // unknown failure TRR_MODE_UNHANDLED_DEFAULT = 8, // Unhandled case in ComputeEffectiveMode TRR_MODE_UNHANDLED_DISABLED = 9, // Unhandled case in ComputeEffectiveMode TRR_DISABLED_FLAG = 10, // the DISABLE_TRR flag was set TRR_TIMEOUT = 11, // the TRR channel timed out TRR_CHANNEL_DNS_FAIL = 12, // DoH server name failed to resolve TRR_IS_OFFLINE = 13, // The browser is offline/no interfaces up TRR_NOT_CONFIRMED = 14, // TRR confirmation is not done yet TRR_DID_NOT_MAKE_QUERY = 15, // TrrLookup exited without doing a TRR query TRR_UNKNOWN_CHANNEL_FAILURE = 16, // unknown channel failure reason TRR_HOST_BLOCKED_TEMPORARY = 17, // host blocklisted TRR_SEND_FAILED = 18, // The call to TRR::SendHTTPRequest failed TRR_NET_RESET = 19, // NS_ERROR_NET_RESET TRR_NET_TIMEOUT = 20, // NS_ERROR_NET_TIMEOUT TRR_NET_REFUSED = 21, // NS_ERROR_CONNECTION_REFUSED TRR_NET_INTERRUPT = 22, // NS_ERROR_NET_INTERRUPT TRR_NET_INADEQ_SEQURITY = 23, // NS_ERROR_NET_INADEQUATE_SECURITY TRR_NO_ANSWERS = 24, // TRR returned no answers TRR_DECODE_FAILED = 25, // DohDecode failed TRR_EXCLUDED = 26, // ExcludedFromTRR TRR_SERVER_RESPONSE_ERR = 27, // Server responded with non-200 code TRR_RCODE_FAIL = 28, // DNS response contains a non-NOERROR rcode TRR_NO_CONNECTIVITY = 29, // Not confirmed because of no connectivity }; // Records the first reason that caused TRR to be skipped or to fail. void RecordReason(TRRSkippedReason reason) { if (mTRRTRRSkippedReason == TRR_UNSET) { mTRRTRRSkippedReason = reason; } } protected: friend class nsHostResolver; friend class mozilla::net::TRR; friend class mozilla::net::TRRQuery; explicit nsHostRecord(const nsHostKey& key); virtual ~nsHostRecord() = default; // Mark hostrecord as not usable void Invalidate(); enum ExpirationStatus { EXP_VALID, EXP_GRACE, EXP_EXPIRED, }; ExpirationStatus CheckExpiration(const mozilla::TimeStamp& now) const; // Convenience function for setting the timestamps above (mValidStart, // mValidEnd, and mGraceStart). valid and grace are durations in seconds. void SetExpiration(const mozilla::TimeStamp& now, unsigned int valid, unsigned int grace); void CopyExpirationTimesAndFlagsFrom(const nsHostRecord* aFromHostRecord); // Checks if the record is usable (not expired and has a value) bool HasUsableResult(const mozilla::TimeStamp& now, uint16_t queryFlags = 0) const; enum DnsPriority { DNS_PRIORITY_LOW, DNS_PRIORITY_MEDIUM, DNS_PRIORITY_HIGH, }; static DnsPriority GetPriority(uint16_t aFlags); virtual void Cancel(); virtual bool HasUsableResultInternal() const { return false; } mozilla::LinkedList> mCallbacks; bool IsAddrRecord() const { return type == nsIDNSService::RESOLVE_TYPE_DEFAULT; } // When the record began being valid. Used mainly for bookkeeping. mozilla::TimeStamp mValidStart; // When the record is no longer valid (it's time of expiration) mozilla::TimeStamp mValidEnd; // When the record enters its grace period. This must be before mValidEnd. // If a record is in its grace period (and not expired), it will be used // but a request to refresh it will be made. mozilla::TimeStamp mGraceStart; // The computed TRR mode that is actually used by the request. // It is set in nsHostResolver::NameLookup and is based on the mode of the // default resolver and the TRRMode encoded in the flags. // The mode into account if the TRR service is disabled, // parental controls are on, domain matches exclusion list, etc. nsIRequest::TRRMode mEffectiveTRRMode; TRRSkippedReason mTRRTRRSkippedReason = TRR_UNSET; TRRSkippedReason mTRRAFailReason = TRR_UNSET; TRRSkippedReason mTRRAAAAFailReason = TRR_UNSET; mozilla::DataMutex> mTRRQuery; mozilla::Atomic mResolving; // counter of outstanding resolving calls uint8_t negative : 1; /* True if this record is a cache of a failed lookup. Negative cache entries are valid just like any other (though never for more than 60 seconds), but a use of that negative entry forces an asynchronous refresh. */ uint8_t mDoomed : 1; // explicitly expired }; // b020e996-f6ab-45e5-9bf5-1da71dd0053a #define ADDRHOSTRECORD_IID \ { \ 0xb020e996, 0xf6ab, 0x45e5, { \ 0x9b, 0xf5, 0x1d, 0xa7, 0x1d, 0xd0, 0x05, 0x3a \ } \ } class AddrHostRecord final : public nsHostRecord { typedef mozilla::Mutex Mutex; public: NS_DECLARE_STATIC_IID_ACCESSOR(ADDRHOSTRECORD_IID) NS_DECL_ISUPPORTS_INHERITED /* a fully resolved host record has either a non-null |addr_info| or |addr| * field. if |addr_info| is null, it implies that the |host| is an IP * address literal. in which case, |addr| contains the parsed address. * otherwise, if |addr_info| is non-null, then it contains one or many * IP addresses corresponding to the given host name. if both |addr_info| * and |addr| are null, then the given host has not yet been fully resolved. * |af| is the address family of the record we are querying for. */ /* the lock protects |addr_info| and |addr_info_gencnt| because they * are mutable and accessed by the resolver worker thread and the * nsDNSService2 class. |addr| doesn't change after it has been * assigned a value. only the resolver worker thread modifies * nsHostRecord (and only in nsHostResolver::CompleteLookup); * the other threads just read it. therefore the resolver worker * thread doesn't need to lock when reading |addr_info|. */ Mutex addr_info_lock; int addr_info_gencnt; /* generation count of |addr_info| */ RefPtr addr_info; mozilla::UniquePtr addr; // hold addr_info_lock when calling the blocklist functions bool Blocklisted(const mozilla::net::NetAddr* query); void ResetBlocklist(); void ReportUnusable(const mozilla::net::NetAddr* aAddress); size_t SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const override; nsIRequest::TRRMode EffectiveTRRMode() const { return mEffectiveTRRMode; } private: friend class nsHostResolver; friend class mozilla::net::TRR; friend class mozilla::net::TRRQuery; explicit AddrHostRecord(const nsHostKey& key); ~AddrHostRecord(); // Checks if the record is usable (not expired and has a value) bool HasUsableResultInternal() const override; bool RemoveOrRefresh(bool aTrrToo); // Mark records currently being resolved // as needed to resolve again. void ResolveComplete(); enum DnsPriority { DNS_PRIORITY_LOW, DNS_PRIORITY_MEDIUM, DNS_PRIORITY_HIGH, }; static DnsPriority GetPriority(uint16_t aFlags); // true if pending and on the queue (not yet given to getaddrinfo()) bool onQueue() { return LoadNative() && isInList(); } // When the lookups of this record started and their durations mozilla::TimeStamp mTrrStart; mozilla::TimeStamp mNativeStart; mozilla::TimeDuration mTrrDuration; mozilla::TimeDuration mNativeDuration; mozilla::Atomic mTRRUsed; // TRR was used on this record uint8_t mTRRSuccess; // number of successful TRR responses uint8_t mNativeSuccess; // number of native lookup responses // clang-format off MOZ_ATOMIC_BITFIELDS(mAtomicBitfields, 8, ( // true if this record is being resolved "natively", which means that // it is either on the pending queue or owned by one of the worker threads. (uint16_t, Native, 1), (uint16_t, NativeUsed, 1), // true if off queue and contributing to mActiveAnyThreadCount (uint16_t, UsingAnyThread, 1), (uint16_t, GetTtl, 1), (uint16_t, ResolveAgain, 1) )) // clang-format on // The number of times ReportUnusable() has been called in the record's // lifetime. uint32_t mUnusableCount = 0; // a list of addresses associated with this record that have been reported // as unusable. the list is kept as a set of strings to make it independent // of gencnt. nsTArray mUnusableItems; }; NS_DEFINE_STATIC_IID_ACCESSOR(AddrHostRecord, ADDRHOSTRECORD_IID) // 77b786a7-04be-44f2-987c-ab8aa96676e0 #define TYPEHOSTRECORD_IID \ { \ 0x77b786a7, 0x04be, 0x44f2, { \ 0x98, 0x7c, 0xab, 0x8a, 0xa9, 0x66, 0x76, 0xe0 \ } \ } class TypeHostRecord final : public nsHostRecord, public nsIDNSTXTRecord, public nsIDNSHTTPSSVCRecord, public mozilla::net::DNSHTTPSSVCRecordBase { public: NS_DECLARE_STATIC_IID_ACCESSOR(TYPEHOSTRECORD_IID) NS_DECL_ISUPPORTS_INHERITED NS_DECL_NSIDNSTXTRECORD NS_DECL_NSIDNSHTTPSSVCRECORD size_t SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const override; uint32_t GetType(); mozilla::net::TypeRecordResultType GetResults(); private: friend class nsHostResolver; friend class mozilla::net::TRRQuery; explicit TypeHostRecord(const nsHostKey& key); ~TypeHostRecord(); // Checks if the record is usable (not expired and has a value) bool HasUsableResultInternal() const override; bool HasUsableResult(); mozilla::net::TypeRecordResultType mResults = AsVariant(mozilla::Nothing()); mozilla::Mutex mResultsLock; // When the lookups of this record started (for telemetry). mozilla::TimeStamp mStart; bool mAllRecordsExcluded = false; }; NS_DEFINE_STATIC_IID_ACCESSOR(TypeHostRecord, TYPEHOSTRECORD_IID) /** * This class is used to notify listeners when a ResolveHost operation is * complete. Classes that derive it must implement threadsafe nsISupports * to be able to use RefPtr with this class. */ class nsResolveHostCallback : public mozilla::LinkedListElement>, public nsISupports { public: /** * OnResolveHostComplete * * this function is called to complete a host lookup initiated by * nsHostResolver::ResolveHost. it may be invoked recursively from * ResolveHost or on an unspecified background thread. * * NOTE: it is the responsibility of the implementor of this method * to handle the callback in a thread safe manner. * * @param resolver * nsHostResolver object associated with this result * @param record * the host record containing the results of the lookup * @param status * if successful, |record| contains non-null results */ virtual void OnResolveHostComplete(nsHostResolver* resolver, nsHostRecord* record, nsresult status) = 0; /** * EqualsAsyncListener * * Determines if the listener argument matches the listener member var. * For subclasses not implementing a member listener, should return false. * For subclasses having a member listener, the function should check if * they are the same. Used for cases where a pointer to an object * implementing nsResolveHostCallback is unknown, but a pointer to * the original listener is known. * * @param aListener * nsIDNSListener object associated with the original request */ virtual bool EqualsAsyncListener(nsIDNSListener* aListener) = 0; virtual size_t SizeOfIncludingThis(mozilla::MallocSizeOf) const = 0; protected: virtual ~nsResolveHostCallback() = default; }; class AHostResolver { public: AHostResolver() = default; virtual ~AHostResolver() = default; NS_INLINE_DECL_PURE_VIRTUAL_REFCOUNTING enum LookupStatus { LOOKUP_OK, LOOKUP_RESOLVEAGAIN, }; virtual LookupStatus CompleteLookup( nsHostRecord*, nsresult, mozilla::net::AddrInfo*, bool pb, const nsACString& aOriginsuffix, nsHostRecord::TRRSkippedReason aReason) = 0; virtual LookupStatus CompleteLookupByType( nsHostRecord*, nsresult, mozilla::net::TypeRecordResultType& aResult, uint32_t aTtl, bool pb) = 0; virtual nsresult GetHostRecord(const nsACString& host, const nsACString& aTrrServer, uint16_t type, uint16_t flags, uint16_t af, bool pb, const nsCString& originSuffix, nsHostRecord** result) { return NS_ERROR_FAILURE; } virtual nsresult TrrLookup_unlocked(nsHostRecord*, mozilla::net::TRR* pushedTRR = nullptr) { return NS_ERROR_FAILURE; } virtual void MaybeRenewHostRecord(nsHostRecord* aRec) {} }; /** * nsHostResolver - an asynchronous host name resolver. */ class nsHostResolver : public nsISupports, public AHostResolver { using CondVar = mozilla::CondVar; using Mutex = mozilla::Mutex; public: NS_DECL_THREADSAFE_ISUPPORTS /** * creates an addref'd instance of a nsHostResolver object. */ static nsresult Create(uint32_t maxCacheEntries, // zero disables cache uint32_t defaultCacheEntryLifetime, // seconds uint32_t defaultGracePeriod, // seconds nsHostResolver** result); /** * Set (new) cache limits. */ void SetCacheLimits(uint32_t maxCacheEntries, // zero disables cache uint32_t defaultCacheEntryLifetime, // seconds uint32_t defaultGracePeriod); // seconds /** * puts the resolver in the shutdown state, which will cause any pending * callbacks to be detached. any future calls to ResolveHost will fail. */ void Shutdown(); /** * resolve the given hostname and originAttributes asynchronously. the caller * can synthesize a synchronous host lookup using a lock and a cvar. as noted * above the callback will occur re-entrantly from an unspecified thread. the * host lookup cannot be canceled (cancelation can be layered above this by * having the callback implementation return without doing anything). */ nsresult ResolveHost(const nsACString& aHost, const nsACString& trrServer, uint16_t type, const mozilla::OriginAttributes& aOriginAttributes, uint16_t flags, uint16_t af, nsResolveHostCallback* callback); nsHostRecord* InitRecord(const nsHostKey& key); mozilla::net::NetworkConnectivityService* GetNCS() { return mNCS; } /** * return a resolved hard coded loopback dns record for the specified key */ already_AddRefed InitLoopbackRecord(const nsHostKey& key, nsresult* aRv); /** * removes the specified callback from the nsHostRecord for the given * hostname, originAttributes, flags, and address family. these parameters * should correspond to the parameters passed to ResolveHost. this function * executes the callback if the callback is still pending with the given * status. */ void DetachCallback(const nsACString& hostname, const nsACString& trrServer, uint16_t type, const mozilla::OriginAttributes& aOriginAttributes, uint16_t flags, uint16_t af, nsResolveHostCallback* callback, nsresult status); /** * Cancels an async request associated with the hostname, originAttributes, * flags, address family and listener. Cancels first callback found which * matches these criteria. These parameters should correspond to the * parameters passed to ResolveHost. If this is the last callback associated * with the host record, it is removed from any request queues it might be on. */ void CancelAsyncRequest(const nsACString& host, const nsACString& trrServer, uint16_t type, const mozilla::OriginAttributes& aOriginAttributes, uint16_t flags, uint16_t af, nsIDNSListener* aListener, nsresult status); /** * values for the flags parameter passed to ResolveHost and DetachCallback * that may be bitwise OR'd together. * * NOTE: in this implementation, these flags correspond exactly in value * to the flags defined on nsIDNSService. */ enum { RES_BYPASS_CACHE = nsIDNSService::RESOLVE_BYPASS_CACHE, RES_CANON_NAME = nsIDNSService::RESOLVE_CANONICAL_NAME, RES_PRIORITY_MEDIUM = nsIDNSService::RESOLVE_PRIORITY_MEDIUM, RES_PRIORITY_LOW = nsIDNSService::RESOLVE_PRIORITY_LOW, RES_SPECULATE = nsIDNSService::RESOLVE_SPECULATE, // RES_DISABLE_IPV6 = nsIDNSService::RESOLVE_DISABLE_IPV6, // Not used RES_OFFLINE = nsIDNSService::RESOLVE_OFFLINE, // RES_DISABLE_IPv4 = nsIDNSService::RESOLVE_DISABLE_IPV4, // Not Used RES_ALLOW_NAME_COLLISION = nsIDNSService::RESOLVE_ALLOW_NAME_COLLISION, RES_DISABLE_TRR = nsIDNSService::RESOLVE_DISABLE_TRR, RES_REFRESH_CACHE = nsIDNSService::RESOLVE_REFRESH_CACHE, RES_IP_HINT = nsIDNSService::RESOLVE_IP_HINT }; size_t SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const; /** * Flush the DNS cache. */ void FlushCache(bool aTrrToo); LookupStatus CompleteLookup(nsHostRecord*, nsresult, mozilla::net::AddrInfo*, bool pb, const nsACString& aOriginsuffix, nsHostRecord::TRRSkippedReason aReason) override; LookupStatus CompleteLookupByType(nsHostRecord*, nsresult, mozilla::net::TypeRecordResultType& aResult, uint32_t aTtl, bool pb) override; nsresult GetHostRecord(const nsACString& host, const nsACString& trrServer, uint16_t type, uint16_t flags, uint16_t af, bool pb, const nsCString& originSuffix, nsHostRecord** result) override; nsresult TrrLookup_unlocked(nsHostRecord*, mozilla::net::TRR* pushedTRR = nullptr) override; static mozilla::net::ResolverMode Mode(); virtual void MaybeRenewHostRecord(nsHostRecord* aRec) override; private: explicit nsHostResolver(uint32_t maxCacheEntries, uint32_t defaultCacheEntryLifetime, uint32_t defaultGracePeriod); virtual ~nsHostResolver(); nsresult Init(); // In debug builds it asserts that the element is in the list. void AssertOnQ(nsHostRecord*, mozilla::LinkedList>&); static void ComputeEffectiveTRRMode(nsHostRecord* aRec); nsresult NativeLookup(nsHostRecord*); nsresult TrrLookup(nsHostRecord*, mozilla::net::TRR* pushedTRR = nullptr); // Kick-off a name resolve operation, using native resolver and/or TRR nsresult NameLookup(nsHostRecord*); bool GetHostToLookup(AddrHostRecord** result); void MaybeRenewHostRecordLocked(nsHostRecord* aRec); // Removes the first element from the list and returns it AddRef-ed in aResult // Should not be called for an empty linked list. void DeQueue(mozilla::LinkedList>& aQ, AddrHostRecord** aResult); // Cancels host records in the pending queue and also // calls CompleteLookup with the NS_ERROR_ABORT result code. void ClearPendingQueue(mozilla::LinkedList>& aPendingQ); nsresult ConditionallyCreateThread(nsHostRecord* rec); /** * Starts a new lookup in the background for entries that are in the grace * period with a failed connect or all cached entries are negative. */ nsresult ConditionallyRefreshRecord(nsHostRecord* rec, const nsACString& host); void AddToEvictionQ(nsHostRecord* rec); void ThreadFunc(); enum { METHOD_HIT = 1, METHOD_RENEWAL = 2, METHOD_NEGATIVE_HIT = 3, METHOD_LITERAL = 4, METHOD_OVERFLOW = 5, METHOD_NETWORK_FIRST = 6, METHOD_NETWORK_SHARED = 7 }; uint32_t mMaxCacheEntries; uint32_t mDefaultCacheLifetime; // granularity seconds uint32_t mDefaultGracePeriod; // granularity seconds mutable Mutex mLock; // mutable so SizeOfIncludingThis can be const CondVar mIdleTaskCV; nsRefPtrHashtable, nsHostRecord> mRecordDB; mozilla::LinkedList> mHighQ; mozilla::LinkedList> mMediumQ; mozilla::LinkedList> mLowQ; mozilla::LinkedList> mEvictionQ; uint32_t mEvictionQSize; PRTime mCreationTime; mozilla::TimeDuration mLongIdleTimeout; mozilla::TimeDuration mShortIdleTimeout; RefPtr mResolverThreads; RefPtr mNCS; mozilla::Atomic mShutdown; mozilla::Atomic mNumIdleTasks; mozilla::Atomic mActiveTaskCount; mozilla::Atomic mActiveAnyThreadCount; mozilla::Atomic mPendingCount; // Set the expiration time stamps appropriately. void PrepareRecordExpirationAddrRecord(AddrHostRecord* rec) const; public: /* * Called by the networking dashboard via the DnsService2 */ void GetDNSCacheEntries(nsTArray*); }; #endif // nsHostResolver_h__