Bug 1631609 - Steer to network-indicated DoH endpoint if detected. r=valentin,dragana,NhiNguyen,johannh
☠☠ backed out by 835bd05cf135 ☠ ☠
authorNihanth Subramanya <nhnt11@gmail.com>
Thu, 04 Jun 2020 19:05:37 +0000
changeset 533964 a72c393582367227a55b1f25e53bcca369f773d4
parent 533963 fc754ed98c417ae5931e0a438487c52dea77d388
child 533965 b40a0f8c92b421366c100f42baac5587ecb27b88
push id37481
push userncsoregi@mozilla.com
push dateFri, 05 Jun 2020 04:39:26 +0000
treeherdermozilla-central@fecffba489bd [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersvalentin, dragana, NhiNguyen, johannh
bugs1631609
milestone79.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 1631609 - Steer to network-indicated DoH endpoint if detected. r=valentin,dragana,NhiNguyen,johannh Differential Revision: https://phabricator.services.mozilla.com/D76414
browser/extensions/doh-rollout/experiments/heuristics/api.js
browser/extensions/doh-rollout/experiments/heuristics/schema.json
browser/extensions/doh-rollout/experiments/preferences/schema.json
browser/extensions/doh-rollout/heuristics.js
--- a/browser/extensions/doh-rollout/experiments/heuristics/api.js
+++ b/browser/extensions/doh-rollout/experiments/heuristics/api.js
@@ -26,16 +26,17 @@ const HEURISTICS_TELEMETRY_EVENTS = {
       "google",
       "youtube",
       "zscalerCanary",
       "canary",
       "modifiedRoots",
       "browserParent",
       "thirdPartyRoots",
       "policy",
+      "steeredProvider",
       "evaluateReason",
     ],
     record_on_release: true,
   },
   state: {
     methods: ["state"],
     objects: [
       "loaded",
--- a/browser/extensions/doh-rollout/experiments/heuristics/schema.json
+++ b/browser/extensions/doh-rollout/experiments/heuristics/schema.json
@@ -57,16 +57,20 @@
               "thirdPartyRoots": {
                 "description": "Indicates whether third-party roots are enabled",
                 "type": "string"
               },
               "policy": {
                 "description": "Indicates whether browser policy blocks DoH",
                 "type": "string"
               },
+              "steeredProvider": {
+                "description": "Indicates whether we steered to a provider-endpoint. Value is the name of the provider",
+                "type": "string"
+              },
               "evaluateReason": {
                 "description": "Reason why we are running heuristics, e.g. startup",
                 "type": "string"
               }
             }
           }
         ],
         "async": false
--- a/browser/extensions/doh-rollout/experiments/preferences/schema.json
+++ b/browser/extensions/doh-rollout/experiments/preferences/schema.json
@@ -77,17 +77,17 @@
       },
       {
         "name": "getCharPref",
         "type": "function",
         "description": "Gets the value of a string preference",
         "parameters": [
           {
             "type": "string",
-            "enum": ["doh-rollout.doorhanger-decision", "doh-rollout.heuristics.mockValues"]
+            "enum": ["doh-rollout.doorhanger-decision", "doh-rollout.heuristics.mockValues", "doh-rollout.provider-steering.provider-list"]
           },
           {
             "type": "string",
             "name": "defaultValue"
           }
         ],
         "async": true
       },
--- a/browser/extensions/doh-rollout/heuristics.js
+++ b/browser/extensions/doh-rollout/heuristics.js
@@ -6,29 +6,34 @@
 
 /* global browser */
 /* exported runHeuristics */
 
 const GLOBAL_CANARY = "use-application-dns.net";
 
 const NXDOMAIN_ERR = "NS_ERROR_UNKNOWN_HOST";
 
