Bug 1460537 - Connectivity Service - Add DNSv4 and DNSv6 checks r=dragana
☠☠ backed out by f5a40dea5902 ☠ ☠
authorValentin Gosu <valentin.gosu@gmail.com>
Thu, 25 Oct 2018 15:48:51 +0000
changeset 491347 bd3f7151c697162af1a815a6b8cea81a94e45279
parent 491346 7ba779ebed434ca3c839e152c2c2b6efb7a45b6a
child 491348 ae2b0a2cd8a752d2e0783baaf7be8a1d69ea385c
push id247
push userfmarier@mozilla.com
push dateSat, 27 Oct 2018 01:06:44 +0000
reviewersdragana
bugs1460537
milestone65.0a1
Bug 1460537 - Connectivity Service - Add DNSv4 and DNSv6 checks r=dragana Differential Revision: https://phabricator.services.mozilla.com/D7844
modules/libpref/init/all.js
netwerk/base/NetworkConnectivityService.cpp
netwerk/base/NetworkConnectivityService.h
netwerk/base/RedirectChannelRegistrar.cpp
netwerk/base/moz.build
netwerk/base/nsINetworkConnectivityService.idl
netwerk/base/nsIOService.cpp
netwerk/build/nsNetCID.h
netwerk/build/nsNetModule.cpp
netwerk/test/unit/test_network_connectivity_service.js
netwerk/test/unit/xpcshell.ini
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -5431,16 +5431,20 @@ pref("memory_info_dumper.watch_fifo.enab
 // If minInterval is 0, the check will only happen
 // when the service has a strong suspicion we are in a captive portal
 pref("network.captive-portal-service.minInterval", 60000); // 60 seconds
 pref("network.captive-portal-service.maxInterval", 1500000); // 25 minutes
 // Every 10 checks, the delay is increased by a factor of 5
 pref("network.captive-portal-service.backoffFactor", "5.0");
 pref("network.captive-portal-service.enabled", false);
 
+pref("network.connectivity-service.enabled", true);
+pref("network.connectivity-service.DNSv4.domain", "mozilla.org");
+pref("network.connectivity-service.DNSv6.domain", "mozilla.org");
+
 // DNS Trusted Recursive Resolver
 // 0 - default off, 1 - race, 2 TRR first, 3 TRR only, 4 shadow, 5 off by choice
 pref("network.trr.mode", 0);
 // DNS-over-HTTP service to use, must be HTTPS://
 pref("network.trr.uri", "https://mozilla.cloudflare-dns.com/dns-query");
 // credentials to pass to DOH end-point
 pref("network.trr.credentials", "");
 // Wait for captive portal confirmation before enabling TRR
new file mode 100644
--- /dev/null
+++ b/netwerk/base/NetworkConnectivityService.cpp
@@ -0,0 +1,157 @@
+/* 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 "NetworkConnectivityService.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/Services.h"
+#include "xpcpublic.h"
+
+namespace mozilla {
+namespace net {
+
+NS_IMPL_ISUPPORTS(NetworkConnectivityService, nsINetworkConnectivityService, nsIObserver, nsIDNSListener)
+
+static StaticRefPtr<NetworkConnectivityService> gConnService;
+
+// static
+already_AddRefed<NetworkConnectivityService>
+NetworkConnectivityService::GetSingleton()
+{
+  if (gConnService) {
+    return do_AddRef(gConnService);
+  }
+
+  RefPtr<NetworkConnectivityService> service = new NetworkConnectivityService();
+  service->Init();
+
+  gConnService = service.forget();
+  ClearOnShutdown(&gConnService);
+  return do_AddRef(gConnService);
+}
+
+nsresult
+NetworkConnectivityService::Init()
+{
+  nsCOMPtr<nsIObserverService> observerService =
+    mozilla::services::GetObserverService();
+  observerService->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false);
+  observerService->AddObserver(this, "network:captive-portal-connectivity", false);
+
+  // We need to schedule this for a bit later, to avoid a recursive service
+  // initialization.
+  MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(
+    NewRunnableMethod("NetworkConnectivityService::PerformChecks",
+                      this,
+                      &NetworkConnectivityService::PerformChecks)));
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+NetworkConnectivityService::GetDNSv4(int32_t *aState)
+{
+  NS_ENSURE_ARG(aState);
+  *aState = mDNSv4;
+  return NS_OK;
+}
+
+
+NS_IMETHODIMP
+NetworkConnectivityService::GetDNSv6(int32_t *aState)
+{
+  NS_ENSURE_ARG(aState);
+  *aState = mDNSv6;
+  return NS_OK;
+}
+
+void
+NetworkConnectivityService::PerformChecks()
+{
+  RecheckDNS();
+}
+
+NS_IMETHODIMP
+NetworkConnectivityService::OnLookupComplete(nsICancelable *aRequest,
+                                             nsIDNSRecord *aRecord,
+                                             nsresult aStatus)
+{
+  int32_t state = aRecord ? nsINetworkConnectivityService::OK
+                          : nsINetworkConnectivityService::NOT_AVAILABLE;
+
+  if (aRequest == mDNSv4Request) {
+    mDNSv4 = state;
+    mDNSv4Request = nullptr;
+  } else if (aRequest == mDNSv6Request) {
+    mDNSv6 = state;
+    mDNSv6Request = nullptr;
+  }
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+NetworkConnectivityService::OnLookupByTypeComplete(nsICancelable *aRequest,
+                                                   nsIDNSByTypeRecord *aRes,
+                                                   nsresult aStatus)
+{
+    return NS_OK;
+}
+
+
+NS_IMETHODIMP
+NetworkConnectivityService::RecheckDNS()
+{
+  bool enabled = Preferences::GetBool("network.connectivity-service.enabled", false);
+  if (!enabled) {
+    return NS_OK;
+  }
+
+  nsresult rv;
+  nsCOMPtr<nsIDNSService> dns = do_GetService(NS_DNSSERVICE_CONTRACTID);
+  OriginAttributes attrs;
+  nsAutoCString host;
+  Preferences::GetCString("network.connectivity-service.DNSv4.domain", host);
+
+  rv = dns->AsyncResolveNative(host, nsIDNSService::RESOLVE_DISABLE_IPV6,
+                               this, NS_GetCurrentThread(),
+                               attrs, getter_AddRefs(mDNSv4Request));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  Preferences::GetCString("network.connectivity-service.DNSv6.domain", host);
+  rv = dns->AsyncResolveNative(host, nsIDNSService::RESOLVE_DISABLE_IPV4,
+                               this, NS_GetCurrentThread(),
+                               attrs, getter_AddRefs(mDNSv6Request));
+  return rv;
+}
+
+NS_IMETHODIMP
+NetworkConnectivityService::Observe(nsISupports *aSubject,
+                                    const char * aTopic,
+                                    const char16_t * aData)
+{
+  if (!strcmp(aTopic, "network:captive-portal-connectivity")) {
+    // Captive portal is cleared, so we redo the checks.
+    mDNSv4 = nsINetworkConnectivityService::UNKNOWN;
+    mDNSv6 = nsINetworkConnectivityService::UNKNOWN;
+
+    PerformChecks();
+  } else if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) {
+    if (mDNSv4Request) {
+      mDNSv4Request->Cancel(NS_ERROR_ABORT);
+      mDNSv4Request = nullptr;
+    }
+    if (mDNSv6Request) {
+      mDNSv6Request->Cancel(NS_ERROR_ABORT);
+      mDNSv6Request = nullptr;
+    }
+
+    nsCOMPtr<nsIObserverService> observerService =
+      mozilla::services::GetObserverService();
+    observerService->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID);
+    observerService->RemoveObserver(this, "network:captive-portal-connectivity");
+  }
+
+  return NS_OK;
+}
+
+} // namespace net
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/netwerk/base/NetworkConnectivityService.h
@@ -0,0 +1,47 @@
+/* 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 NetworkConnectivityService_h_
+#define NetworkConnectivityService_h_
+
+#include "nsINetworkConnectivityService.h"
+
+namespace mozilla {
+namespace net {
+
+class NetworkConnectivityService
+  : public nsINetworkConnectivityService
+  , public nsIObserver
+  , public nsIDNSListener
+{
+public:
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSINETWORKCONNECTIVITYSERVICE
+  NS_DECL_NSIOBSERVER
+  NS_DECL_NSIDNSLISTENER
+
+  nsresult Init();
+  static already_AddRefed<NetworkConnectivityService> GetSingleton();
+
+private:
+  NetworkConnectivityService() = default;
+  virtual ~NetworkConnectivityService() = default;
+
+  // Calls all the check methods
+  void PerformChecks();
+
+  // Will be set to OK if the DNS request returned in IP of this type,
+  //                NOT_AVAILABLE if that type of resolution is not available
+  //                UNKNOWN if the check wasn't performed
+  int32_t mDNSv4 = nsINetworkConnectivityService::UNKNOWN;
+  int32_t mDNSv6 = nsINetworkConnectivityService::UNKNOWN;
+
+  nsCOMPtr<nsICancelable> mDNSv4Request;
+  nsCOMPtr<nsICancelable> mDNSv6Request;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // NetworkConnectivityService_h_
--- a/netwerk/base/RedirectChannelRegistrar.cpp
+++ b/netwerk/base/RedirectChannelRegistrar.cpp
@@ -1,14 +1,15 @@
 /* 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 "RedirectChannelRegistrar.h"
 #include "mozilla/StaticPtr.h"
+#include "nsThreadUtils.h"
 
 namespace mozilla {
 namespace net {
 
 namespace {
 StaticRefPtr<RedirectChannelRegistrar> gSingleton;
 }
 
--- a/netwerk/base/moz.build
+++ b/netwerk/base/moz.build
@@ -58,16 +58,17 @@ XPIDL_SOURCES += [
     'nsILoadGroup.idl',
     'nsILoadGroupChild.idl',
     'nsILoadInfo.idl',
     'nsIMIMEInputStream.idl',
     'nsIMultiPartChannel.idl',
     'nsINestedURI.idl',
     'nsINetAddr.idl',
     'nsINetUtil.idl',
+    'nsINetworkConnectivityService.idl',
     'nsINetworkInfoService.idl',
     'nsINetworkInterceptController.idl',
     'nsINetworkLinkService.idl',
     'nsINetworkPredictor.idl',
     'nsINetworkPredictorVerifier.idl',
     'nsINetworkProperties.idl',
     'nsINullChannel.idl',
     'nsIParentChannel.idl',
@@ -168,16 +169,17 @@ EXPORTS.mozilla += [
 EXPORTS.mozilla.net += [
     'CaptivePortalService.h',
     'ChannelDiverterChild.h',
     'ChannelDiverterParent.h',
     'Dashboard.h',
     'DashboardTypes.h',
     'IOActivityMonitor.h',
     'MemoryDownloader.h',
+    'NetworkConnectivityService.h',
     'PartiallySeekableInputStream.h',
     'Predictor.h',
     'RedirectChannelRegistrar.h',
     'ReferrerPolicy.h',
     'RequestContextService.h',
     'SimpleChannelParent.h',
     'TCPFastOpen.h',
 ]
@@ -189,16 +191,17 @@ UNIFIED_SOURCES += [
     'ChannelDiverterChild.cpp',
     'ChannelDiverterParent.cpp',
     'Dashboard.cpp',
     'EventTokenBucket.cpp',
     'IOActivityMonitor.cpp',
     'LoadContextInfo.cpp',
     'LoadInfo.cpp',
     'MemoryDownloader.cpp',
+    'NetworkConnectivityService.cpp',
     'nsAsyncRedirectVerifyHelper.cpp',
     'nsAsyncStreamCopier.cpp',
     'nsAuthInformationHolder.cpp',
     'nsBase64Encoder.cpp',
     'nsBaseChannel.cpp',
     'nsBaseContentStream.cpp',
     'nsBufferedStreams.cpp',
     'nsChannelClassifier.cpp',
new file mode 100644
--- /dev/null
+++ b/netwerk/base/nsINetworkConnectivityService.idl
@@ -0,0 +1,27 @@
+/* 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 "nsISupports.idl"
+
+[scriptable, builtinclass, uuid(2693457e-3ba5-4455-991f-5350946adb12)]
+interface nsINetworkConnectivityService : nsISupports
+{
+  /**
+   * Each tested feature may be in one of 3 states:
+   *   UNKNOWN, if a check hasn't been performed.
+   *   OK, if the feature was successfully tested
+   *   NOT_AVAILABLE, if the feature is blocked by the network.
+   *                  Note that the endpoints are guaranteed to support the features.
+   */
+  const long UNKNOWN             = 0;
+  const long OK                  = 1;
+  const long NOT_AVAILABLE       = 2;
+
+  /* If DNS v4/v6 queries actually work on the current network */
+  readonly attribute long DNSv4;
+  readonly attribute long DNSv6;
+
+  /* Starts the DNS request to check for DNS v4/v6 availability */
+  void recheckDNS();
+};
--- a/netwerk/base/nsIOService.cpp
+++ b/netwerk/base/nsIOService.cpp
@@ -48,16 +48,17 @@
 #include "mozilla/net/DNS.h"
 #include "mozilla/ipc/URIUtils.h"
 #include "mozilla/net/NeckoChild.h"
 #include "mozilla/net/NeckoParent.h"
 #include "mozilla/dom/ClientInfo.h"
 #include "mozilla/dom/ContentParent.h"
 #include "mozilla/dom/ServiceWorkerDescriptor.h"
 #include "mozilla/net/CaptivePortalService.h"
