Bug 1539819 - P5: Extract the logic of processing TRRService URI r=valentin
authorKershaw Chang <kershaw@mozilla.com>
Mon, 18 May 2020 20:18:15 +0000
changeset 530707 7c84257e93bec6b4b09514e349886e29b6f7e573
parent 530706 a0f338331b410462ac54b92603232828cecc65f4
child 530708 e4e0437883e3876fb38f7e350bc83a6c4671d896
push id116316
push userkjang@mozilla.com
push dateMon, 18 May 2020 20:28:45 +0000
treeherderautoland@7c84257e93be [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersvalentin
bugs1539819
milestone78.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1539819 - P5: Extract the logic of processing TRRService URI r=valentin Differential Revision: https://phabricator.services.mozilla.com/D73903
netwerk/dns/ChildDNSService.cpp
netwerk/dns/PTRRService.ipdl
netwerk/dns/TRRService.cpp
netwerk/dns/TRRService.h
netwerk/dns/TRRServiceBase.cpp
netwerk/dns/TRRServiceBase.h
netwerk/dns/TRRServiceChild.cpp
netwerk/dns/TRRServiceChild.h
netwerk/dns/TRRServiceParent.cpp
netwerk/dns/TRRServiceParent.h
netwerk/dns/moz.build
--- a/netwerk/dns/ChildDNSService.cpp
+++ b/netwerk/dns/ChildDNSService.cpp
@@ -355,39 +355,62 @@ ChildDNSService::GetDNSCacheEntries(
   // Only used by networking dashboard, so may not ever need this in child.
   // (and would provide a way to spy on what hosts other apps are connecting to,
   // unless we start keeping per-app DNS caches).
   return NS_ERROR_NOT_AVAILABLE;
 }
 
 NS_IMETHODIMP
 ChildDNSService::ClearCache(bool aTrrToo) {
+  if (!mTRRServiceParent || !mTRRServiceParent->CanSend()) {
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
   Unused << mTRRServiceParent->SendClearDNSCache(aTrrToo);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 ChildDNSService::ReloadParentalControlEnabled() {
+  if (!mTRRServiceParent) {
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
   mTRRServiceParent->UpdateParentalControlEnabled();
   return NS_OK;
 }
 
 NS_IMETHODIMP
 ChildDNSService::SetDetectedTrrURI(const nsACString& aURI) {
-  return NS_ERROR_NOT_AVAILABLE;
+  if (!mTRRServiceParent) {
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
+  mTRRServiceParent->SetDetectedTrrURI(aURI);
+  return NS_OK;
 }
 
 NS_IMETHODIMP
 ChildDNSService::GetCurrentTrrURI(nsACString& aURI) {
-  return NS_ERROR_NOT_AVAILABLE;
+  if (!mTRRServiceParent) {
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
+  mTRRServiceParent->GetTrrURI(aURI);
+  return NS_OK;
 }
 
 NS_IMETHODIMP
 ChildDNSService::GetCurrentTrrMode(uint32_t* aMode) {
-  return NS_ERROR_NOT_AVAILABLE;
+  if (!mTRRServiceParent) {
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
+  *aMode = mTRRServiceParent->Mode();
+  return NS_OK;
 }
 
 NS_IMETHODIMP
 ChildDNSService::GetMyHostName(nsACString& result) {
   // TODO: get value from parent during PNecko construction?
   return NS_ERROR_NOT_AVAILABLE;
 }
 
--- a/netwerk/dns/PTRRService.ipdl
+++ b/netwerk/dns/PTRRService.ipdl
@@ -18,12 +18,13 @@ async refcounted protocol PTRRService
 child:
   async __delete__();
   async NotifyObserver(nsCString aTopic, nsString aData);
   async UpdatePlatformDNSInformation(nsCString[] aSuffixList,
                                      bool aPlatformDisabledTRR);
   async InitTRRBLStorage(DataStorageEntry aEntry, FileDescriptor aWriteFd);
   async UpdateParentalControlEnabled(bool aEnabled);
   async ClearDNSCache(bool aTrrToo);
+  async SetDetectedTrrURI(nsCString aURI);
 };
 
 } //namespace net
 } //namespace mozilla
--- a/netwerk/dns/TRRService.cpp
+++ b/netwerk/dns/TRRService.cpp
@@ -41,17 +41,16 @@ extern mozilla::LazyLogModule gHostResol
 
 TRRService* gTRRService = nullptr;
 StaticRefPtr<nsIThread> sTRRBackgroundThread;
 
 NS_IMPL_ISUPPORTS(TRRService, nsIObserver, nsISupportsWeakReference)
 
 TRRService::TRRService()
     : mInitialized(false),
-      mMode(0),
       mTRRBlacklistExpireTime(72 * 3600),
       mLock("trrservice"),
       mConfirmationNS(NS_LITERAL_CSTRING("example.com")),
       mWaitForCaptive(true),
       mRfc1918(false),
       mCaptiveIsPassed(false),
       mUseGET(false),
       mDisableECS(true),
@@ -201,65 +200,16 @@ bool TRRService::Enabled(nsIRequest::TRR
 }
 
 void TRRService::GetPrefBranch(nsIPrefBranch** result) {
   MOZ_ASSERT(NS_IsMainThread(), "wrong thread");
   *result = nullptr;
   CallGetService(NS_PREFSERVICE_CONTRACTID, result);
 }
 
-void TRRService::ProcessURITemplate(nsACString& aURI) {
-  // URI Template, RFC 6570.
-  if (aURI.IsEmpty()) {
-    return;
-  }
-  nsAutoCString scheme;
-  nsCOMPtr<nsIIOService> ios(do_GetIOService());
-  if (ios) {
-    ios->ExtractScheme(aURI, scheme);
-  }
-  if (!scheme.Equals("https")) {
-    LOG(("TRRService TRR URI %s is not https. Not used.\n",
-         PromiseFlatCString(aURI).get()));
-    aURI.Truncate();
-    return;
-  }
-
-  // cut off everything from "{" to "}" sequences (potentially multiple),
-  // as a crude conversion from template into URI.
-  nsAutoCString uri(aURI);
-
-  do {
-    nsCCharSeparatedTokenizer openBrace(uri, '{');
-    if (openBrace.hasMoreTokens()) {
-      // the 'nextToken' is the left side of the open brace (or full uri)
-      nsAutoCString prefix(openBrace.nextToken());
-
-      // if there is an open brace, there's another token
-      const nsACString& endBrace = openBrace.nextToken();
-      nsCCharSeparatedTokenizer closeBrace(endBrace, '}');
-      if (closeBrace.hasMoreTokens()) {
-        // there is a close brace as well, make a URI out of the prefix
-        // and the suffix
-        closeBrace.nextToken();
-        nsAutoCString suffix(closeBrace.nextToken());
-        uri = prefix + suffix;
-      } else {
-        // no (more) close brace
-        break;
-      }
-    } else {
-      // no (more) open brace
-      break;
-    }
-  } while (true);
-
-  aURI = uri;
-}
-
 bool TRRService::MaybeSetPrivateURI(const nsACString& aURI) {
   bool clearCache = false;
   nsAutoCString newURI(aURI);
   ProcessURITemplate(newURI);
 
   {
     MutexAutoLock lock(mLock);
     if (mPrivateURI.Equals(newURI)) {
@@ -281,91 +231,30 @@ bool TRRService::MaybeSetPrivateURI(cons
 
   nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
   if (obs) {
     obs->NotifyObservers(nullptr, NS_NETWORK_TRR_URI_CHANGED_TOPIC, nullptr);
   }
   return true;
 }
 
-void TRRService::CheckURIPrefs() {
-  mURISetByDetection = false;
-
-  // The user has set a custom URI so it takes precedence.
-  if (mURIPrefHasUserValue) {
-    MaybeSetPrivateURI(mURIPref);
-    return;
-  }
-
-  // Check if the rollout addon has set a pref.
-  if (!mRolloutURIPref.IsEmpty()) {
-    MaybeSetPrivateURI(mRolloutURIPref);
-    return;
-  }
-
-  // Otherwise just use the default value.
-  MaybeSetPrivateURI(mURIPref);
-}
-
-// static
-uint32_t TRRService::ModeFromPrefs() {
-  // 0 - off, 1 - reserved, 2 - TRR first, 3 - TRR only, 4 - reserved,
-  // 5 - explicit off
-
-  auto processPrefValue = [](uint32_t value) -> uint32_t {
-    if (value == MODE_RESERVED1 || value == MODE_RESERVED4 ||
-        value > MODE_TRROFF) {
-      return MODE_TRROFF;
-    }
-    return value;
-  };
-
-  uint32_t tmp = MODE_NATIVEONLY;
-  if (NS_SUCCEEDED(Preferences::GetUint(TRR_PREF("mode"), &tmp))) {
-    tmp = processPrefValue(tmp);
-  }
-
-  if (tmp != MODE_NATIVEONLY) {
-    return tmp;
-  }
-
-  if (NS_SUCCEEDED(Preferences::GetUint(kRolloutModePref, &tmp))) {
-    return processPrefValue(tmp);
-  }
-
-  return MODE_NATIVEONLY;
-}
-
 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 oldMode = mMode;
-    mMode = ModeFromPrefs();
-    if (mMode != oldMode) {
-      nsCOMPtr<nsIObserverService> obs =
-          mozilla::services::GetObserverService();
-      if (obs) {
-        obs->NotifyObservers(nullptr, NS_NETWORK_TRR_MODE_CHANGED_TOPIC,
-                             nullptr);
-      }
-    }
+    OnTRRModeChange();
   }
   if (!name || !strcmp(name, TRR_PREF("uri")) ||
       !strcmp(name, kRolloutURIPref)) {
-    mURIPrefHasUserValue = Preferences::HasUserValue(TRR_PREF("uri"));
-    Preferences::GetCString(TRR_PREF("uri"), mURIPref);
-    Preferences::GetCString(kRolloutURIPref, mRolloutURIPref);
-
-    CheckURIPrefs();
+    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);
--- a/netwerk/dns/TRRService.h
+++ b/netwerk/dns/TRRService.h
@@ -1,48 +1,48 @@
 /* -*- 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/. */
 
 #ifndef TRRService_h_
 #define TRRService_h_
 
-#include "mozilla/Atomics.h"
 #include "mozilla/DataStorage.h"
 #include "nsHostResolver.h"
 #include "nsIObserver.h"
 #include "nsWeakReference.h"
+#include "TRRServiceBase.h"
 
 class nsDNSService;
 class nsIPrefBranch;
 class nsINetworkLinkService;
 
 namespace mozilla {
 namespace net {
 
 class TRRServiceChild;
 class TRRServiceParent;
 
-class TRRService : public nsIObserver,
+class TRRService : public TRRServiceBase,
+                   public nsIObserver,
                    public nsITimerCallback,
                    public nsSupportsWeakReference,
                    public AHostResolver {
  public:
   NS_DECL_THREADSAFE_ISUPPORTS
   NS_DECL_NSIOBSERVER
   NS_DECL_NSITIMERCALLBACK
 
   TRRService();
   nsresult Init();
   nsresult Start();
   bool Enabled(nsIRequest::TRRMode aMode);
   bool IsConfirmed() { return mConfirmationState == CONFIRM_OK; }
 
-  uint32_t Mode() { return mMode; }
   bool AllowRFC1918() { return mRfc1918; }
   bool UseGET() { return mUseGET; }
   bool EarlyAAAA() { return mEarlyAAAA; }
   bool CheckIPv6Connectivity() { return mCheckIPv6Connectivity; }
   bool WaitForAllResponses() { return mWaitForAllResponses; }
   bool DisableIPv6() { return mDisableIPv6; }
   bool DisableECS() { return mDisableECS; }
   bool SkipTRRWhenParentalControlEnabled() {
@@ -99,46 +99,27 @@ class TRRService : public nsIObserver,
   void RebuildSuffixList(nsTArray<nsCString>&& aSuffixList);
 
   nsresult DispatchTRRRequestInternal(TRR* aTrrRequest, bool aWithLock);
   already_AddRefed<nsIThread> TRRThread_locked();
 
   // This method will process the URI and try to set mPrivateURI to that value.
   // Will return true if performed the change (if the value was different)
   // or false if mPrivateURI already had that value.
-  bool MaybeSetPrivateURI(const nsACString& aURI);
-  // Checks the network.trr.uri or the doh-rollout.uri prefs and sets the URI
-  // in order of preference:
-  // 1. The value of network.trr.uri if it is not the default one, meaning
-  //    is was set by an explicit user action
-  // 2. The value of doh-rollout.uri if it exists
-  //    this is set by the rollout addon
-  // 3. The default value of network.trr.uri
-  void CheckURIPrefs();
-  void ProcessURITemplate(nsACString& aURI);
+  bool MaybeSetPrivateURI(const nsACString& aURI) override;
   void ClearEntireCache();
 
-  static uint32_t ModeFromPrefs();
-
   bool mInitialized;
-  Atomic<uint32_t, Relaxed> mMode;
   Atomic<uint32_t, Relaxed> mTRRBlacklistExpireTime;
 
-  // Pref caches should only be used on the main thread.
-  nsCString mURIPref;
-  bool mURIPrefHasUserValue = false;
-  nsCString mRolloutURIPref;
-
   Mutex mLock;
 
-  nsCString mPrivateURI;   // main thread only
   nsCString mPrivateCred;  // main thread only
   nsCString mConfirmationNS;
   nsCString mBootstrapAddr;
-  bool mURISetByDetection = false;
 
   Atomic<bool, Relaxed> mWaitForCaptive;  // wait for the captive portal to say
                                           // OK before using TRR
   Atomic<bool, Relaxed>
       mRfc1918;  // okay with local IP addresses in DOH responses?
   Atomic<bool, Relaxed>
       mCaptiveIsPassed;           // set when captive portal check is passed
   Atomic<bool, Relaxed> mUseGET;  // do DOH using GET requests (instead of POST)
new file mode 100644
--- /dev/null
+++ b/netwerk/dns/TRRServiceBase.cpp
@@ -0,0 +1,138 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 : */
+/* 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 "TRRServiceBase.h"
+
+#include "mozilla/Preferences.h"
+#include "nsIOService.h"
+
+namespace mozilla {
+namespace net {
+
+#undef LOG
+extern mozilla::LazyLogModule gHostResolverLog;
+#define LOG(args) MOZ_LOG(gHostResolverLog, mozilla::LogLevel::Debug, args)
+
+TRRServiceBase::TRRServiceBase() : mMode(0) {}
+
+void TRRServiceBase::ProcessURITemplate(nsACString& aURI) {
+  // URI Template, RFC 6570.
+  if (aURI.IsEmpty()) {
+    return;
+  }
+  nsAutoCString scheme;
+  nsCOMPtr<nsIIOService> ios(do_GetIOService());
+  if (ios) {
+    ios->ExtractScheme(aURI, scheme);
+  }
+  if (!scheme.Equals("https")) {
+    LOG(("TRRService TRR URI %s is not https. Not used.\n",
+         PromiseFlatCString(aURI).get()));
+    aURI.Truncate();
+    return;
+  }
+
+  // cut off everything from "{" to "}" sequences (potentially multiple),
+  // as a crude conversion from template into URI.
+  nsAutoCString uri(aURI);
+
+  do {
+    nsCCharSeparatedTokenizer openBrace(uri, '{');
+    if (openBrace.hasMoreTokens()) {
+      // the 'nextToken' is the left side of the open brace (or full uri)
+      nsAutoCString prefix(openBrace.nextToken());
+
+      // if there is an open brace, there's another token
+      const nsACString& endBrace = openBrace.nextToken();
+      nsCCharSeparatedTokenizer closeBrace(endBrace, '}');
+      if (closeBrace.hasMoreTokens()) {
+        // there is a close brace as well, make a URI out of the prefix
+        // and the suffix
+        closeBrace.nextToken();
+        nsAutoCString suffix(closeBrace.nextToken());
+        uri = prefix + suffix;
+      } else {
+        // no (more) close brace
+        break;
+      }
+    } else {
+      // no (more) open brace
+      break;
+    }
+  } while (true);
+
+  aURI = uri;
+}
+
+void TRRServiceBase::CheckURIPrefs() {
+  mURISetByDetection = false;
+
+  // The user has set a custom URI so it takes precedence.
+  if (mURIPrefHasUserValue) {
+    MaybeSetPrivateURI(mURIPref);
+    return;
+  }
+
+  // Check if the rollout addon has set a pref.
+  if (!mRolloutURIPref.IsEmpty()) {
+    MaybeSetPrivateURI(mRolloutURIPref);
+    return;
+  }
+
+  // Otherwise just use the default value.
+  MaybeSetPrivateURI(mURIPref);
+}
+
+// static
+uint32_t ModeFromPrefs() {
+  // 0 - off, 1 - reserved, 2 - TRR first, 3 - TRR only, 4 - reserved,
+  // 5 - explicit off
+
+  auto processPrefValue = [](uint32_t value) -> uint32_t {
+    if (value == MODE_RESERVED1 || value == MODE_RESERVED4 ||
+        value > MODE_TRROFF) {
+      return MODE_TRROFF;
+    }
+    return value;
+  };
+
+  uint32_t tmp = MODE_NATIVEONLY;
+  if (NS_SUCCEEDED(Preferences::GetUint("network.trr.mode", &tmp))) {
+    tmp = processPrefValue(tmp);
+  }
+
+  if (tmp != MODE_NATIVEONLY) {
+    return tmp;
+  }
+
+  if (NS_SUCCEEDED(Preferences::GetUint(kRolloutModePref, &tmp))) {
+    return processPrefValue(tmp);
+  }
+
+  return MODE_NATIVEONLY;
+}
+
+void TRRServiceBase::OnTRRModeChange() {
+  uint32_t oldMode = mMode;
+  mMode = ModeFromPrefs();
+  if (mMode != oldMode) {
+    nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+    if (obs) {
+      obs->NotifyObservers(nullptr, NS_NETWORK_TRR_MODE_CHANGED_TOPIC, nullptr);
+    }
+  }
+}
+
+void TRRServiceBase::OnTRRURIChange() {
+  mURIPrefHasUserValue = Preferences::HasUserValue("network.trr.uri");
+  Preferences::GetCString("network.trr.uri", mURIPref);
+  Preferences::GetCString(kRolloutURIPref, mRolloutURIPref);
+
+  CheckURIPrefs();
+}
+
+}  // namespace net
+}  // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/netwerk/dns/TRRServiceBase.h
@@ -0,0 +1,48 @@
+/* -*- 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/. */
+
+#ifndef TRRServiceBase_h_
+#define TRRServiceBase_h_
+
+#include "mozilla/Atomics.h"
+#include "nsString.h"
+
+namespace mozilla {
+namespace net {
+
+class TRRServiceBase {
+ public:
+  TRRServiceBase();
+  uint32_t Mode() { return mMode; }
+
+ protected:
+  ~TRRServiceBase() = default;
+  virtual bool MaybeSetPrivateURI(const nsACString& aURI) = 0;
+  void ProcessURITemplate(nsACString& aURI);
+  // Checks the network.trr.uri or the doh-rollout.uri prefs and sets the URI
+  // in order of preference:
+  // 1. The value of network.trr.uri if it is not the default one, meaning
+  //    is was set by an explicit user action
+  // 2. The value of doh-rollout.uri if it exists
+  //    this is set by the rollout addon
+  // 3. The default value of network.trr.uri
+  void CheckURIPrefs();
+
+  void OnTRRModeChange();
+  void OnTRRURIChange();
+
+  nsCString mPrivateURI;
+  // Pref caches should only be used on the main thread.
+  bool mURIPrefHasUserValue = false;
+  bool mURISetByDetection = false;
+  nsCString mURIPref;
+  nsCString mRolloutURIPref;
+  Atomic<uint32_t, Relaxed> mMode;
+};
+
+}  // namespace net
+}  // namespace mozilla
+
+#endif  // TRRServiceBase_h_
--- a/netwerk/dns/TRRServiceChild.cpp
+++ b/netwerk/dns/TRRServiceChild.cpp
@@ -65,10 +65,16 @@ mozilla::ipc::IPCResult TRRServiceChild:
 }
 
 mozilla::ipc::IPCResult TRRServiceChild::RecvClearDNSCache(
     const bool& aTrrToo) {
   Unused << sDNSService->ClearCache(aTrrToo);
   return IPC_OK();
 }
 
+mozilla::ipc::IPCResult TRRServiceChild::RecvSetDetectedTrrURI(
+    const nsCString& aURI) {
+  gTRRService->SetDetectedTrrURI(aURI);
+  return IPC_OK();
+}
+
 }  // namespace net
 }  // namespace mozilla
--- a/netwerk/dns/TRRServiceChild.h
+++ b/netwerk/dns/TRRServiceChild.h
@@ -29,16 +29,17 @@ class TRRServiceChild : public PTRRServi
                                              const nsString& aData);
   mozilla::ipc::IPCResult RecvUpdatePlatformDNSInformation(
       nsTArray<nsCString>&& aDNSSuffixList, const bool& aPlatformDisabledTRR);
   mozilla::ipc::IPCResult RecvInitTRRBLStorage(
       const psm::DataStorageEntry& aEntry, const FileDescriptor& aWriteFd);
   mozilla::ipc::IPCResult RecvUpdateParentalControlEnabled(
       const bool& aEnabled);
   mozilla::ipc::IPCResult RecvClearDNSCache(const bool& aTrrToo);
+  mozilla::ipc::IPCResult RecvSetDetectedTrrURI(const nsCString& aURI);
 
  private:
   virtual ~TRRServiceChild() = default;
 };
 
 }  // namespace net
 }  // namespace mozilla
 
--- a/netwerk/dns/TRRServiceParent.cpp
+++ b/netwerk/dns/TRRServiceParent.cpp
@@ -15,16 +15,21 @@
 #include "nsINetworkLinkService.h"
 #include "nsIObserverService.h"
 #include "nsIOService.h"
 #include "TRRService.h"
 
 namespace mozilla {
 namespace net {
 
+static const char* gTRRUriCallbackPrefs[] = {
+    "network.trr.uri", "network.trr.mode", kRolloutURIPref, kRolloutURIPref,
+    nullptr,
+};
+
 NS_IMPL_ISUPPORTS(TRRServiceParent, nsIObserver, nsISupportsWeakReference)
 
 void TRRServiceParent::Init() {
   MOZ_ASSERT(gIOService);
 
   if (!gIOService->SocketProcessReady()) {
     RefPtr<TRRServiceParent> self = this;
     gIOService->CallOrWaitForSocketProcess([self]() { self->Init(); });
@@ -42,16 +47,20 @@ void TRRServiceParent::Init() {
 
   nsCOMPtr<nsINetworkLinkService> nls =
       do_GetService(NS_NETWORK_LINK_SERVICE_CONTRACTID);
   nsTArray<nsCString> suffixList;
   if (nls) {
     nls->GetDnsSuffixList(suffixList);
   }
 
+  Preferences::RegisterPrefixCallbacks(TRRServiceParent::PrefsChanged,
+                                       gTRRUriCallbackPrefs, this);
+  prefsChanged(nullptr);
+
   Unused << socketParent->SendPTRRServiceConstructor(
       this, captiveIsPassed, parentalControlEnabled, suffixList);
 }
 
 NS_IMETHODIMP
 TRRServiceParent::Observe(nsISupports* aSubject, const char* aTopic,
                           const char16_t* aData) {
   if (!strcmp(aTopic, kOpenCaptivePortalLoginEvent)) {
@@ -107,20 +116,83 @@ TRRServiceParent::Observe(nsISupports* a
     // subject, but some unit tests will sometimes pass a null subject.
     if (link) {
       nsTArray<nsCString> suffixList;
       link->GetDnsSuffixList(suffixList);
       bool platformDisabledTRR = TRRService::CheckPlatformDNSStatus(link);
       Unused << SendUpdatePlatformDNSInformation(suffixList,
                                                  platformDisabledTRR);
     }
+
+    if (!strcmp(aTopic, NS_NETWORK_LINK_TOPIC) && mURISetByDetection) {
+      Unused << SendNotifyObserver(
+          nsDependentCString(aTopic),
+          aData ? nsDependentString(aData) : VoidString());
+      CheckURIPrefs();
+    }
   }
 
   return NS_OK;
 }
 
+bool TRRServiceParent::MaybeSetPrivateURI(const nsACString& aURI) {
+  nsAutoCString newURI(aURI);
+  ProcessURITemplate(newURI);
+
+  if (mPrivateURI.Equals(newURI)) {
+    return false;
+  }
+
+  mPrivateURI = newURI;
+
+  nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+  if (obs) {
+    obs->NotifyObservers(nullptr, NS_NETWORK_TRR_URI_CHANGED_TOPIC, nullptr);
+  }
+  return true;
+}
+
+void TRRServiceParent::SetDetectedTrrURI(const nsACString& aURI) {
+  if (mURIPrefHasUserValue) {
+    return;
+  }
+
+  mURISetByDetection = MaybeSetPrivateURI(aURI);
+  RefPtr<TRRServiceParent> self = this;
+  nsCString uri(aURI);
+  gIOService->CallOrWaitForSocketProcess(
+      [self, uri]() { Unused << self->SendSetDetectedTrrURI(uri); });
+}
+
+void TRRServiceParent::GetTrrURI(nsACString& aURI) { aURI = mPrivateURI; }
+
 void TRRServiceParent::UpdateParentalControlEnabled() {
   bool enabled = TRRService::GetParentalControlEnabledInternal();
-  Unused << SendUpdateParentalControlEnabled(enabled);
+  RefPtr<TRRServiceParent> self = this;
+  gIOService->CallOrWaitForSocketProcess([self, enabled]() {
+    Unused << self->SendUpdateParentalControlEnabled(enabled);
+  });
+}
+
+// static
+void TRRServiceParent::PrefsChanged(const char* aName, void* aSelf) {
+  static_cast<TRRServiceParent*>(aSelf)->prefsChanged(aName);
+}
+
+void TRRServiceParent::prefsChanged(const char* aName) {
+  if (!aName || !strcmp(aName, "network.trr.uri") ||
+      !strcmp(aName, kRolloutURIPref)) {
+    OnTRRURIChange();
+  }
+
+  if (!aName || !strcmp(aName, "network.trr.mode") ||
+      !strcmp(aName, kRolloutModePref)) {
+    OnTRRModeChange();
+  }
+}
+
+void TRRServiceParent::ActorDestroy(ActorDestroyReason why) {
+  Preferences::UnregisterPrefixCallbacks(TRRServiceParent::PrefsChanged,
+                                         gTRRUriCallbackPrefs, this);
 }
 
 }  // namespace net
 }  // namespace mozilla
--- a/netwerk/dns/TRRServiceParent.h
+++ b/netwerk/dns/TRRServiceParent.h
@@ -2,37 +2,44 @@
 /* vim: set sw=2 ts=8 et tw=80 : */
 /* 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 mozilla_net_TRRServiceParent_h
 #define mozilla_net_TRRServiceParent_h
 
-#include "mozilla/DataStorage.h"
 #include "mozilla/net/PTRRServiceParent.h"
+#include "mozilla/net/TRRServiceBase.h"
 #include "nsIObserver.h"
 #include "nsWeakReference.h"
 
 namespace mozilla {
 namespace net {
 
-class TRRServiceParent : public nsIObserver,
+class TRRServiceParent : public TRRServiceBase,
+                         public nsIObserver,
                          public nsSupportsWeakReference,
                          public PTRRServiceParent {
  public:
   NS_DECL_ISUPPORTS
   NS_DECL_NSIOBSERVER
 
   explicit TRRServiceParent() : mTRRBLStorageInited(false) {}
   void Init();
   void UpdateParentalControlEnabled();
+  static void PrefsChanged(const char* aName, void* aSelf);
+  void SetDetectedTrrURI(const nsACString& aURI);
+  bool MaybeSetPrivateURI(const nsACString& aURI) override;
+  void GetTrrURI(nsACString& aURI);
 
  private:
   virtual ~TRRServiceParent() = default;
+  virtual void ActorDestroy(ActorDestroyReason why) override;
+  void prefsChanged(const char* aName);
 
   bool mTRRBLStorageInited;
 };
 
 }  // namespace net
 }  // namespace mozilla
 
 #endif  // mozilla_net_TRRServiceParent_h
--- a/netwerk/dns/moz.build
+++ b/netwerk/dns/moz.build
@@ -41,16 +41,17 @@ EXPORTS.mozilla.net += [
     'DNSByTypeRecord.h',
     'DNSListenerProxy.h',
     'DNSRequestBase.h',
     'DNSRequestChild.h',
     'DNSRequestParent.h',
     'HTTPSSVC.h',
     'IDNBlocklistUtils.h',
     'TRRService.h',
+    'TRRServiceBase.h',
     'TRRServiceChild.h',
     'TRRServiceParent.h',
 ]
 
 SOURCES += [
     'nsEffectiveTLDService.cpp', # Excluded from UNIFIED_SOURCES due to special build flags.
     'nsHostResolver.cpp', # Redefines LOG
 ]
@@ -64,16 +65,17 @@ UNIFIED_SOURCES += [
     'GetAddrInfo.cpp',
     'HTTPSSVC.cpp',
     'IDNBlocklistUtils.cpp',
     'nsDNSService2.cpp',
     'nsIDNService.cpp',
     'punycode.c',
     'TRR.cpp',
     'TRRService.cpp',
+    'TRRServiceBase.cpp',
     'TRRServiceChild.cpp',
     'TRRServiceParent.cpp',
 ]
 
 IPDL_SOURCES = [
     'PDNSRequest.ipdl',
     'PDNSRequestParams.ipdlh',
     'PTRRService.ipdl',