-async function dnsLookup(hostname) {
+async function dnsLookup(hostname, resolveCanonicalName = false) {
   let flags = ["disable_trr", "disable_ipv6", "bypass_cache"];
-  let addresses, err;
+  if (resolveCanonicalName) {
+    flags.push("canonical_name");
+  }
+
+  let addresses, canonicalName, err;
 
   try {
     let response = await browser.dns.resolve(hostname, flags);
     addresses = response.addresses;
+    canonicalName = response.canonicalName;
   } catch (e) {
     addresses = [null];
     err = e.message;
   }
 
-  return { addresses, err };
+  return { addresses, canonicalName, err };
 }
 
 async function dnsListLookup(domainList) {
   let results = [];
 
   for (let domain of domainList) {
     let { addresses } = await dnsLookup(domain);
     results = results.concat(addresses);
@@ -116,31 +121,91 @@ async function modifiedRoots() {
 
   if (rootsEnabled) {
     return "disable_doh";
   }
 
   return "enable_doh";
 }
 
-async function runHeuristics() {
-  let safeSearchChecks = await safeSearch();
-  let zscalerCheck = await zscalerCanary();
-  let canaryCheck = await globalCanary();
-  let modifiedRootsCheck = await modifiedRoots();
+// Check if the network provides a DoH endpoint to use. Returns the name of the
+// provider if the check is successful, else null.
+async function providerSteering() {
+  if (
+    !(await browser.experiments.preferences.getBoolPref(
+      "doh-rollout.provider-steering.enabled",
+      false
+    ))
+  ) {
+    return null;
+  }
+  const TEST_DOMAIN = "doh.test";
 
-  // Check other heuristics through privileged code
-  let browserParentCheck = await browser.experiments.heuristics.checkParentalControls();
-  let enterpriseCheck = await browser.experiments.heuristics.checkEnterprisePolicies();
-  let thirdPartyRootsCheck = await browser.experiments.heuristics.checkThirdPartyRoots();
+  // Array of { name, canonicalName, uri } where name is an identifier for
+  // telemetry, canonicalName is the expected CNAME when looking up doh.test,
+  // and uri is the provider's DoH endpoint.
+  let steeredProviders = await browser.experiments.preferences.getCharPref(
+    "doh-rollout.provider-steering.provider-list",
+    "[]"
+  );
+  try {
+    steeredProviders = JSON.parse(steeredProviders);
+  } catch (e) {
+    console.log("Provider list is invalid JSON, moving on.");
+    return null;
+  }
+
+  if (!steeredProviders || !steeredProviders.length) {
+    return null;
+  }
+
+  let { canonicalName, err } = await dnsLookup(TEST_DOMAIN, true);
+  if (err || !canonicalName) {
+    return null;
+  }
 
-  // Return result of each heuristic
-  return {
-    google: safeSearchChecks.google,
-    youtube: safeSearchChecks.youtube,
-    zscalerCanary: zscalerCheck,
-    canary: canaryCheck,
-    modifiedRoots: modifiedRootsCheck,
-    browserParent: browserParentCheck,
-    thirdPartyRoots: thirdPartyRootsCheck,
-    policy: enterpriseCheck,
+  let provider = steeredProviders.find(p => {
+    return p && p.canonicalName == canonicalName;
+  });
+  if (!provider || !provider.uri || !provider.name) {
+    return null;
+  }
+
+  // We handle this here instead of background.js since we need to set this
+  // override every time we run heuristics.
+  browser.experiments.heuristics.setDetectedTrrURI(provider.uri);
+
+  return provider.name;
+}
+
+async function runHeuristics() {
+  // First run enterprise and OS-level parental controls heuristics.
+  let results = {
+    google: "",
+    youtube: "",
+    zscalerCanary: "",
+    canary: await globalCanary(),
+    modifiedRoots: await modifiedRoots(),
+    browserParent: await browser.experiments.heuristics.checkParentalControls(),
+    thirdPartyRoots: await browser.experiments.heuristics.checkThirdPartyRoots(),
+    policy: await browser.experiments.heuristics.checkEnterprisePolicies(),
+    steeredProvider: "",
   };
+
+  // If any of those were triggered, return the results immediately.
+  if (Object.values(results).includes("disable_doh")) {
+    return results;
+  }
+
+  // Check for provider steering, return results immediately if triggered.
+  results.steeredProvider = (await providerSteering()) || "";
+  if (results.steeredProvider) {
+    return results;
+  }
+
+  // Finally, run safe search checks and zscaler canary.
+  let safeSearchChecks = await safeSearch();
+  results.google = safeSearchChecks.google;
+  results.youtube = safeSearchChecks.youtube;
+  results.zscalerCanary = await zscalerCanary();
+
+  return results;
 }