summaryrefslogtreecommitdiffstats
path: root/netwerk/dns/TRRService.cpp
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--netwerk/dns/TRRService.cpp944
1 files changed, 944 insertions, 0 deletions
diff --git a/netwerk/dns/TRRService.cpp b/netwerk/dns/TRRService.cpp
new file mode 100644
index 0000000000..601069dbb2
--- /dev/null
+++ b/netwerk/dns/TRRService.cpp
@@ -0,0 +1,944 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#include "nsAppDirectoryServiceDefs.h"
+#include "nsCharSeparatedTokenizer.h"
+#include "nsComponentManagerUtils.h"
+#include "nsDirectoryServiceUtils.h"
+#include "nsICaptivePortalService.h"
+#include "nsIFile.h"
+#include "nsIParentalControlsService.h"
+#include "nsINetworkLinkService.h"
+#include "nsIObserverService.h"
+#include "nsIOService.h"
+#include "nsNetUtil.h"
+#include "nsStandardURL.h"
+#include "TRR.h"
+#include "TRRService.h"
+
+#include "mozilla/Preferences.h"
+#include "mozilla/StaticPrefs_network.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/Tokenizer.h"
+#include "mozilla/net/rust_helper.h"
+
+#if defined(XP_WIN) && !defined(__MINGW32__)
+# include <shlobj_core.h> // for SHGetSpecialFolderPathA
+#endif // XP_WIN
+
+static const char kOpenCaptivePortalLoginEvent[] = "captive-portal-login";
+static const char kClearPrivateData[] = "clear-private-data";
+static const char kPurge[] = "browser:purge-session-history";
+static const char kDisableIpv6Pref[] = "network.dns.disableIPv6";
+static const char kRolloutURIPref[] = "doh-rollout.uri";
+static const char kRolloutModePref[] = "doh-rollout.mode";
+
+#define TRR_PREF_PREFIX "network.trr."
+#define TRR_PREF(x) TRR_PREF_PREFIX x
+
+namespace mozilla {
+namespace net {
+
+#undef LOG
+extern mozilla::LazyLogModule gHostResolverLog;
+#define LOG(args) MOZ_LOG(gHostResolverLog, mozilla::LogLevel::Debug, args)
+
+TRRService* gTRRService = nullptr;
+StaticRefPtr<nsIThread> sTRRBackgroundThread;
+static Atomic<TRRService*> sTRRServicePtr;
+
+NS_IMPL_ISUPPORTS(TRRService, nsIObserver, nsISupportsWeakReference)
+
+TRRService::TRRService()
+ : mInitialized(false),
+ mBlocklistDurationSeconds(60),
+ mLock("trrservice"),
+ mConfirmationNS("example.com"_ns),
+ mCaptiveIsPassed(false),
+ mTRRBLStorage("DataMutex::TRRBlocklist"),
+ mConfirmationState(CONFIRM_INIT),
+ mRetryConfirmInterval(125),
+ mTRRFailures(0),
+ mParentalControlEnabled(false) {
+ MOZ_ASSERT(NS_IsMainThread(), "wrong thread");
+}
+
+// static
+void TRRService::AddObserver(nsIObserver* aObserver,
+ nsIObserverService* aObserverService) {
+ nsCOMPtr<nsIObserverService> observerService;
+ if (aObserverService) {
+ observerService = aObserverService;
+ } else {
+ observerService = mozilla::services::GetObserverService();
+ }
+
+ if (observerService) {
+ observerService->AddObserver(aObserver, NS_CAPTIVE_PORTAL_CONNECTIVITY,
+ true);
+ observerService->AddObserver(aObserver, kOpenCaptivePortalLoginEvent, true);
+ observerService->AddObserver(aObserver, kClearPrivateData, true);
+ observerService->AddObserver(aObserver, kPurge, true);
+ observerService->AddObserver(aObserver, NS_NETWORK_LINK_TOPIC, true);
+ observerService->AddObserver(aObserver, NS_DNS_SUFFIX_LIST_UPDATED_TOPIC,
+ true);
+ observerService->AddObserver(aObserver, "xpcom-shutdown-threads", true);
+ }
+}
+
+// static
+bool TRRService::CheckCaptivePortalIsPassed() {
+ bool result = false;
+ nsCOMPtr<nsICaptivePortalService> captivePortalService =
+ do_GetService(NS_CAPTIVEPORTAL_CID);
+ if (captivePortalService) {
+ int32_t captiveState;
+ MOZ_ALWAYS_SUCCEEDS(captivePortalService->GetState(&captiveState));
+
+ if ((captiveState == nsICaptivePortalService::UNLOCKED_PORTAL) ||
+ (captiveState == nsICaptivePortalService::NOT_CAPTIVE)) {
+ result = true;
+ }
+ LOG(("TRRService::Init mCaptiveState=%d mCaptiveIsPassed=%d\n",
+ captiveState, (int)result));
+ }
+
+ return result;
+}
+
+constexpr auto kTRRIsAutoDetectedKey = "(auto-detected)"_ns;
+constexpr auto kTRRNotAutoDetectedKey = "(default)"_ns;
+// static
+const nsCString& TRRService::AutoDetectedKey() {
+ if (gTRRService && gTRRService->IsUsingAutoDetectedURL()) {
+ return kTRRIsAutoDetectedKey.AsString();
+ }
+
+ return kTRRNotAutoDetectedKey.AsString();
+}
+
+static void RemoveTRRBlocklistFile() {
+ MOZ_ASSERT(NS_IsMainThread(), "Getting the profile dir on the main thread");
+
+ nsCOMPtr<nsIFile> file;
+ nsresult rv =
+ NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, getter_AddRefs(file));
+ if (NS_FAILED(rv)) {
+ return;
+ }
+
+ rv = file->AppendNative("TRRBlacklist.txt"_ns);
+ if (NS_FAILED(rv)) {
+ return;
+ }
+
+ // Dispatch an async task that removes the blocklist file from the profile.
+ rv = NS_DispatchBackgroundTask(
+ NS_NewRunnableFunction("RemoveTRRBlocklistFile::Remove",
+ [file] { file->Remove(false); }),
+ NS_DISPATCH_EVENT_MAY_BLOCK);
+ if (NS_FAILED(rv)) {
+ return;
+ }
+ Preferences::SetBool("network.trr.blocklist_cleanup_done", true);
+}
+
+nsresult TRRService::Init() {
+ MOZ_ASSERT(NS_IsMainThread(), "wrong thread");
+ if (mInitialized) {
+ return NS_OK;
+ }
+ mInitialized = true;
+
+ AddObserver(this);
+
+ nsCOMPtr<nsIPrefBranch> prefBranch;
+ GetPrefBranch(getter_AddRefs(prefBranch));
+ if (prefBranch) {
+ prefBranch->AddObserver(TRR_PREF_PREFIX, this, true);
+ prefBranch->AddObserver(kDisableIpv6Pref, this, true);
+ prefBranch->AddObserver(kRolloutURIPref, this, true);
+ prefBranch->AddObserver(kRolloutModePref, this, true);
+ }
+
+ gTRRService = this;
+ sTRRServicePtr = this;
+
+ ReadPrefs(nullptr);
+
+ if (XRE_IsParentProcess()) {
+ mCaptiveIsPassed = CheckCaptivePortalIsPassed();
+
+ mParentalControlEnabled = GetParentalControlEnabledInternal();
+
+ nsCOMPtr<nsINetworkLinkService> nls =
+ do_GetService(NS_NETWORK_LINK_SERVICE_CONTRACTID);
+ if (nls) {
+ nsTArray<nsCString> suffixList;
+ nls->GetDnsSuffixList(suffixList);
+ RebuildSuffixList(std::move(suffixList));
+ }
+
+ nsCOMPtr<nsIThread> thread;
+ if (NS_FAILED(
+ NS_NewNamedThread("TRR Background", getter_AddRefs(thread)))) {
+ NS_WARNING("NS_NewNamedThread failed!");
+ return NS_ERROR_FAILURE;
+ }
+
+ sTRRBackgroundThread = thread;
+
+ if (!StaticPrefs::network_trr_blocklist_cleanup_done()) {
+ // Dispatch an idle task to the main thread that gets the profile dir
+ // then attempts to delete the blocklist file on a background thread.
+ Unused << NS_DispatchToMainThreadQueue(
+ NS_NewCancelableRunnableFunction("RemoveTRRBlocklistFile::GetDir",
+ [] { RemoveTRRBlocklistFile(); }),
+ EventQueuePriority::Idle);
+ }
+ }
+
+ LOG(("Initialized TRRService\n"));
+ return NS_OK;
+}
+
+// static
+bool TRRService::GetParentalControlEnabledInternal() {
+ nsCOMPtr<nsIParentalControlsService> pc =
+ do_CreateInstance("@mozilla.org/parental-controls-service;1");
+ if (pc) {
+ bool result = false;
+ pc->GetParentalControlsEnabled(&result);
+ LOG(("TRRService::GetParentalControlEnabledInternal=%d\n", result));
+ return result;
+ }
+
+ return false;
+}
+
+void TRRService::SetDetectedTrrURI(const nsACString& aURI) {
+ // If the user has set a custom URI then we don't want to override that.
+ if (mURIPrefHasUserValue) {
+ return;
+ }
+
+ mURISetByDetection = MaybeSetPrivateURI(aURI);
+}
+
+bool TRRService::Enabled(nsIRequest::TRRMode aMode) {
+ if (mMode == MODE_TRROFF) {
+ return false;
+ }
+ if (mConfirmationState == CONFIRM_INIT &&
+ (!StaticPrefs::network_trr_wait_for_portal() || mCaptiveIsPassed ||
+ (mMode == MODE_TRRONLY || aMode == nsIRequest::TRR_ONLY_MODE))) {
+ LOG(("TRRService::Enabled => CONFIRM_TRYING\n"));
+ mConfirmationState = CONFIRM_TRYING;
+ }
+
+ if (mConfirmationState == CONFIRM_TRYING) {
+ LOG(("TRRService::Enabled MaybeConfirm()\n"));
+ MaybeConfirm();
+ }
+
+ if (mConfirmationState != CONFIRM_OK) {
+ LOG(("TRRService::Enabled mConfirmationState=%d mCaptiveIsPassed=%d\n",
+ (int)mConfirmationState, (int)mCaptiveIsPassed));
+ }
+
+ return (mConfirmationState == CONFIRM_OK);
+}
+
+void TRRService::GetPrefBranch(nsIPrefBranch** result) {
+ MOZ_ASSERT(NS_IsMainThread(), "wrong thread");
+ *result = nullptr;
+ CallGetService(NS_PREFSERVICE_CONTRACTID, result);
+}
+
+bool TRRService::MaybeSetPrivateURI(const nsACString& aURI) {
+ bool clearCache = false;
+ nsAutoCString newURI(aURI);
+ ProcessURITemplate(newURI);
+
+ {
+ MutexAutoLock lock(mLock);
+ if (mPrivateURI.Equals(newURI)) {
+ return false;
+ }
+
+ if (!mPrivateURI.IsEmpty()) {
+ LOG(("TRRService clearing blocklist because of change in uri service\n"));
+ auto bl = mTRRBLStorage.Lock();
+ bl->Clear();
+ clearCache = true;
+ }
+ mPrivateURI = newURI;
+ }
+
+ // Clear the cache because we changed the URI
+ if (clearCache) {
+ ClearEntireCache();
+ }
+
+ nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+ if (obs) {
+ obs->NotifyObservers(nullptr, NS_NETWORK_TRR_URI_CHANGED_TOPIC, nullptr);
+ }
+ return true;
+}
+
+nsresult TRRService::ReadPrefs(const char* name) {
+ MOZ_ASSERT(NS_IsMainThread(), "wrong thread");
+
+ // Whenever a pref change occurs that would cause us to clear the cache
+ // we set this to true then do it at the end of the method.
+ bool clearEntireCache = false;
+
+ if (!name || !strcmp(name, TRR_PREF("mode")) ||
+ !strcmp(name, kRolloutModePref)) {
+ uint32_t prevMode = Mode();
+
+ OnTRRModeChange();
+
+ // When the TRR service gets disabled we should purge the TRR cache to
+ // make sure we don't use any of the cached entries on a network where
+ // they are invalid - for example after turning on a VPN.
+ if (TRR_DISABLED(Mode()) && !TRR_DISABLED(prevMode)) {
+ clearEntireCache = true;
+ }
+ }
+ if (!name || !strcmp(name, TRR_PREF("uri")) ||
+ !strcmp(name, kRolloutURIPref)) {
+ OnTRRURIChange();
+ }
+ if (!name || !strcmp(name, TRR_PREF("credentials"))) {
+ MutexAutoLock lock(mLock);
+ Preferences::GetCString(TRR_PREF("credentials"), mPrivateCred);
+ }
+ if (!name || !strcmp(name, TRR_PREF("confirmationNS"))) {
+ MutexAutoLock lock(mLock);
+ nsAutoCString old(mConfirmationNS);
+ Preferences::GetCString(TRR_PREF("confirmationNS"), mConfirmationNS);
+ if (name && !old.IsEmpty() && !mConfirmationNS.Equals(old) &&
+ (mConfirmationState > CONFIRM_TRYING) &&
+ (mMode == MODE_TRRFIRST || mMode == MODE_TRRONLY)) {
+ LOG(("TRR::ReadPrefs: restart confirmationNS state\n"));
+ mConfirmationState = CONFIRM_TRYING;
+ MaybeConfirm_locked();
+ }
+ }
+ if (!name || !strcmp(name, TRR_PREF("bootstrapAddress"))) {
+ MutexAutoLock lock(mLock);
+ Preferences::GetCString(TRR_PREF("bootstrapAddress"), mBootstrapAddr);
+ clearEntireCache = true;
+ }
+ if (!name || !strcmp(name, TRR_PREF("blacklist-duration"))) {
+ // prefs is given in number of seconds
+ uint32_t secs;
+ if (NS_SUCCEEDED(
+ Preferences::GetUint(TRR_PREF("blacklist-duration"), &secs))) {
+ mBlocklistDurationSeconds = secs;
+ }
+ }
+ if (!name || !strcmp(name, kDisableIpv6Pref)) {
+ bool tmp;
+ if (NS_SUCCEEDED(Preferences::GetBool(kDisableIpv6Pref, &tmp))) {
+ mDisableIPv6 = tmp;
+ }
+ }
+ if (!name || !strcmp(name, TRR_PREF("excluded-domains")) ||
+ !strcmp(name, TRR_PREF("builtin-excluded-domains"))) {
+ MutexAutoLock lock(mLock);
+ mExcludedDomains.Clear();
+
+ auto parseExcludedDomains = [this](const char* aPrefName) {
+ nsAutoCString excludedDomains;
+ Preferences::GetCString(aPrefName, excludedDomains);
+ if (excludedDomains.IsEmpty()) {
+ return;
+ }
+
+ for (const nsACString& tokenSubstring :
+ nsCCharSeparatedTokenizerTemplate<
+ NS_IsAsciiWhitespace, nsTokenizerFlags::SeparatorOptional>(
+ excludedDomains, ',')
+ .ToRange()) {
+ nsCString token{tokenSubstring};
+ LOG(("TRRService::ReadPrefs %s host:[%s]\n", aPrefName, token.get()));
+ mExcludedDomains.PutEntry(token);
+ }
+ };
+
+ parseExcludedDomains(TRR_PREF("excluded-domains"));
+ parseExcludedDomains(TRR_PREF("builtin-excluded-domains"));
+ clearEntireCache = true;
+ }
+
+ // if name is null, then we're just now initializing. In that case we don't
+ // need to clear the cache.
+ if (name && clearEntireCache) {
+ ClearEntireCache();
+ }
+
+ return NS_OK;
+}
+
+void TRRService::ClearEntireCache() {
+ if (!StaticPrefs::network_trr_clear_cache_on_pref_change()) {
+ return;
+ }
+ nsCOMPtr<nsIDNSService> dns = do_GetService(NS_DNSSERVICE_CONTRACTID);
+ if (!dns) {
+ return;
+ }
+ dns->ClearCache(true);
+}
+
+void TRRService::AddEtcHosts(const nsTArray<nsCString>& aArray) {
+ MutexAutoLock lock(mLock);
+ for (const auto& item : aArray) {
+ LOG(("Adding %s from /etc/hosts to excluded domains", item.get()));
+ mEtcHostsDomains.PutEntry(item);
+ }
+}
+
+void TRRService::ReadEtcHostsFile() {
+ if (!StaticPrefs::network_trr_exclude_etc_hosts()) {
+ return;
+ }
+
+ auto readHostsTask = []() {
+ MOZ_ASSERT(!NS_IsMainThread(), "Must not run on the main thread");
+#if defined(XP_WIN) && !defined(__MINGW32__)
+ // Inspired by libevent/evdns.c
+ // Windows is a little coy about where it puts its configuration
+ // files. Sure, they're _usually_ in C:\windows\system32, but
+ // there's no reason in principle they couldn't be in
+ // W:\hoboken chicken emergency
+
+ nsCString path;
+ path.SetLength(MAX_PATH + 1);
+ if (!SHGetSpecialFolderPathA(NULL, path.BeginWriting(), CSIDL_SYSTEM,
+ false)) {
+ LOG(("Calling SHGetSpecialFolderPathA failed"));
+ return;
+ }
+
+ path.SetLength(strlen(path.get()));
+ path.Append("\\drivers\\etc\\hosts");
+#elif defined(__MINGW32__)
+ nsAutoCString path("C:\\windows\\system32\\drivers\\etc\\hosts"_ns);
+#else
+ nsAutoCString path("/etc/hosts"_ns);
+#endif
+
+ LOG(("Reading hosts file at %s", path.get()));
+ rust_parse_etc_hosts(&path, [](const nsTArray<nsCString>* aArray) -> bool {
+ RefPtr<TRRService> service(sTRRServicePtr);
+ if (service && aArray) {
+ service->AddEtcHosts(*aArray);
+ }
+ return !!service;
+ });
+ };
+
+ Unused << NS_DispatchBackgroundTask(
+ NS_NewRunnableFunction("Read /etc/hosts file", readHostsTask),
+ NS_DISPATCH_EVENT_MAY_BLOCK);
+}
+
+nsresult TRRService::GetURI(nsACString& result) {
+ MutexAutoLock lock(mLock);
+ result = mPrivateURI;
+ return NS_OK;
+}
+
+nsresult TRRService::GetCredentials(nsCString& result) {
+ MutexAutoLock lock(mLock);
+ result = mPrivateCred;
+ return NS_OK;
+}
+
+uint32_t TRRService::GetRequestTimeout() {
+ if (mMode == MODE_TRRONLY) {
+ return StaticPrefs::network_trr_request_timeout_mode_trronly_ms();
+ }
+
+ return StaticPrefs::network_trr_request_timeout_ms();
+}
+
+nsresult TRRService::Start() {
+ MOZ_ASSERT(NS_IsMainThread(), "wrong thread");
+ if (!mInitialized) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+ return NS_OK;
+}
+
+TRRService::~TRRService() {
+ MOZ_ASSERT(NS_IsMainThread(), "wrong thread");
+ LOG(("Exiting TRRService\n"));
+ gTRRService = nullptr;
+}
+
+nsresult TRRService::DispatchTRRRequest(TRR* aTrrRequest) {
+ return DispatchTRRRequestInternal(aTrrRequest, true);
+}
+
+nsresult TRRService::DispatchTRRRequestInternal(TRR* aTrrRequest,
+ bool aWithLock) {
+ NS_ENSURE_ARG_POINTER(aTrrRequest);
+ if (!StaticPrefs::network_trr_fetch_off_main_thread() ||
+ XRE_IsSocketProcess()) {
+ return NS_DispatchToMainThread(aTrrRequest);
+ }
+
+ RefPtr<TRR> trr = aTrrRequest;
+ nsCOMPtr<nsIThread> thread = aWithLock ? TRRThread() : TRRThread_locked();
+ if (!thread) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return thread->Dispatch(trr.forget());
+}
+
+already_AddRefed<nsIThread> TRRService::TRRThread() {
+ MutexAutoLock lock(mLock);
+ return TRRThread_locked();
+}
+
+already_AddRefed<nsIThread> TRRService::TRRThread_locked() {
+ RefPtr<nsIThread> thread = sTRRBackgroundThread;
+ return thread.forget();
+}
+
+bool TRRService::IsOnTRRThread() {
+ nsCOMPtr<nsIThread> thread;
+ {
+ MutexAutoLock lock(mLock);
+ thread = sTRRBackgroundThread;
+ }
+ if (!thread) {
+ return false;
+ }
+
+ return thread->IsOnCurrentThread();
+}
+
+NS_IMETHODIMP
+TRRService::Observe(nsISupports* aSubject, const char* aTopic,
+ const char16_t* aData) {
+ MOZ_ASSERT(NS_IsMainThread(), "wrong thread");
+ LOG(("TRR::Observe() topic=%s\n", aTopic));
+ if (!strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID)) {
+ ReadPrefs(NS_ConvertUTF16toUTF8(aData).get());
+
+ MutexAutoLock lock(mLock);
+ if (((mConfirmationState == CONFIRM_INIT) && !mBootstrapAddr.IsEmpty() &&
+ (mMode == MODE_TRRONLY)) ||
+ (mConfirmationState == CONFIRM_FAILED)) {
+ mConfirmationState = CONFIRM_TRYING;
+ MaybeConfirm_locked();
+ }
+
+ } else if (!strcmp(aTopic, kOpenCaptivePortalLoginEvent)) {
+ // We are in a captive portal
+ LOG(("TRRservice in captive portal\n"));
+ mCaptiveIsPassed = false;
+ } else if (!strcmp(aTopic, NS_CAPTIVE_PORTAL_CONNECTIVITY)) {
+ nsAutoCString data = NS_ConvertUTF16toUTF8(aData);
+ LOG(("TRRservice captive portal was %s\n", data.get()));
+
+ // We should avoid doing calling MaybeConfirm in response to a pref change
+ // unless the service is in a TRR=enabled mode.
+ if (mMode == MODE_TRRFIRST || mMode == MODE_TRRONLY) {
+ if (!mCaptiveIsPassed) {
+ if (mConfirmationState != CONFIRM_OK) {
+ mConfirmationState = CONFIRM_TRYING;
+ MaybeConfirm();
+ }
+ } else {
+ LOG(("TRRservice CP clear when already up!\n"));
+ }
+ mCaptiveIsPassed = true;
+ }
+
+ } else if (!strcmp(aTopic, kClearPrivateData) || !strcmp(aTopic, kPurge)) {
+ // flush the TRR blocklist
+ auto bl = mTRRBLStorage.Lock();
+ bl->Clear();
+ } else if (!strcmp(aTopic, NS_DNS_SUFFIX_LIST_UPDATED_TOPIC) ||
+ !strcmp(aTopic, NS_NETWORK_LINK_TOPIC)) {
+ // nsINetworkLinkService is only available on parent process.
+ if (XRE_IsParentProcess()) {
+ nsCOMPtr<nsINetworkLinkService> link = do_QueryInterface(aSubject);
+ // The network link service notification normally passes itself as the
+ // subject, but some unit tests will sometimes pass a null subject.
+ if (link) {
+ nsTArray<nsCString> suffixList;
+ link->GetDnsSuffixList(suffixList);
+ RebuildSuffixList(std::move(suffixList));
+ }
+ }
+
+ if (!strcmp(aTopic, NS_NETWORK_LINK_TOPIC) && mURISetByDetection) {
+ // If the URI was set via SetDetectedTrrURI we need to restore it to the
+ // default pref when a network link change occurs.
+ CheckURIPrefs();
+ }
+ } else if (!strcmp(aTopic, "xpcom-shutdown-threads")) {
+ if (sTRRBackgroundThread) {
+ nsCOMPtr<nsIThread> thread;
+ {
+ MutexAutoLock lock(mLock);
+ thread = sTRRBackgroundThread.get();
+ sTRRBackgroundThread = nullptr;
+ }
+ MOZ_ALWAYS_SUCCEEDS(thread->Shutdown());
+ sTRRServicePtr = nullptr;
+ }
+ }
+ return NS_OK;
+}
+
+void TRRService::RebuildSuffixList(nsTArray<nsCString>&& aSuffixList) {
+ if (!StaticPrefs::network_trr_split_horizon_mitigations()) {
+ return;
+ }
+
+ MutexAutoLock lock(mLock);
+ mDNSSuffixDomains.Clear();
+ for (const auto& item : aSuffixList) {
+ LOG(("TRRService adding %s to suffix list", item.get()));
+ mDNSSuffixDomains.PutEntry(item);
+ }
+}
+
+void TRRService::MaybeConfirm() {
+ MutexAutoLock lock(mLock);
+ MaybeConfirm_locked();
+}
+
+void TRRService::MaybeConfirm_locked() {
+ mLock.AssertCurrentThreadOwns();
+ if (mMode == MODE_TRROFF || mConfirmer ||
+ mConfirmationState != CONFIRM_TRYING) {
+ LOG(
+ ("TRRService:MaybeConfirm mode=%d, mConfirmer=%p "
+ "mConfirmationState=%d\n",
+ (int)mMode, (void*)mConfirmer, (int)mConfirmationState));
+ return;
+ }
+
+ if (mConfirmationNS.Equals("skip") || mMode == MODE_TRRONLY) {
+ LOG(("TRRService starting confirmation test %s SKIPPED\n",
+ mPrivateURI.get()));
+ mConfirmationState = CONFIRM_OK;
+ } else {
+ LOG(("TRRService starting confirmation test %s %s\n", mPrivateURI.get(),
+ mConfirmationNS.get()));
+ mConfirmer = new TRR(this, mConfirmationNS, TRRTYPE_NS, ""_ns, false);
+ DispatchTRRRequestInternal(mConfirmer, false);
+ }
+}
+
+bool TRRService::MaybeBootstrap(const nsACString& aPossible,
+ nsACString& aResult) {
+ MutexAutoLock lock(mLock);
+ if (mMode == MODE_TRROFF || mBootstrapAddr.IsEmpty()) {
+ return false;
+ }
+
+ nsCOMPtr<nsIURI> url;
+ nsresult rv =
+ NS_MutateURI(NS_STANDARDURLMUTATOR_CONTRACTID)
+ .Apply(NS_MutatorMethod(&nsIStandardURLMutator::Init,
+ nsIStandardURL::URLTYPE_STANDARD, 443,
+ mPrivateURI, nullptr, nullptr, nullptr))
+ .Finalize(url);
+ if (NS_FAILED(rv)) {
+ LOG(("TRRService::MaybeBootstrap failed to create URI!\n"));
+ return false;
+ }
+
+ nsAutoCString host;
+ url->GetHost(host);
+ if (!aPossible.Equals(host)) {
+ return false;
+ }
+ LOG(("TRRService::MaybeBootstrap: use %s instead of %s\n",
+ mBootstrapAddr.get(), host.get()));
+ aResult = mBootstrapAddr;
+ return true;
+}
+
+bool TRRService::IsDomainBlocked(const nsACString& aHost,
+ const nsACString& aOriginSuffix,
+ bool aPrivateBrowsing) {
+ if (!Enabled(nsIRequest::TRR_DEFAULT_MODE)) {
+ return true;
+ }
+
+ auto bl = mTRRBLStorage.Lock();
+ if (bl->IsEmpty()) {
+ return false;
+ }
+
+ // use a unified casing for the hashkey
+ nsAutoCString hashkey(aHost + aOriginSuffix);
+ if (int32_t* val = bl->GetValue(hashkey)) {
+ int32_t until = *val + mBlocklistDurationSeconds;
+ int32_t expire = NowInSeconds();
+ if (until > expire) {
+ LOG(("Host [%s] is TRR blocklisted\n", nsCString(aHost).get()));
+ return true;
+ }
+
+ // the blocklisted entry has expired
+ bl->Remove(hashkey);
+ }
+ return false;
+}
+
+// When running in TRR-only mode, the blocklist is not used and it will also
+// try resolving the localhost / .local names.
+bool TRRService::IsTemporarilyBlocked(const nsACString& aHost,
+ const nsACString& aOriginSuffix,
+ bool aPrivateBrowsing,
+ bool aParentsToo) // false if domain
+{
+ if (mMode == MODE_TRRONLY) {
+ return false; // might as well try
+ }
+
+ LOG(("Checking if host [%s] is blocklisted", aHost.BeginReading()));
+
+ int32_t dot = aHost.FindChar('.');
+ if ((dot == kNotFound) && aParentsToo) {
+ // Only if a full host name. Domains can be dotless to be able to
+ // blocklist entire TLDs
+ return true;
+ }
+
+ if (IsDomainBlocked(aHost, aOriginSuffix, aPrivateBrowsing)) {
+ return true;
+ }
+
+ nsDependentCSubstring domain = Substring(aHost, 0);
+ while (dot != kNotFound) {
+ dot++;
+ domain.Rebind(domain, dot, domain.Length() - dot);
+
+ if (IsDomainBlocked(domain, aOriginSuffix, aPrivateBrowsing)) {
+ return true;
+ }
+
+ dot = domain.FindChar('.');
+ }
+
+ return false;
+}
+
+bool TRRService::IsExcludedFromTRR(const nsACString& aHost) {
+ // This method may be called off the main thread. We need to lock so
+ // mExcludedDomains and mDNSSuffixDomains don't change while this code
+ // is running.
+ MutexAutoLock lock(mLock);
+
+ return IsExcludedFromTRR_unlocked(aHost);
+}
+
+bool TRRService::IsExcludedFromTRR_unlocked(const nsACString& aHost) {
+ if (!NS_IsMainThread()) {
+ mLock.AssertCurrentThreadOwns();
+ }
+
+ int32_t dot = 0;
+ // iteratively check the sub-domain of |aHost|
+ while (dot < static_cast<int32_t>(aHost.Length())) {
+ nsDependentCSubstring subdomain =
+ Substring(aHost, dot, aHost.Length() - dot);
+
+ if (mExcludedDomains.GetEntry(subdomain)) {
+ LOG(("Subdomain [%s] of host [%s] Is Excluded From TRR via pref\n",
+ subdomain.BeginReading(), aHost.BeginReading()));
+ return true;
+ }
+ if (mDNSSuffixDomains.GetEntry(subdomain)) {
+ LOG(("Subdomain [%s] of host [%s] Is Excluded From TRR via pref\n",
+ subdomain.BeginReading(), aHost.BeginReading()));
+ return true;
+ }
+ if (mEtcHostsDomains.GetEntry(subdomain)) {
+ LOG(("Subdomain [%s] of host [%s] Is Excluded From TRR by /etc/hosts\n",
+ subdomain.BeginReading(), aHost.BeginReading()));
+ return true;
+ }
+
+ dot = aHost.FindChar('.', dot + 1);
+ if (dot == kNotFound) {
+ break;
+ }
+ dot++;
+ }
+
+ return false;
+}
+
+void TRRService::AddToBlocklist(const nsACString& aHost,
+ const nsACString& aOriginSuffix,
+ bool privateBrowsing, bool aParentsToo) {
+ LOG(("TRR blocklist %s\n", nsCString(aHost).get()));
+ nsAutoCString hashkey(aHost + aOriginSuffix);
+
+ // this overwrites any existing entry
+ {
+ auto bl = mTRRBLStorage.Lock();
+ bl->Put(hashkey, NowInSeconds());
+ }
+
+ if (aParentsToo) {
+ // when given a full host name, verify its domain as well
+ int32_t dot = aHost.FindChar('.');
+ if (dot != kNotFound) {
+ // this has a domain to be checked
+ dot++;
+ nsDependentCSubstring domain =
+ Substring(aHost, dot, aHost.Length() - dot);
+ nsAutoCString check(domain);
+ if (IsTemporarilyBlocked(check, aOriginSuffix, privateBrowsing, false)) {
+ // the domain part is already blocklisted, no need to add this entry
+ return;
+ }
+ // verify 'check' over TRR
+ LOG(("TRR: verify if '%s' resolves as NS\n", check.get()));
+
+ // check if there's an NS entry for this name
+ RefPtr<TRR> trr =
+ new TRR(this, check, TRRTYPE_NS, aOriginSuffix, privateBrowsing);
+ DispatchTRRRequest(trr);
+ }
+ }
+}
+
+NS_IMETHODIMP
+TRRService::Notify(nsITimer* aTimer) {
+ if (aTimer == mRetryConfirmTimer) {
+ mRetryConfirmTimer = nullptr;
+ if (mConfirmationState == CONFIRM_FAILED) {
+ LOG(("TRRService retry NS of %s\n", mConfirmationNS.get()));
+ mConfirmationState = CONFIRM_TRYING;
+ MaybeConfirm();
+ }
+ } else {
+ MOZ_CRASH("Unknown timer");
+ }
+
+ return NS_OK;
+}
+
+void TRRService::TRRIsOkay(enum TrrOkay aReason) {
+ MOZ_ASSERT_IF(XRE_IsParentProcess(), NS_IsMainThread() || IsOnTRRThread());
+ MOZ_ASSERT_IF(XRE_IsSocketProcess(), NS_IsMainThread());
+
+ Telemetry::AccumulateCategoricalKeyed(
+ AutoDetectedKey(),
+ aReason == OKAY_NORMAL
+ ? Telemetry::LABELS_DNS_TRR_SUCCESS2::Fine
+ : (aReason == OKAY_TIMEOUT
+ ? Telemetry::LABELS_DNS_TRR_SUCCESS2::Timeout
+ : Telemetry::LABELS_DNS_TRR_SUCCESS2::Bad));
+ if (aReason == OKAY_NORMAL) {
+ mTRRFailures = 0;
+ } else if ((mMode == MODE_TRRFIRST) && (mConfirmationState == CONFIRM_OK)) {
+ // only count failures while in OK state
+ uint32_t fails = ++mTRRFailures;
+ if (fails >= StaticPrefs::network_trr_max_fails()) {
+ LOG(("TRRService goes FAILED after %u failures in a row\n", fails));
+ mConfirmationState = CONFIRM_FAILED;
+ // Fire off a timer and start re-trying the NS domain again
+ NS_NewTimerWithCallback(getter_AddRefs(mRetryConfirmTimer), this,
+ mRetryConfirmInterval, nsITimer::TYPE_ONE_SHOT);
+ mTRRFailures = 0; // clear it again
+ }
+ }
+}
+
+AHostResolver::LookupStatus TRRService::CompleteLookup(
+ nsHostRecord* rec, nsresult status, AddrInfo* aNewRRSet, bool pb,
+ const nsACString& aOriginSuffix, nsHostRecord::TRRSkippedReason aReason) {
+ // this is an NS check for the TRR blocklist or confirmationNS check
+
+ MOZ_ASSERT_IF(XRE_IsParentProcess(), NS_IsMainThread() || IsOnTRRThread());
+ MOZ_ASSERT_IF(XRE_IsSocketProcess(), NS_IsMainThread());
+ MOZ_ASSERT(!rec);
+
+ RefPtr<AddrInfo> newRRSet(aNewRRSet);
+ MOZ_ASSERT(newRRSet && newRRSet->IsTRR() == TRRTYPE_NS);
+
+#ifdef DEBUG
+ {
+ MutexAutoLock lock(mLock);
+ MOZ_ASSERT(!mConfirmer || (mConfirmationState == CONFIRM_TRYING));
+ }
+#endif
+ if (mConfirmationState == CONFIRM_TRYING) {
+ {
+ MutexAutoLock lock(mLock);
+ MOZ_ASSERT(mConfirmer);
+ mConfirmationState = NS_SUCCEEDED(status) ? CONFIRM_OK : CONFIRM_FAILED;
+ LOG(("TRRService finishing confirmation test %s %d %X\n",
+ mPrivateURI.get(), (int)mConfirmationState, (unsigned int)status));
+ mConfirmer = nullptr;
+
+ if (mConfirmationState == CONFIRM_OK) {
+ // A fresh confirmation means previous blocked entries might not
+ // be valid anymore.
+ auto bl = mTRRBLStorage.Lock();
+ bl->Clear();
+ }
+ }
+ if (mConfirmationState == CONFIRM_FAILED) {
+ // retry failed NS confirmation
+ NS_NewTimerWithCallback(getter_AddRefs(mRetryConfirmTimer), this,
+ mRetryConfirmInterval, nsITimer::TYPE_ONE_SHOT);
+ if (mRetryConfirmInterval < 64000) {
+ // double the interval up to this point
+ mRetryConfirmInterval *= 2;
+ }
+ } else {
+ if (mMode != MODE_TRRONLY) {
+ // don't accumulate trronly data here since trronly failures are
+ // handled above by trying again, so counting the successes here would
+ // skew the numbers
+ Telemetry::Accumulate(Telemetry::DNS_TRR_NS_VERFIFIED2,
+ TRRService::AutoDetectedKey(),
+ (mConfirmationState == CONFIRM_OK));
+ }
+ mRetryConfirmInterval = StaticPrefs::network_trr_retry_timeout_ms();
+ }
+ return LOOKUP_OK;
+ }
+
+ // when called without a host record, this is a domain name check response.
+ if (NS_SUCCEEDED(status)) {
+ LOG(("TRR verified %s to be fine!\n", newRRSet->Hostname().get()));
+ } else {
+ LOG(("TRR says %s doesn't resolve as NS!\n", newRRSet->Hostname().get()));
+ AddToBlocklist(newRRSet->Hostname(), aOriginSuffix, pb, false);
+ }
+ return LOOKUP_OK;
+}
+
+AHostResolver::LookupStatus TRRService::CompleteLookupByType(
+ nsHostRecord*, nsresult, mozilla::net::TypeRecordResultType& aResult,
+ uint32_t aTtl, bool aPb) {
+ return LOOKUP_OK;
+}
+
+#undef LOG
+
+} // namespace net
+} // namespace mozilla