+#include "mozilla/net/NetworkConnectivityService.h"
 #include "mozilla/Unused.h"
 #include "ReferrerPolicy.h"
 #include "nsContentSecurityManager.h"
 #include "nsContentUtils.h"
 
 namespace mozilla {
 namespace net {
 
@@ -257,16 +258,20 @@ nsIOService::Init()
 
     gIOService = this;
 
     InitializeNetworkLinkService();
     InitializeProtocolProxyService();
 
     SetOffline(false);
 
+    RefPtr<NetworkConnectivityService> ncs =
+      NetworkConnectivityService::GetSingleton();
+    ncs->Init();
+
     return NS_OK;
 }
 
 
 nsIOService::~nsIOService()
 {
     if (gIOService) {
         MOZ_ASSERT(gIOService == this);
--- a/netwerk/build/nsNetCID.h
+++ b/netwerk/build/nsNetCID.h
@@ -447,16 +447,26 @@
 #define NS_CAPTIVEPORTAL_CID \
 { /* bdbe0555-fc3d-4f7b-9205-c309ceb2d641 */ \
     0xbdbe0555, \
     0xfc3d, \
     0x4f7b, \
   { 0x92, 0x05, 0xc3, 0x09, 0xce, 0xb2, 0xd6, 0x41 } \
 }
 
+#define NS_NETWORKCONNECTIVITYSERVICE_CONTRACTID \
+    "@mozilla.org/network/network-connectivity-service;1"
+#define NS_NETWORKCONNECTIVITYSERVICE_CID \
+{ /* 2693457e-3ba5-4455-991f-5350946adb12 */ \
+    0x2693457e, \
+    0x3ba5, \
+    0x4455, \
+  { 0x99, 0x1f, 0x53, 0x50, 0x94, 0x6a, 0xdb, 0x12 } \
+}
+
 /******************************************************************************
  * netwerk/cache/ classes
  */
 
 // service implementing nsICacheService.
 #define NS_CACHESERVICE_CONTRACTID \
     "@mozilla.org/network/cache-service;1"
 #define NS_CACHESERVICE_CID                          \
--- a/netwerk/build/nsNetModule.cpp
+++ b/netwerk/build/nsNetModule.cpp
@@ -136,16 +136,24 @@ NS_GENERIC_FACTORY_CONSTRUCTOR(LoadConte
 #include "mozilla/net/CaptivePortalService.h"
 namespace mozilla {
 namespace net {
   NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(nsICaptivePortalService,
     CaptivePortalService::GetSingleton)
 } // namespace net
 } // namespace mozilla
 
+#include "mozilla/net/NetworkConnectivityService.h"
+namespace mozilla {
+namespace net {
+  NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(nsINetworkConnectivityService,
+    NetworkConnectivityService::GetSingleton)
+} // namespace net
+} // namespace mozilla
+
 ///////////////////////////////////////////////////////////////////////////////
 
 extern nsresult
 net_NewIncrementalDownload(nsISupports *, const nsIID &, void **);
 
 #define NS_INCREMENTALDOWNLOAD_CID \
 { /* a62af1ba-79b3-4896-8aaf-b148bfce4280 */         \
     0xa62af1ba,                                      \
@@ -717,16 +725,17 @@ NS_DEFINE_NAMED_CID(NS_NETWORK_LINK_SERV
 #elif defined(XP_LINUX)
 NS_DEFINE_NAMED_CID(NS_NETWORK_LINK_SERVICE_CID);
 #endif
 NS_DEFINE_NAMED_CID(NS_SERIALIZATION_HELPER_CID);
 NS_DEFINE_NAMED_CID(NS_CACHE_STORAGE_SERVICE_CID);
 NS_DEFINE_NAMED_CID(NS_NSILOADCONTEXTINFOFACTORY_CID);
 NS_DEFINE_NAMED_CID(NS_NETWORKPREDICTOR_CID);
 NS_DEFINE_NAMED_CID(NS_CAPTIVEPORTAL_CID);
+NS_DEFINE_NAMED_CID(NS_NETWORKCONNECTIVITYSERVICE_CID);
 #ifdef BUILD_NETWORK_INFO_SERVICE
 NS_DEFINE_NAMED_CID(NETWORKINFOSERVICE_CID);
 #endif // BUILD_NETWORK_INFO_SERVICE
 
 static const mozilla::Module::CIDEntry kNeckoCIDs[] = {
     // clang-format off
     { &kNS_IOSERVICE_CID, false, nullptr, nsIOServiceConstructor },
     { &kNS_STREAMTRANSPORTSERVICE_CID, false, nullptr, nsStreamTransportServiceConstructor },
@@ -825,16 +834,17 @@ static const mozilla::Module::CIDEntry k
 #elif defined(XP_LINUX)
     { &kNS_NETWORK_LINK_SERVICE_CID, false, nullptr, nsNotifyAddrListenerConstructor },
 #endif
     { &kNS_SERIALIZATION_HELPER_CID, false, nullptr, nsSerializationHelperConstructor },
     { &kNS_CACHE_STORAGE_SERVICE_CID, false, nullptr, CacheStorageServiceConstructor },
     { &kNS_NSILOADCONTEXTINFOFACTORY_CID, false, nullptr, LoadContextInfoFactoryConstructor },
     { &kNS_NETWORKPREDICTOR_CID, false, nullptr, mozilla::net::Predictor::Create },
     { &kNS_CAPTIVEPORTAL_CID, false, nullptr, mozilla::net::nsICaptivePortalServiceConstructor },
+    { &kNS_NETWORKCONNECTIVITYSERVICE_CID, false, nullptr, mozilla::net::nsINetworkConnectivityServiceConstructor },
 #ifdef BUILD_NETWORK_INFO_SERVICE
     { &kNETWORKINFOSERVICE_CID, false, nullptr, nsNetworkInfoServiceConstructor },
 #endif
     { nullptr }
     // clang-format on
 };
 
 static const mozilla::Module::ContractIDEntry kNeckoContracts[] = {
@@ -933,16 +943,17 @@ static const mozilla::Module::ContractID
     { NS_NETWORK_LINK_SERVICE_CONTRACTID, &kNS_NETWORK_LINK_SERVICE_CID },
 #endif
     { NS_SERIALIZATION_HELPER_CONTRACTID, &kNS_SERIALIZATION_HELPER_CID },
     { NS_CACHE_STORAGE_SERVICE_CONTRACTID, &kNS_CACHE_STORAGE_SERVICE_CID },
     { NS_CACHE_STORAGE_SERVICE_CONTRACTID2, &kNS_CACHE_STORAGE_SERVICE_CID },
     { NS_NSILOADCONTEXTINFOFACTORY_CONTRACTID, &kNS_NSILOADCONTEXTINFOFACTORY_CID },
     { NS_NETWORKPREDICTOR_CONTRACTID, &kNS_NETWORKPREDICTOR_CID },
     { NS_CAPTIVEPORTAL_CONTRACTID, &kNS_CAPTIVEPORTAL_CID },
+    { NS_NETWORKCONNECTIVITYSERVICE_CONTRACTID, &kNS_NETWORKCONNECTIVITYSERVICE_CID },
 #ifdef BUILD_NETWORK_INFO_SERVICE
     { NETWORKINFOSERVICE_CONTRACT_ID, &kNETWORKINFOSERVICE_CID },
 #endif
     { nullptr }
     // clang-format on
 };
 
 static const mozilla::Module kNeckoModule = {
new file mode 100644
--- /dev/null
+++ b/netwerk/test/unit/test_network_connectivity_service.js
@@ -0,0 +1,53 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+"use strict";
+
+ChromeUtils.import("resource://testing-common/httpd.js");
+ChromeUtils.import("resource://gre/modules/NetUtil.jsm");
+ChromeUtils.import("resource://gre/modules/Services.jsm");
+
+registerCleanupFunction(() => {
+  Services.prefs.clearUserPref("network.connectivity-service.DNSv4.domain");
+  Services.prefs.clearUserPref("network.connectivity-service.DNSv6.domain");
+});
+
+const DEFAULT_WAIT_TIME = 200; // ms
+
+add_task(async function testDNS() {
+  let ncs = Cc["@mozilla.org/network/network-connectivity-service;1"]
+              .getService(Ci.nsINetworkConnectivityService);
+
+  // Set the endpoints, trigger a DNS recheck, and wait for it to complete.
+  Services.prefs.setCharPref("network.connectivity-service.DNSv4.domain", "example.org");
+  Services.prefs.setCharPref("network.connectivity-service.DNSv6.domain", "example.org");
+  ncs.recheckDNS();
+  await new Promise(resolve => do_timeout(DEFAULT_WAIT_TIME, resolve));
+
+  equal(ncs.DNSv4, Ci.nsINetworkConnectivityService.OK, "Check DNSv4 support (expect OK)");
+  equal(ncs.DNSv6, Ci.nsINetworkConnectivityService.OK, "Check DNSv6 support (expect OK)");
+
+  // Set the endpoints to non-exitant domains, trigger a DNS recheck, and wait for it to complete.
+  Services.prefs.setCharPref("network.connectivity-service.DNSv4.domain", "does-not-exist.example");
+  Services.prefs.setCharPref("network.connectivity-service.DNSv6.domain", "does-not-exist.example");
+  ncs.recheckDNS();
+  await new Promise(resolve => do_timeout(DEFAULT_WAIT_TIME, resolve));
+
+  equal(ncs.DNSv4, Ci.nsINetworkConnectivityService.NOT_AVAILABLE, "Check DNSv4 support (expect N/A)");
+  equal(ncs.DNSv6, Ci.nsINetworkConnectivityService.NOT_AVAILABLE, "Check DNSv6 support (expect N/A)");
+
+  // Set the endpoints back to the proper domains, and simulate a captive portal
+  // event.
+  Services.prefs.setCharPref("network.connectivity-service.DNSv4.domain", "example.org");
+  Services.prefs.setCharPref("network.connectivity-service.DNSv6.domain", "example.org");
+  Services.obs.notifyObservers(null, "network:captive-portal-connectivity", null);
+  // This will cause the state to go to UNKNOWN for a bit, until the check is completed.
+  equal(ncs.DNSv4, Ci.nsINetworkConnectivityService.UNKNOWN, "Check DNSv4 support (expect UNKNOWN)");
+  equal(ncs.DNSv6, Ci.nsINetworkConnectivityService.UNKNOWN, "Check DNSv6 support (expect UNKNOWN)");
+
+  await new Promise(resolve => do_timeout(DEFAULT_WAIT_TIME, resolve));
+
+  equal(ncs.DNSv4, Ci.nsINetworkConnectivityService.OK, "Check DNSv4 support (expect OK)");
+  equal(ncs.DNSv6, Ci.nsINetworkConnectivityService.OK, "Check DNSv6 support (expect OK)");
+});
--- a/netwerk/test/unit/xpcshell.ini
+++ b/netwerk/test/unit/xpcshell.ini
@@ -417,8 +417,9 @@ skip-if = os == "android"
 [test_ioservice.js]
 [test_substituting_protocol_handler.js]
 [test_captive_portal_service.js]
 skip-if = os == "android" # CP service is disabled on Android
 run-sequentially = node server exceptions dont replay well
 [test_esni_dns_fetch.js]
 # http2-using tests require node available
 skip-if = os == "android"
+[test_network_connectivity_service.js]