summaryrefslogtreecommitdiffstats
path: root/netwerk/test/unit/test_trr.js
diff options
context:
space:
mode:
Diffstat (limited to 'netwerk/test/unit/test_trr.js')
-rw-r--r--netwerk/test/unit/test_trr.js902
1 files changed, 902 insertions, 0 deletions
diff --git a/netwerk/test/unit/test_trr.js b/netwerk/test/unit/test_trr.js
new file mode 100644
index 0000000000..1fcba44f86
--- /dev/null
+++ b/netwerk/test/unit/test_trr.js
@@ -0,0 +1,902 @@
+"use strict";
+
+/* import-globals-from trr_common.js */
+
+const gDefaultPref = Services.prefs.getDefaultBranch("");
+
+SetParentalControlEnabled(false);
+
+function setup() {
+ h2Port = trr_test_setup();
+}
+
+setup();
+registerCleanupFunction(async () => {
+ trr_clear_prefs();
+});
+
+async function waitForConfirmation(expectedResponseIP, confirmationShouldFail) {
+ // Check that the confirmation eventually completes.
+ let count = 100;
+ while (count > 0) {
+ if (count == 50 || count == 10) {
+ // At these two points we do a longer timeout to account for a slow
+ // response on the server side. This is usually a problem on the Android
+ // because of the increased delay between the emulator and host.
+ await new Promise(resolve => do_timeout(100 * (100 / count), resolve));
+ }
+ let { inRecord } = await new TRRDNSListener(
+ `ip${count}.example.org`,
+ undefined,
+ false
+ );
+ inRecord.QueryInterface(Ci.nsIDNSAddrRecord);
+ let responseIP = inRecord.getNextAddrAsString();
+ Assert.ok(true, responseIP);
+ if (responseIP == expectedResponseIP) {
+ break;
+ }
+ count--;
+ }
+
+ if (confirmationShouldFail) {
+ Assert.equal(count, 0, "Confirmation did not finish after 100 iterations");
+ return;
+ }
+
+ Assert.greater(count, 0, "Finished confirmation before 100 iterations");
+}
+
+function setModeAndURI(mode, path) {
+ Services.prefs.setIntPref("network.trr.mode", mode);
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ `https://${TRR_Domain}:${h2Port}/${path}`
+ );
+}
+
+function makeChan(url, mode, bypassCache) {
+ let chan = NetUtil.newChannel({
+ uri: url,
+ loadUsingSystemPrincipal: true,
+ }).QueryInterface(Ci.nsIHttpChannel);
+ chan.loadFlags |= Ci.nsIRequest.LOAD_BYPASS_CACHE;
+ chan.loadFlags |= Ci.nsIRequest.INHIBIT_CACHING;
+ chan.setTRRMode(mode);
+ return chan;
+}
+
+add_task(async function test_server_up() {
+ // This test checks that moz-http2.js running in node is working.
+ // This should always be the first test in this file (except for setup)
+ // otherwise we may encounter random failures when the http2 server is down.
+
+ await NodeServer.execute("bad_id", `"hello"`)
+ .then(() => ok(false, "expecting to throw"))
+ .catch(e => equal(e.message, "Error: could not find id"));
+});
+
+add_task(async function test_trr_flags() {
+ Services.prefs.setBoolPref("network.trr.fallback-on-zero-response", true);
+
+ let httpserv = new HttpServer();
+ httpserv.registerPathHandler("/", function handler(metadata, response) {
+ let content = "ok";
+ response.setHeader("Content-Length", `${content.length}`);
+ response.bodyOutputStream.write(content, content.length);
+ });
+ httpserv.start(-1);
+
+ const URL = `http://example.com:${httpserv.identity.primaryPort}/`;
+
+ for (let mode of [0, 1, 2, 3, 4, 5]) {
+ setModeAndURI(mode, "doh?responseIP=127.0.0.1");
+ for (let flag of [
+ Ci.nsIRequest.TRR_DEFAULT_MODE,
+ Ci.nsIRequest.TRR_DISABLED_MODE,
+ Ci.nsIRequest.TRR_FIRST_MODE,
+ Ci.nsIRequest.TRR_ONLY_MODE,
+ ]) {
+ Services.dns.clearCache(true);
+ let chan = makeChan(URL, flag);
+ let expectTRR =
+ ([2, 3].includes(mode) && flag != Ci.nsIRequest.TRR_DISABLED_MODE) ||
+ (mode == 0 &&
+ [Ci.nsIRequest.TRR_FIRST_MODE, Ci.nsIRequest.TRR_ONLY_MODE].includes(
+ flag
+ ));
+
+ await new Promise(resolve =>
+ chan.asyncOpen(new ChannelListener(resolve))
+ );
+
+ equal(chan.getTRRMode(), flag);
+ equal(
+ expectTRR,
+ chan.QueryInterface(Ci.nsIHttpChannelInternal).isResolvedByTRR
+ );
+ }
+ }
+
+ await new Promise(resolve => httpserv.stop(resolve));
+ Services.prefs.clearUserPref("network.trr.fallback-on-zero-response");
+});
+
+add_task(test_A_record);
+
+add_task(async function test_push() {
+ info("Verify DOH push");
+ Services.dns.clearCache(true);
+ info("Asking server to push us a record");
+ setModeAndURI(3, "doh?responseIP=5.5.5.5&push=true");
+
+ await new TRRDNSListener("first.example.com", "5.5.5.5");
+
+ // At this point the second host name should've been pushed and we can resolve it using
+ // cache only. Set back the URI to a path that fails.
+ // Don't clear the cache, otherwise we lose the pushed record.
+ setModeAndURI(3, "404");
+
+ await new TRRDNSListener("push.example.org", "2018::2018");
+});
+
+add_task(test_AAAA_records);
+
+add_task(test_RFC1918);
+
+add_task(test_GET_ECS);
+
+add_task(test_timeout_mode3);
+
+add_task(test_trr_retry);
+
+add_task(test_strict_native_fallback);
+
+add_task(test_no_answers_fallback);
+
+add_task(test_404_fallback);
+
+add_task(test_mode_1_and_4);
+
+add_task(test_CNAME);
+
+add_task(test_name_mismatch);
+
+add_task(test_mode_2);
+
+add_task(test_excluded_domains);
+
+add_task(test_captiveportal_canonicalURL);
+
+add_task(test_parentalcontrols);
+
+// TRR-first check that DNS result is used if domain is part of the builtin-excluded-domains pref
+add_task(test_builtin_excluded_domains);
+
+add_task(test_excluded_domains_mode3);
+
+add_task(test25e);
+
+add_task(test_parentalcontrols_mode3);
+
+add_task(test_builtin_excluded_domains_mode3);
+
+add_task(count_cookies);
+
+add_task(test_connection_closed);
+
+add_task(async function test_clearCacheOnURIChange() {
+ info("Check that the TRR cache should be cleared by a pref change.");
+ Services.dns.clearCache(true);
+ Services.prefs.setBoolPref("network.trr.clear-cache-on-pref-change", true);
+ setModeAndURI(2, "doh?responseIP=7.7.7.7");
+
+ await new TRRDNSListener("bar.example.com", "7.7.7.7");
+
+ // The TRR cache should be cleared by this pref change.
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ `https://localhost:${h2Port}/doh?responseIP=8.8.8.8`
+ );
+
+ await new TRRDNSListener("bar.example.com", "8.8.8.8");
+ Services.prefs.setBoolPref("network.trr.clear-cache-on-pref-change", false);
+});
+
+add_task(async function test_dnsSuffix() {
+ info("Checking that domains matching dns suffix list use Do53");
+ async function checkDnsSuffixInMode(mode) {
+ Services.dns.clearCache(true);
+ setModeAndURI(mode, "doh?responseIP=1.2.3.4&push=true");
+ await new TRRDNSListener("example.org", "1.2.3.4");
+ await new TRRDNSListener("push.example.org", "2018::2018");
+ await new TRRDNSListener("test.com", "1.2.3.4");
+
+ let networkLinkService = {
+ dnsSuffixList: ["example.org"],
+ QueryInterface: ChromeUtils.generateQI(["nsINetworkLinkService"]),
+ };
+ Services.obs.notifyObservers(
+ networkLinkService,
+ "network:dns-suffix-list-updated"
+ );
+ await new TRRDNSListener("test.com", "1.2.3.4");
+ if (Services.prefs.getBoolPref("network.trr.split_horizon_mitigations")) {
+ await new TRRDNSListener("example.org", "127.0.0.1");
+ // Also test that we don't use the pushed entry.
+ await new TRRDNSListener("push.example.org", "127.0.0.1");
+ } else {
+ await new TRRDNSListener("example.org", "1.2.3.4");
+ await new TRRDNSListener("push.example.org", "2018::2018");
+ }
+
+ // Attempt to clean up, just in case
+ networkLinkService.dnsSuffixList = [];
+ Services.obs.notifyObservers(
+ networkLinkService,
+ "network:dns-suffix-list-updated"
+ );
+ }
+
+ Services.prefs.setBoolPref("network.trr.split_horizon_mitigations", true);
+ await checkDnsSuffixInMode(2);
+ Services.prefs.setCharPref("network.trr.bootstrapAddr", "127.0.0.1");
+ await checkDnsSuffixInMode(3);
+ Services.prefs.setBoolPref("network.trr.split_horizon_mitigations", false);
+ // Test again with mitigations off
+ await checkDnsSuffixInMode(2);
+ await checkDnsSuffixInMode(3);
+ Services.prefs.clearUserPref("network.trr.split_horizon_mitigations");
+ Services.prefs.clearUserPref("network.trr.bootstrapAddr");
+});
+
+add_task(async function test_async_resolve_with_trr_server() {
+ info("Checking asyncResolveWithTrrServer");
+ Services.dns.clearCache(true);
+ Services.prefs.setIntPref("network.trr.mode", 0); // TRR-disabled
+
+ await new TRRDNSListener(
+ "bar_with_trr1.example.com",
+ "2.2.2.2",
+ true,
+ undefined,
+ `https://foo.example.com:${h2Port}/doh?responseIP=2.2.2.2`
+ );
+
+ // Test request without trr server, it should return a native dns response.
+ await new TRRDNSListener("bar_with_trr1.example.com", "127.0.0.1");
+
+ // Mode 2
+ Services.dns.clearCache(true);
+ setModeAndURI(2, "doh?responseIP=2.2.2.2");
+
+ await new TRRDNSListener(
+ "bar_with_trr2.example.com",
+ "3.3.3.3",
+ true,
+ undefined,
+ `https://foo.example.com:${h2Port}/doh?responseIP=3.3.3.3`
+ );
+
+ // Test request without trr server, it should return a response from trr server defined in the pref.
+ await new TRRDNSListener("bar_with_trr2.example.com", "2.2.2.2");
+
+ // Mode 3
+ Services.dns.clearCache(true);
+ setModeAndURI(3, "doh?responseIP=2.2.2.2");
+
+ await new TRRDNSListener(
+ "bar_with_trr3.example.com",
+ "3.3.3.3",
+ true,
+ undefined,
+ `https://foo.example.com:${h2Port}/doh?responseIP=3.3.3.3`
+ );
+
+ // Test request without trr server, it should return a response from trr server defined in the pref.
+ await new TRRDNSListener("bar_with_trr3.example.com", "2.2.2.2");
+
+ // Mode 5
+ Services.dns.clearCache(true);
+ setModeAndURI(5, "doh?responseIP=2.2.2.2");
+
+ // When dns is resolved in socket process, we can't set |expectEarlyFail| to true.
+ let inSocketProcess = mozinfo.socketprocess_networking;
+ await new TRRDNSListener(
+ "bar_with_trr3.example.com",
+ undefined,
+ false,
+ undefined,
+ `https://foo.example.com:${h2Port}/doh?responseIP=3.3.3.3`,
+ !inSocketProcess
+ );
+
+ // Call normal AsyncOpen, it will return result from the native resolver.
+ await new TRRDNSListener("bar_with_trr3.example.com", "127.0.0.1");
+
+ // Check that cache is ignored when server is different
+ Services.dns.clearCache(true);
+ setModeAndURI(3, "doh?responseIP=2.2.2.2");
+
+ await new TRRDNSListener("bar_with_trr4.example.com", "2.2.2.2", true);
+
+ // The record will be fetch again.
+ await new TRRDNSListener(
+ "bar_with_trr4.example.com",
+ "3.3.3.3",
+ true,
+ undefined,
+ `https://foo.example.com:${h2Port}/doh?responseIP=3.3.3.3`
+ );
+
+ // The record will be fetch again.
+ await new TRRDNSListener(
+ "bar_with_trr5.example.com",
+ "4.4.4.4",
+ true,
+ undefined,
+ `https://foo.example.com:${h2Port}/doh?responseIP=4.4.4.4`
+ );
+
+ // Check no fallback and no blocklisting upon failure
+ Services.dns.clearCache(true);
+ setModeAndURI(2, "doh?responseIP=2.2.2.2");
+
+ let { inStatus } = await new TRRDNSListener(
+ "bar_with_trr6.example.com",
+ undefined,
+ false,
+ undefined,
+ `https://foo.example.com:${h2Port}/404`
+ );
+ Assert.ok(
+ !Components.isSuccessCode(inStatus),
+ `${inStatus} should be an error code`
+ );
+
+ await new TRRDNSListener("bar_with_trr6.example.com", "2.2.2.2", true);
+
+ // Check that DoH push doesn't work
+ Services.dns.clearCache(true);
+ setModeAndURI(2, "doh?responseIP=2.2.2.2");
+
+ await new TRRDNSListener(
+ "bar_with_trr7.example.com",
+ "3.3.3.3",
+ true,
+ undefined,
+ `https://foo.example.com:${h2Port}/doh?responseIP=3.3.3.3&push=true`
+ );
+
+ // AsyncResoleWithTrrServer rejects server pushes and the entry for push.example.org
+ // shouldn't be neither in the default cache not in AsyncResoleWithTrrServer cache.
+ setModeAndURI(2, "404");
+
+ await new TRRDNSListener(
+ "push.example.org",
+ "3.3.3.3",
+ true,
+ undefined,
+ `https://foo.example.com:${h2Port}/doh?responseIP=3.3.3.3&push=true`
+ );
+
+ await new TRRDNSListener("push.example.org", "127.0.0.1");
+
+ // Check confirmation is ignored
+ Services.dns.clearCache(true);
+ setModeAndURI(2, "doh?responseIP=1::ffff");
+ Services.prefs.clearUserPref("network.trr.useGET");
+ Services.prefs.clearUserPref("network.trr.disable-ECS");
+ Services.prefs.setCharPref(
+ "network.trr.confirmationNS",
+ "confirm.example.com"
+ );
+
+ // AsyncResoleWithTrrServer will succeed
+ await new TRRDNSListener(
+ "bar_with_trr8.example.com",
+ "3.3.3.3",
+ true,
+ undefined,
+ `https://foo.example.com:${h2Port}/doh?responseIP=3.3.3.3`
+ );
+
+ Services.prefs.setCharPref("network.trr.confirmationNS", "skip");
+
+ // Bad port
+ Services.dns.clearCache(true);
+ setModeAndURI(2, "doh?responseIP=2.2.2.2");
+
+ ({ inStatus } = await new TRRDNSListener(
+ "only_once.example.com",
+ undefined,
+ false,
+ undefined,
+ `https://target.example.com:666/404`
+ ));
+ Assert.ok(
+ !Components.isSuccessCode(inStatus),
+ `${inStatus} should be an error code`
+ );
+
+ // // MOZ_LOG=sync,timestamp,nsHostResolver:5 We should not keep resolving only_once.example.com
+ // // TODO: find a way of automating this
+ // await new Promise(resolve => {});
+});
+
+add_task(test_fetch_time);
+
+add_task(async function test_content_encoding_gzip() {
+ info("Checking gzip content encoding");
+ Services.dns.clearCache(true);
+ Services.prefs.setBoolPref(
+ "network.trr.send_empty_accept-encoding_headers",
+ false
+ );
+ setModeAndURI(3, "doh?responseIP=2.2.2.2");
+
+ await new TRRDNSListener("bar.example.com", "2.2.2.2");
+ Services.prefs.clearUserPref(
+ "network.trr.send_empty_accept-encoding_headers"
+ );
+});
+
+add_task(async function test_redirect() {
+ info("Check handling of redirect");
+
+ // GET
+ Services.dns.clearCache(true);
+ setModeAndURI(3, "doh?redirect=4.4.4.4{&dns}");
+ Services.prefs.setBoolPref("network.trr.useGET", true);
+ Services.prefs.setBoolPref("network.trr.disable-ECS", true);
+
+ await new TRRDNSListener("ecs.example.com", "4.4.4.4");
+
+ // POST
+ Services.dns.clearCache(true);
+ Services.prefs.setBoolPref("network.trr.useGET", false);
+ setModeAndURI(3, "doh?redirect=4.4.4.4");
+
+ await new TRRDNSListener("bar.example.com", "4.4.4.4");
+
+ Services.prefs.clearUserPref("network.trr.useGET");
+ Services.prefs.clearUserPref("network.trr.disable-ECS");
+});
+
+// confirmationNS set without confirmed NS yet
+// checks that we properly fall back to DNS is confirmation is not ready yet,
+// and wait-for-confirmation pref is true
+add_task(async function test_confirmation() {
+ info("Checking that we fall back correctly when confirmation is pending");
+ Services.dns.clearCache(true);
+ Services.prefs.setBoolPref("network.trr.wait-for-confirmation", true);
+ setModeAndURI(2, "doh?responseIP=7.7.7.7&slowConfirm=true");
+ Services.prefs.setCharPref(
+ "network.trr.confirmationNS",
+ "confirm.example.com"
+ );
+
+ await new TRRDNSListener("example.org", "127.0.0.1");
+ await new Promise(resolve => do_timeout(1000, resolve));
+ await waitForConfirmation("7.7.7.7");
+
+ // Reset between each test to force re-confirm
+ Services.prefs.setCharPref("network.trr.confirmationNS", "skip");
+
+ info("Check that confirmation is skipped in mode 3");
+ // This is just a smoke test to make sure lookups succeed immediately
+ // in mode 3 without waiting for confirmation.
+ Services.dns.clearCache(true);
+ setModeAndURI(3, "doh?responseIP=1::ffff&slowConfirm=true");
+ Services.prefs.setCharPref(
+ "network.trr.confirmationNS",
+ "confirm.example.com"
+ );
+
+ await new TRRDNSListener("skipConfirmationForMode3.example.com", "1::ffff");
+
+ // Reset between each test to force re-confirm
+ Services.prefs.setCharPref("network.trr.confirmationNS", "skip");
+
+ Services.dns.clearCache(true);
+ Services.prefs.setBoolPref("network.trr.wait-for-confirmation", false);
+ setModeAndURI(2, "doh?responseIP=7.7.7.7&slowConfirm=true");
+ Services.prefs.setCharPref(
+ "network.trr.confirmationNS",
+ "confirm.example.com"
+ );
+
+ // DoH available immediately
+ await new TRRDNSListener("example.org", "7.7.7.7");
+
+ // Reset between each test to force re-confirm
+ Services.prefs.setCharPref("network.trr.confirmationNS", "skip");
+
+ // Fallback when confirmation fails
+ Services.dns.clearCache(true);
+ Services.prefs.setBoolPref("network.trr.wait-for-confirmation", true);
+ setModeAndURI(2, "404");
+ Services.prefs.setCharPref(
+ "network.trr.confirmationNS",
+ "confirm.example.com"
+ );
+
+ await waitForConfirmation("7.7.7.7", true);
+
+ await new TRRDNSListener("example.org", "127.0.0.1");
+
+ // Reset
+ Services.prefs.setCharPref("network.trr.confirmationNS", "skip");
+ Services.prefs.clearUserPref("network.trr.wait-for-confirmation");
+});
+
+add_task(test_fqdn);
+
+add_task(async function test_detected_uri() {
+ info("Test setDetectedTrrURI");
+ Services.dns.clearCache(true);
+ Services.prefs.setIntPref("network.trr.mode", 3);
+ Services.prefs.clearUserPref("network.trr.uri");
+ let defaultURI = gDefaultPref.getCharPref("network.trr.default_provider_uri");
+ gDefaultPref.setCharPref(
+ "network.trr.default_provider_uri",
+ `https://foo.example.com:${h2Port}/doh?responseIP=3.4.5.6`
+ );
+ await new TRRDNSListener("domainA.example.org.", "3.4.5.6");
+ Services.dns.setDetectedTrrURI(
+ `https://foo.example.com:${h2Port}/doh?responseIP=1.2.3.4`
+ );
+ await new TRRDNSListener("domainB.example.org.", "1.2.3.4");
+ gDefaultPref.setCharPref("network.trr.default_provider_uri", defaultURI);
+
+ // With a user-set doh uri this time.
+ Services.dns.clearCache(true);
+ setModeAndURI(2, "doh?responseIP=4.5.6.7");
+ await new TRRDNSListener("domainA.example.org.", "4.5.6.7");
+
+ // This should be a no-op, since we have a user-set URI
+ Services.dns.setDetectedTrrURI(
+ `https://foo.example.com:${h2Port}/doh?responseIP=1.2.3.4`
+ );
+ await new TRRDNSListener("domainB.example.org.", "4.5.6.7");
+
+ // Test network link status change
+ Services.dns.clearCache(true);
+ Services.prefs.setIntPref("network.trr.mode", 3);
+ Services.prefs.clearUserPref("network.trr.uri");
+ gDefaultPref.setCharPref(
+ "network.trr.default_provider_uri",
+ `https://foo.example.com:${h2Port}/doh?responseIP=3.4.5.6`
+ );
+ await new TRRDNSListener("domainA.example.org.", "3.4.5.6");
+ Services.dns.setDetectedTrrURI(
+ `https://foo.example.com:${h2Port}/doh?responseIP=1.2.3.4`
+ );
+ await new TRRDNSListener("domainB.example.org.", "1.2.3.4");
+
+ let networkLinkService = {
+ platformDNSIndications: 0,
+ QueryInterface: ChromeUtils.generateQI(["nsINetworkLinkService"]),
+ };
+
+ Services.obs.notifyObservers(
+ networkLinkService,
+ "network:link-status-changed",
+ "changed"
+ );
+
+ await new TRRDNSListener("domainC.example.org.", "3.4.5.6");
+
+ gDefaultPref.setCharPref("network.trr.default_provider_uri", defaultURI);
+});
+
+add_task(async function test_pref_changes() {
+ info("Testing pref change handling");
+ Services.prefs.clearUserPref("network.trr.uri");
+ let defaultURI = gDefaultPref.getCharPref("network.trr.default_provider_uri");
+
+ async function doThenCheckURI(closure, expectedURI, expectChange = true) {
+ let uriChanged;
+ if (expectChange) {
+ uriChanged = topicObserved("network:trr-uri-changed");
+ }
+ closure();
+ if (expectChange) {
+ await uriChanged;
+ }
+ equal(Services.dns.currentTrrURI, expectedURI);
+ }
+
+ // setting the default value of the pref should be reflected in the URI
+ await doThenCheckURI(() => {
+ gDefaultPref.setCharPref(
+ "network.trr.default_provider_uri",
+ `https://foo.example.com:${h2Port}/doh?default`
+ );
+ }, `https://foo.example.com:${h2Port}/doh?default`);
+
+ // the user set value should be reflected in the URI
+ await doThenCheckURI(() => {
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ `https://foo.example.com:${h2Port}/doh?user`
+ );
+ }, `https://foo.example.com:${h2Port}/doh?user`);
+
+ // A user set pref is selected, so it should be chosen instead of the rollout
+ await doThenCheckURI(
+ () => {
+ Services.prefs.setCharPref(
+ "doh-rollout.uri",
+ `https://foo.example.com:${h2Port}/doh?rollout`
+ );
+ },
+ `https://foo.example.com:${h2Port}/doh?user`,
+ false
+ );
+
+ // There is no user set pref, so we go to the rollout pref
+ await doThenCheckURI(() => {
+ Services.prefs.clearUserPref("network.trr.uri");
+ }, `https://foo.example.com:${h2Port}/doh?rollout`);
+
+ // When the URI is set by the rollout addon, detection is allowed
+ await doThenCheckURI(() => {
+ Services.dns.setDetectedTrrURI(
+ `https://foo.example.com:${h2Port}/doh?detected`
+ );
+ }, `https://foo.example.com:${h2Port}/doh?detected`);
+
+ // Should switch back to the default provided by the rollout addon
+ await doThenCheckURI(() => {
+ let networkLinkService = {
+ platformDNSIndications: 0,
+ QueryInterface: ChromeUtils.generateQI(["nsINetworkLinkService"]),
+ };
+ Services.obs.notifyObservers(
+ networkLinkService,
+ "network:link-status-changed",
+ "changed"
+ );
+ }, `https://foo.example.com:${h2Port}/doh?rollout`);
+
+ // Again the user set pref should be chosen
+ await doThenCheckURI(() => {
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ `https://foo.example.com:${h2Port}/doh?user`
+ );
+ }, `https://foo.example.com:${h2Port}/doh?user`);
+
+ // Detection should not work with a user set pref
+ await doThenCheckURI(
+ () => {
+ Services.dns.setDetectedTrrURI(
+ `https://foo.example.com:${h2Port}/doh?detected`
+ );
+ },
+ `https://foo.example.com:${h2Port}/doh?user`,
+ false
+ );
+
+ // Should stay the same on network changes
+ await doThenCheckURI(
+ () => {
+ let networkLinkService = {
+ platformDNSIndications: 0,
+ QueryInterface: ChromeUtils.generateQI(["nsINetworkLinkService"]),
+ };
+ Services.obs.notifyObservers(
+ networkLinkService,
+ "network:link-status-changed",
+ "changed"
+ );
+ },
+ `https://foo.example.com:${h2Port}/doh?user`,
+ false
+ );
+
+ // Restore the pref
+ gDefaultPref.setCharPref("network.trr.default_provider_uri", defaultURI);
+});
+
+add_task(async function test_dohrollout_mode() {
+ info("Testing doh-rollout.mode");
+ Services.prefs.clearUserPref("network.trr.mode");
+ Services.prefs.clearUserPref("doh-rollout.mode");
+
+ equal(Services.dns.currentTrrMode, 0);
+
+ async function doThenCheckMode(trrMode, rolloutMode, expectedMode, message) {
+ let modeChanged;
+ if (Services.dns.currentTrrMode != expectedMode) {
+ modeChanged = topicObserved("network:trr-mode-changed");
+ }
+
+ if (trrMode != undefined) {
+ Services.prefs.setIntPref("network.trr.mode", trrMode);
+ }
+
+ if (rolloutMode != undefined) {
+ Services.prefs.setIntPref("doh-rollout.mode", rolloutMode);
+ }
+
+ if (modeChanged) {
+ await modeChanged;
+ }
+ equal(Services.dns.currentTrrMode, expectedMode, message);
+ }
+
+ await doThenCheckMode(2, undefined, 2);
+ await doThenCheckMode(3, undefined, 3);
+ await doThenCheckMode(5, undefined, 5);
+ await doThenCheckMode(2, undefined, 2);
+ await doThenCheckMode(0, undefined, 0);
+ await doThenCheckMode(1, undefined, 5);
+ await doThenCheckMode(6, undefined, 5);
+
+ await doThenCheckMode(2, 0, 2);
+ await doThenCheckMode(2, 1, 2);
+ await doThenCheckMode(2, 2, 2);
+ await doThenCheckMode(2, 3, 2);
+ await doThenCheckMode(2, 5, 2);
+ await doThenCheckMode(3, 2, 3);
+ await doThenCheckMode(5, 2, 5);
+
+ Services.prefs.clearUserPref("network.trr.mode");
+ Services.prefs.clearUserPref("doh-rollout.mode");
+
+ await doThenCheckMode(undefined, 2, 2);
+ await doThenCheckMode(undefined, 3, 3);
+
+ // All modes that are not 0,2,3 are treated as 5
+ await doThenCheckMode(undefined, 5, 5);
+ await doThenCheckMode(undefined, 4, 5);
+ await doThenCheckMode(undefined, 6, 5);
+
+ await doThenCheckMode(undefined, 2, 2);
+ await doThenCheckMode(3, undefined, 3);
+
+ Services.prefs.clearUserPref("network.trr.mode");
+ equal(Services.dns.currentTrrMode, 2);
+ Services.prefs.clearUserPref("doh-rollout.mode");
+ equal(Services.dns.currentTrrMode, 0);
+});
+
+add_task(test_ipv6_trr_fallback);
+
+add_task(test_ipv4_trr_fallback);
+
+add_task(test_no_retry_without_doh);
+
+// This test checks that normally when the TRR mode goes from ON -> OFF
+// we purge the DNS cache (including TRR), so the entries aren't used on
+// networks where they shouldn't. For example - turning on a VPN.
+add_task(async function test_purge_trr_cache_on_mode_change() {
+ info("Checking that we purge cache when TRR is turned off");
+ Services.prefs.setBoolPref("network.trr.clear-cache-on-pref-change", true);
+
+ Services.prefs.setIntPref("network.trr.mode", 0);
+ Services.prefs.setIntPref("doh-rollout.mode", 2);
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ `https://foo.example.com:${h2Port}/doh?responseIP=3.3.3.3`
+ );
+
+ await new TRRDNSListener("cached.example.com", "3.3.3.3");
+ Services.prefs.clearUserPref("doh-rollout.mode");
+
+ await new TRRDNSListener("cached.example.com", "127.0.0.1");
+
+ Services.prefs.setBoolPref("network.trr.clear-cache-on-pref-change", false);
+ Services.prefs.clearUserPref("doh-rollout.mode");
+});
+
+add_task(async function test_old_bootstrap_pref() {
+ Services.dns.clearCache(true);
+ // Note this is a remote address. Setting this pref should have no effect,
+ // as this is the old name for the bootstrap pref.
+ // If this were to be used, the test would crash when accessing a non-local
+ // IP address.
+ Services.prefs.setCharPref("network.trr.bootstrapAddress", "1.1.1.1");
+ setModeAndURI(Ci.nsIDNSService.MODE_TRRONLY, `doh?responseIP=4.4.4.4`);
+ await new TRRDNSListener("testytest.com", "4.4.4.4");
+});
+
+add_task(async function test_padding() {
+ setModeAndURI(Ci.nsIDNSService.MODE_TRRONLY, `doh`);
+ async function CheckPadding(
+ pad_length,
+ request,
+ none,
+ ecs,
+ padding,
+ ecsPadding
+ ) {
+ Services.prefs.setIntPref("network.trr.padding.length", pad_length);
+ Services.dns.clearCache(true);
+ Services.prefs.setBoolPref("network.trr.padding", false);
+ Services.prefs.setBoolPref("network.trr.disable-ECS", false);
+ await new TRRDNSListener(request, none);
+
+ Services.dns.clearCache(true);
+ Services.prefs.setBoolPref("network.trr.padding", false);
+ Services.prefs.setBoolPref("network.trr.disable-ECS", true);
+ await new TRRDNSListener(request, ecs);
+
+ Services.dns.clearCache(true);
+ Services.prefs.setBoolPref("network.trr.padding", true);
+ Services.prefs.setBoolPref("network.trr.disable-ECS", false);
+ await new TRRDNSListener(request, padding);
+
+ Services.dns.clearCache(true);
+ Services.prefs.setBoolPref("network.trr.padding", true);
+ Services.prefs.setBoolPref("network.trr.disable-ECS", true);
+ await new TRRDNSListener(request, ecsPadding);
+ }
+
+ // short domain name
+ await CheckPadding(
+ 16,
+ "a.pd",
+ "2.2.0.22",
+ "2.2.0.41",
+ "1.1.0.48",
+ "1.1.0.48"
+ );
+ await CheckPadding(256, "a.pd", "2.2.0.22", "2.2.0.41", "1.1.1.0", "1.1.1.0");
+
+ // medium domain name
+ await CheckPadding(
+ 16,
+ "has-padding.pd",
+ "2.2.0.32",
+ "2.2.0.51",
+ "1.1.0.48",
+ "1.1.0.64"
+ );
+ await CheckPadding(
+ 128,
+ "has-padding.pd",
+ "2.2.0.32",
+ "2.2.0.51",
+ "1.1.0.128",
+ "1.1.0.128"
+ );
+ await CheckPadding(
+ 80,
+ "has-padding.pd",
+ "2.2.0.32",
+ "2.2.0.51",
+ "1.1.0.80",
+ "1.1.0.80"
+ );
+
+ // long domain name
+ await CheckPadding(
+ 16,
+ "abcdefghijklmnopqrstuvwxyz0123456789.abcdefghijklmnopqrstuvwxyz0123456789.abcdefghijklmnopqrstuvwxyz0123456789.pd",
+ "2.2.0.131",
+ "2.2.0.150",
+ "1.1.0.160",
+ "1.1.0.160"
+ );
+ await CheckPadding(
+ 128,
+ "abcdefghijklmnopqrstuvwxyz0123456789.abcdefghijklmnopqrstuvwxyz0123456789.abcdefghijklmnopqrstuvwxyz0123456789.pd",
+ "2.2.0.131",
+ "2.2.0.150",
+ "1.1.1.0",
+ "1.1.1.0"
+ );
+ await CheckPadding(
+ 80,
+ "abcdefghijklmnopqrstuvwxyz0123456789.abcdefghijklmnopqrstuvwxyz0123456789.abcdefghijklmnopqrstuvwxyz0123456789.pd",
+ "2.2.0.131",
+ "2.2.0.150",
+ "1.1.0.160",
+ "1.1.0.160"
+ );
+});
+
+add_task(test_connection_reuse_and_cycling);