summaryrefslogtreecommitdiffstats
path: root/netwerk/test/unit/test_trr_https_fallback.js
diff options
context:
space:
mode:
Diffstat (limited to 'netwerk/test/unit/test_trr_https_fallback.js')
-rw-r--r--netwerk/test/unit/test_trr_https_fallback.js1105
1 files changed, 1105 insertions, 0 deletions
diff --git a/netwerk/test/unit/test_trr_https_fallback.js b/netwerk/test/unit/test_trr_https_fallback.js
new file mode 100644
index 0000000000..1b5217876d
--- /dev/null
+++ b/netwerk/test/unit/test_trr_https_fallback.js
@@ -0,0 +1,1105 @@
+/* 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/. */
+
+"use strict";
+
+const { TestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/TestUtils.sys.mjs"
+);
+
+let h2Port;
+let h3Port;
+let h3NoResponsePort;
+let trrServer;
+
+const certOverrideService = Cc[
+ "@mozilla.org/security/certoverride;1"
+].getService(Ci.nsICertOverrideService);
+
+add_setup(async function setup() {
+ trr_test_setup();
+
+ h2Port = Services.env.get("MOZHTTP2_PORT");
+ Assert.notEqual(h2Port, null);
+ Assert.notEqual(h2Port, "");
+
+ h3Port = Services.env.get("MOZHTTP3_PORT");
+ Assert.notEqual(h3Port, null);
+ Assert.notEqual(h3Port, "");
+
+ h3NoResponsePort = Services.env.get("MOZHTTP3_PORT_NO_RESPONSE");
+ Assert.notEqual(h3NoResponsePort, null);
+ Assert.notEqual(h3NoResponsePort, "");
+
+ Services.prefs.setBoolPref("network.dns.upgrade_with_https_rr", true);
+ Services.prefs.setBoolPref("network.dns.use_https_rr_as_altsvc", true);
+ Services.prefs.setBoolPref("network.dns.echconfig.enabled", true);
+
+ registerCleanupFunction(async () => {
+ trr_clear_prefs();
+ Services.prefs.clearUserPref("network.dns.upgrade_with_https_rr");
+ Services.prefs.clearUserPref("network.dns.use_https_rr_as_altsvc");
+ Services.prefs.clearUserPref("network.dns.echconfig.enabled");
+ Services.prefs.clearUserPref(
+ "network.dns.echconfig.fallback_to_origin_when_all_failed"
+ );
+ Services.prefs.clearUserPref("network.dns.httpssvc.reset_exclustion_list");
+ Services.prefs.clearUserPref("network.http.http3.enable");
+ Services.prefs.clearUserPref(
+ "network.dns.httpssvc.http3_fast_fallback_timeout"
+ );
+ Services.prefs.clearUserPref("network.http.speculative-parallel-limit");
+ Services.prefs.clearUserPref("network.dns.localDomains");
+ Services.prefs.clearUserPref("network.dns.http3_echconfig.enabled");
+ if (trrServer) {
+ await trrServer.stop();
+ }
+ });
+
+ if (mozinfo.socketprocess_networking) {
+ Services.dns; // Needed to trigger socket process.
+ await TestUtils.waitForCondition(() => Services.io.socketProcessLaunched);
+ }
+
+ Services.prefs.setIntPref("network.trr.mode", Ci.nsIDNSService.MODE_TRRFIRST);
+});
+
+function makeChan(url) {
+ let chan = NetUtil.newChannel({
+ uri: url,
+ loadUsingSystemPrincipal: true,
+ contentPolicyType: Ci.nsIContentPolicy.TYPE_DOCUMENT,
+ }).QueryInterface(Ci.nsIHttpChannel);
+ return chan;
+}
+
+function channelOpenPromise(chan, flags) {
+ return new Promise(resolve => {
+ function finish(req, buffer) {
+ resolve([req, buffer]);
+ certOverrideService.setDisableAllSecurityChecksAndLetAttackersInterceptMyData(
+ false
+ );
+ }
+ let internal = chan.QueryInterface(Ci.nsIHttpChannelInternal);
+ internal.setWaitForHTTPSSVCRecord();
+ certOverrideService.setDisableAllSecurityChecksAndLetAttackersInterceptMyData(
+ true
+ );
+ chan.asyncOpen(new ChannelListener(finish, null, flags));
+ });
+}
+
+// Test if we can fallback to the last record sucessfully.
+add_task(async function testFallbackToTheLastRecord() {
+ trrServer = new TRRServer();
+ await trrServer.start();
+
+ Services.prefs.setIntPref("network.trr.mode", 3);
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ `https://foo.example.com:${trrServer.port}/dns-query`
+ );
+
+ // Only the last record is valid to use.
+ await trrServer.registerDoHAnswers("test.fallback.com", "HTTPS", {
+ answers: [
+ {
+ name: "test.fallback.com",
+ ttl: 55,
+ type: "HTTPS",
+ flush: false,
+ data: {
+ priority: 1,
+ name: "test.fallback1.com",
+ values: [
+ { key: "alpn", value: ["h2", "h3-26"] },
+ { key: "echconfig", value: "123..." },
+ ],
+ },
+ },
+ {
+ name: "test.fallback.com",
+ ttl: 55,
+ type: "HTTPS",
+ flush: false,
+ data: {
+ priority: 4,
+ name: "foo.example.com",
+ values: [
+ { key: "alpn", value: ["h2", "h3-26"] },
+ { key: "port", value: h2Port },
+ { key: "echconfig", value: "456..." },
+ ],
+ },
+ },
+ {
+ name: "test.fallback.com",
+ ttl: 55,
+ type: "HTTPS",
+ flush: false,
+ data: {
+ priority: 3,
+ name: "test.fallback3.com",
+ values: [
+ { key: "alpn", value: ["h2", "h3-26"] },
+ { key: "echconfig", value: "456..." },
+ ],
+ },
+ },
+ {
+ name: "test.fallback.com",
+ ttl: 55,
+ type: "HTTPS",
+ flush: false,
+ data: {
+ priority: 2,
+ name: "test.fallback2.com",
+ values: [
+ { key: "alpn", value: ["h2", "h3-26"] },
+ { key: "echconfig", value: "456..." },
+ ],
+ },
+ },
+ ],
+ });
+
+ await new TRRDNSListener("test.fallback.com", {
+ type: Ci.nsIDNSService.RESOLVE_TYPE_HTTPSSVC,
+ });
+
+ let chan = makeChan(`https://test.fallback.com:${h2Port}/server-timing`);
+ let [req] = await channelOpenPromise(chan);
+ // Test if this request is done by h2.
+ Assert.equal(req.getResponseHeader("x-connection-http2"), "yes");
+
+ await trrServer.stop();
+});
+
+add_task(async function testFallbackToTheOrigin() {
+ trrServer = new TRRServer();
+ await trrServer.start();
+ Services.prefs.setIntPref("network.trr.mode", 3);
+ Services.prefs.setBoolPref(
+ "network.dns.echconfig.fallback_to_origin_when_all_failed",
+ true
+ );
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ `https://foo.example.com:${trrServer.port}/dns-query`
+ );
+
+ // All records are not able to use to connect, so we fallback to the origin
+ // one.
+ await trrServer.registerDoHAnswers("test.foo.com", "HTTPS", {
+ answers: [
+ {
+ name: "test.foo.com",
+ ttl: 55,
+ type: "HTTPS",
+ flush: false,
+ data: {
+ priority: 1,
+ name: "test.foo1.com",
+ values: [
+ { key: "alpn", value: ["h2", "h3-26"] },
+ { key: "echconfig", value: "123..." },
+ ],
+ },
+ },
+ {
+ name: "test.foo.com",
+ ttl: 55,
+ type: "HTTPS",
+ flush: false,
+ data: {
+ priority: 3,
+ name: "test.foo3.com",
+ values: [
+ { key: "alpn", value: ["h2", "h3-26"] },
+ { key: "echconfig", value: "456..." },
+ ],
+ },
+ },
+ {
+ name: "test.foo.com",
+ ttl: 55,
+ type: "HTTPS",
+ flush: false,
+ data: {
+ priority: 2,
+ name: "test.foo2.com",
+ values: [
+ { key: "alpn", value: ["h2", "h3-26"] },
+ { key: "echconfig", value: "456..." },
+ ],
+ },
+ },
+ ],
+ });
+
+ await trrServer.registerDoHAnswers("test.foo.com", "A", {
+ answers: [
+ {
+ name: "test.foo.com",
+ ttl: 55,
+ type: "A",
+ flush: false,
+ data: "127.0.0.1",
+ },
+ ],
+ });
+
+ await new TRRDNSListener("test.foo.com", {
+ type: Ci.nsIDNSService.RESOLVE_TYPE_HTTPSSVC,
+ });
+
+ let chan = makeChan(`https://test.foo.com:${h2Port}/server-timing`);
+ let [req] = await channelOpenPromise(chan);
+ // Test if this request is done by h2.
+ Assert.equal(req.getResponseHeader("x-connection-http2"), "yes");
+
+ await trrServer.stop();
+});
+
+// Test when all records are failed and network.dns.echconfig.fallback_to_origin
+// is false. In this case, the connection is always failed.
+add_task(async function testAllRecordsFailed() {
+ trrServer = new TRRServer();
+ await trrServer.start();
+ Services.prefs.setIntPref("network.trr.mode", 3);
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ `https://foo.example.com:${trrServer.port}/dns-query`
+ );
+ Services.prefs.setBoolPref(
+ "network.dns.echconfig.fallback_to_origin_when_all_failed",
+ false
+ );
+
+ await trrServer.registerDoHAnswers("test.bar.com", "HTTPS", {
+ answers: [
+ {
+ name: "test.bar.com",
+ ttl: 55,
+ type: "HTTPS",
+ flush: false,
+ data: {
+ priority: 1,
+ name: "test.bar1.com",
+ values: [
+ { key: "alpn", value: ["h2", "h3-26"] },
+ { key: "echconfig", value: "123..." },
+ ],
+ },
+ },
+ {
+ name: "test.bar.com",
+ ttl: 55,
+ type: "HTTPS",
+ flush: false,
+ data: {
+ priority: 3,
+ name: "test.bar3.com",
+ values: [
+ { key: "alpn", value: ["h2", "h3-26"] },
+ { key: "echconfig", value: "456..." },
+ ],
+ },
+ },
+ {
+ name: "test.bar.com",
+ ttl: 55,
+ type: "HTTPS",
+ flush: false,
+ data: {
+ priority: 2,
+ name: "test.bar2.com",
+ values: [
+ { key: "alpn", value: ["h2", "h3-26"] },
+ { key: "echconfig", value: "456..." },
+ ],
+ },
+ },
+ ],
+ });
+
+ await new TRRDNSListener("test.bar.com", {
+ type: Ci.nsIDNSService.RESOLVE_TYPE_HTTPSSVC,
+ });
+
+ // This channel should be failed.
+ let chan = makeChan(`https://test.bar.com:${h2Port}/server-timing`);
+ await channelOpenPromise(chan, CL_EXPECT_LATE_FAILURE | CL_ALLOW_UNKNOWN_CL);
+
+ await trrServer.stop();
+});
+
+// Test when all records have no echConfig, we directly fallback to the origin
+// one.
+add_task(async function testFallbackToTheOrigin2() {
+ trrServer = new TRRServer();
+ await trrServer.start();
+ Services.prefs.setIntPref("network.trr.mode", 3);
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ `https://foo.example.com:${trrServer.port}/dns-query`
+ );
+
+ await trrServer.registerDoHAnswers("test.example.com", "HTTPS", {
+ answers: [
+ {
+ name: "test.example.com",
+ ttl: 55,
+ type: "HTTPS",
+ flush: false,
+ data: {
+ priority: 1,
+ name: "test.example1.com",
+ values: [{ key: "alpn", value: ["h2", "h3-26"] }],
+ },
+ },
+ {
+ name: "test.example.com",
+ ttl: 55,
+ type: "HTTPS",
+ flush: false,
+ data: {
+ priority: 3,
+ name: "test.example3.com",
+ values: [{ key: "alpn", value: ["h2", "h3-26"] }],
+ },
+ },
+ ],
+ });
+
+ await new TRRDNSListener("test.example.com", {
+ type: Ci.nsIDNSService.RESOLVE_TYPE_HTTPSSVC,
+ });
+
+ let chan = makeChan(`https://test.example.com:${h2Port}/server-timing`);
+ await channelOpenPromise(chan, CL_EXPECT_LATE_FAILURE | CL_ALLOW_UNKNOWN_CL);
+
+ await trrServer.registerDoHAnswers("test.example.com", "A", {
+ answers: [
+ {
+ name: "test.example.com",
+ ttl: 55,
+ type: "A",
+ flush: false,
+ data: "127.0.0.1",
+ },
+ ],
+ });
+
+ chan = makeChan(`https://test.example.com:${h2Port}/server-timing`);
+ await channelOpenPromise(chan);
+
+ await trrServer.stop();
+});
+
+// Test when some records have echConfig and some not, we directly fallback to
+// the origin one.
+add_task(async function testFallbackToTheOrigin3() {
+ Services.dns.clearCache(true);
+
+ trrServer = new TRRServer();
+ await trrServer.start();
+ Services.prefs.setIntPref("network.trr.mode", 3);
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ `https://foo.example.com:${trrServer.port}/dns-query`
+ );
+
+ await trrServer.registerDoHAnswers("vulnerable.com", "A", {
+ answers: [
+ {
+ name: "vulnerable.com",
+ ttl: 55,
+ type: "A",
+ flush: false,
+ data: "127.0.0.1",
+ },
+ ],
+ });
+
+ await trrServer.registerDoHAnswers("vulnerable.com", "HTTPS", {
+ answers: [
+ {
+ name: "vulnerable.com",
+ ttl: 55,
+ type: "HTTPS",
+ flush: false,
+ data: {
+ priority: 1,
+ name: "vulnerable1.com",
+ values: [
+ { key: "alpn", value: ["h2", "h3-26"] },
+ { key: "echconfig", value: "456..." },
+ ],
+ },
+ },
+ {
+ name: "vulnerable.com",
+ ttl: 55,
+ type: "HTTPS",
+ flush: false,
+ data: {
+ priority: 2,
+ name: "vulnerable2.com",
+ values: [
+ { key: "alpn", value: ["h2", "h3-26"] },
+ { key: "echconfig", value: "456..." },
+ ],
+ },
+ },
+ {
+ name: "vulnerable.com",
+ ttl: 55,
+ type: "HTTPS",
+ flush: false,
+ data: {
+ priority: 3,
+ name: "vulnerable3.com",
+ values: [{ key: "alpn", value: ["h2", "h3-26"] }],
+ },
+ },
+ ],
+ });
+
+ await new TRRDNSListener("vulnerable.com", {
+ type: Ci.nsIDNSService.RESOLVE_TYPE_HTTPSSVC,
+ });
+
+ let chan = makeChan(`https://vulnerable.com:${h2Port}/server-timing`);
+ await channelOpenPromise(chan);
+
+ await trrServer.stop();
+});
+
+add_task(async function testResetExclusionList() {
+ trrServer = new TRRServer();
+ await trrServer.start();
+ Services.prefs.setIntPref("network.trr.mode", 3);
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ `https://foo.example.com:${trrServer.port}/dns-query`
+ );
+ Services.prefs.setBoolPref(
+ "network.dns.httpssvc.reset_exclustion_list",
+ false
+ );
+
+ await trrServer.registerDoHAnswers("test.reset.com", "HTTPS", {
+ answers: [
+ {
+ name: "test.reset.com",
+ ttl: 55,
+ type: "HTTPS",
+ flush: false,
+ data: {
+ priority: 1,
+ name: "test.reset1.com",
+ values: [
+ { key: "alpn", value: ["h2", "h3-26"] },
+ { key: "port", value: h2Port },
+ { key: "echconfig", value: "456..." },
+ ],
+ },
+ },
+ {
+ name: "test.reset.com",
+ ttl: 55,
+ type: "HTTPS",
+ flush: false,
+ data: {
+ priority: 2,
+ name: "test.reset2.com",
+ values: [
+ { key: "alpn", value: ["h2", "h3-26"] },
+ { key: "echconfig", value: "456..." },
+ ],
+ },
+ },
+ ],
+ });
+
+ await new TRRDNSListener("test.reset.com", {
+ type: Ci.nsIDNSService.RESOLVE_TYPE_HTTPSSVC,
+ });
+
+ // After this request, test.reset1.com and test.reset2.com should be both in
+ // the exclusion list.
+ let chan = makeChan(`https://test.reset.com:${h2Port}/server-timing`);
+ await channelOpenPromise(chan, CL_EXPECT_LATE_FAILURE | CL_ALLOW_UNKNOWN_CL);
+
+ // This request should be also failed, because all records are excluded.
+ chan = makeChan(`https://test.reset.com:${h2Port}/server-timing`);
+ await channelOpenPromise(chan, CL_EXPECT_LATE_FAILURE | CL_ALLOW_UNKNOWN_CL);
+
+ await trrServer.registerDoHAnswers("test.reset1.com", "A", {
+ answers: [
+ {
+ name: "test.reset1.com",
+ ttl: 55,
+ type: "A",
+ flush: false,
+ data: "127.0.0.1",
+ },
+ ],
+ });
+
+ Services.prefs.setBoolPref(
+ "network.dns.httpssvc.reset_exclustion_list",
+ true
+ );
+
+ // After enable network.dns.httpssvc.reset_exclustion_list and register
+ // A record for test.reset1.com, this request should be succeeded.
+ chan = makeChan(`https://test.reset.com:${h2Port}/server-timing`);
+ await channelOpenPromise(chan);
+
+ await trrServer.stop();
+});
+
+// Simply test if we can connect to H3 server.
+add_task(async function testH3Connection() {
+ trrServer = new TRRServer();
+ await trrServer.start();
+ Services.prefs.setIntPref("network.trr.mode", 3);
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ `https://foo.example.com:${trrServer.port}/dns-query`
+ );
+ Services.prefs.setBoolPref("network.http.http3.enable", true);
+
+ Services.prefs.setIntPref(
+ "network.dns.httpssvc.http3_fast_fallback_timeout",
+ 100
+ );
+
+ await trrServer.registerDoHAnswers("test.h3.com", "HTTPS", {
+ answers: [
+ {
+ name: "test.h3.com",
+ ttl: 55,
+ type: "HTTPS",
+ flush: false,
+ data: {
+ priority: 1,
+ name: "www.h3.com",
+ values: [
+ { key: "alpn", value: "h3-29" },
+ { key: "port", value: h3Port },
+ { key: "echconfig", value: "456..." },
+ ],
+ },
+ },
+ ],
+ });
+
+ await trrServer.registerDoHAnswers("www.h3.com", "A", {
+ answers: [
+ {
+ name: "www.h3.com",
+ ttl: 55,
+ type: "A",
+ flush: false,
+ data: "127.0.0.1",
+ },
+ ],
+ });
+
+ await new TRRDNSListener("test.h3.com", {
+ type: Ci.nsIDNSService.RESOLVE_TYPE_HTTPSSVC,
+ });
+
+ let chan = makeChan(`https://test.h3.com`);
+ let [req] = await channelOpenPromise(chan);
+ Assert.equal(req.protocolVersion, "h3-29");
+ let internal = req.QueryInterface(Ci.nsIHttpChannelInternal);
+ Assert.equal(internal.remotePort, h3Port);
+
+ await trrServer.stop();
+});
+
+add_task(async function testFastfallbackToH2() {
+ trrServer = new TRRServer();
+ await trrServer.start();
+ Services.prefs.setIntPref("network.trr.mode", 3);
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ `https://foo.example.com:${trrServer.port}/dns-query`
+ );
+ Services.prefs.setBoolPref("network.http.http3.enable", true);
+ // Use a short timeout to make sure the fast fallback timer will be triggered.
+ Services.prefs.setIntPref(
+ "network.dns.httpssvc.http3_fast_fallback_timeout",
+ 1
+ );
+ Services.prefs.setCharPref(
+ "network.dns.localDomains",
+ "test.fastfallback1.com"
+ );
+
+ await trrServer.registerDoHAnswers("test.fastfallback.com", "HTTPS", {
+ answers: [
+ {
+ name: "test.fastfallback.com",
+ ttl: 55,
+ type: "HTTPS",
+ flush: false,
+ data: {
+ priority: 1,
+ name: "test.fastfallback1.com",
+ values: [
+ { key: "alpn", value: "h3-29" },
+ { key: "port", value: h3NoResponsePort },
+ { key: "echconfig", value: "456..." },
+ ],
+ },
+ },
+ {
+ name: "test.fastfallback.com",
+ ttl: 55,
+ type: "HTTPS",
+ flush: false,
+ data: {
+ priority: 2,
+ name: "test.fastfallback2.com",
+ values: [
+ { key: "alpn", value: "h2" },
+ { key: "port", value: h2Port },
+ { key: "echconfig", value: "456..." },
+ ],
+ },
+ },
+ ],
+ });
+
+ await trrServer.registerDoHAnswers("test.fastfallback2.com", "A", {
+ answers: [
+ {
+ name: "test.fastfallback2.com",
+ ttl: 55,
+ type: "A",
+ flush: false,
+ data: "127.0.0.1",
+ },
+ ],
+ });
+
+ await new TRRDNSListener("test.fastfallback.com", {
+ type: Ci.nsIDNSService.RESOLVE_TYPE_HTTPSSVC,
+ });
+
+ let chan = makeChan(`https://test.fastfallback.com/server-timing`);
+ let [req] = await channelOpenPromise(chan);
+ Assert.equal(req.protocolVersion, "h2");
+ let internal = req.QueryInterface(Ci.nsIHttpChannelInternal);
+ Assert.equal(internal.remotePort, h2Port);
+
+ // Use a longer timeout to test the case that the timer is canceled.
+ Services.prefs.setIntPref(
+ "network.dns.httpssvc.http3_fast_fallback_timeout",
+ 5000
+ );
+
+ chan = makeChan(`https://test.fastfallback.com/server-timing`);
+ [req] = await channelOpenPromise(chan);
+ Assert.equal(req.protocolVersion, "h2");
+ internal = req.QueryInterface(Ci.nsIHttpChannelInternal);
+ Assert.equal(internal.remotePort, h2Port);
+
+ await trrServer.stop();
+});
+
+// Test when we fail to establish H3 connection.
+add_task(async function testFailedH3Connection() {
+ trrServer = new TRRServer();
+ await trrServer.start();
+ Services.dns.clearCache(true);
+ Services.prefs.setIntPref("network.trr.mode", 3);
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ `https://foo.example.com:${trrServer.port}/dns-query`
+ );
+ Services.prefs.setBoolPref("network.http.http3.enable", true);
+ Services.prefs.setIntPref(
+ "network.dns.httpssvc.http3_fast_fallback_timeout",
+ 0
+ );
+
+ await trrServer.registerDoHAnswers("test.h3.org", "HTTPS", {
+ answers: [
+ {
+ name: "test.h3.org",
+ ttl: 55,
+ type: "HTTPS",
+ flush: false,
+ data: {
+ priority: 1,
+ name: "www.h3.org",
+ values: [
+ { key: "alpn", value: "h3-29" },
+ { key: "port", value: h3Port },
+ { key: "echconfig", value: "456..." },
+ ],
+ },
+ },
+ ],
+ });
+
+ await new TRRDNSListener("test.h3.org", {
+ type: Ci.nsIDNSService.RESOLVE_TYPE_HTTPSSVC,
+ });
+
+ let chan = makeChan(`https://test.h3.org`);
+ await channelOpenPromise(chan, CL_EXPECT_LATE_FAILURE | CL_ALLOW_UNKNOWN_CL);
+
+ await trrServer.stop();
+});
+
+// Test we don't use the service mode record whose domain is in
+// http3 excluded list.
+add_task(async function testHttp3ExcludedList() {
+ trrServer = new TRRServer();
+ await trrServer.start();
+ Services.dns.clearCache(true);
+ Services.prefs.setIntPref("network.trr.mode", 3);
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ `https://foo.example.com:${trrServer.port}/dns-query`
+ );
+ Services.prefs.setBoolPref("network.http.http3.enable", true);
+ Services.prefs.setIntPref(
+ "network.dns.httpssvc.http3_fast_fallback_timeout",
+ 0
+ );
+
+ Services.prefs.setCharPref(
+ "network.http.http3.alt-svc-mapping-for-testing",
+ "www.h3_fail.org;h3-29=:" + h3Port
+ );
+
+ // This will fail because there is no address record for www.h3_fail.org.
+ let chan = makeChan(`https://www.h3_fail.org`);
+ await channelOpenPromise(chan, CL_EXPECT_LATE_FAILURE | CL_ALLOW_UNKNOWN_CL);
+
+ // Now www.h3_fail.org should be already excluded, so the second record
+ // foo.example.com will be selected.
+ await trrServer.registerDoHAnswers("test.h3_excluded.org", "HTTPS", {
+ answers: [
+ {
+ name: "test.h3_excluded.org",
+ ttl: 55,
+ type: "HTTPS",
+ flush: false,
+ data: {
+ priority: 1,
+ name: "www.h3_fail.org",
+ values: [
+ { key: "alpn", value: "h3-29" },
+ { key: "port", value: h3Port },
+ ],
+ },
+ },
+ {
+ name: "test.h3_excluded.org",
+ ttl: 55,
+ type: "HTTPS",
+ flush: false,
+ data: {
+ priority: 2,
+ name: "foo.example.com",
+ values: [
+ { key: "alpn", value: "h3-29" },
+ { key: "port", value: h3Port },
+ ],
+ },
+ },
+ ],
+ });
+
+ await new TRRDNSListener("test.h3_excluded.org", {
+ type: Ci.nsIDNSService.RESOLVE_TYPE_HTTPSSVC,
+ });
+
+ chan = makeChan(`https://test.h3_excluded.org`);
+ let [req] = await channelOpenPromise(chan);
+ Assert.equal(req.protocolVersion, "h3-29");
+ let internal = req.QueryInterface(Ci.nsIHttpChannelInternal);
+ Assert.equal(internal.remotePort, h3Port);
+
+ await trrServer.stop();
+});
+
+add_task(async function testAllRecordsInHttp3ExcludedList() {
+ trrServer = new TRRServer();
+ await trrServer.start();
+ Services.dns.clearCache(true);
+ Services.prefs.setIntPref("network.trr.mode", 3);
+ Services.prefs.setBoolPref("network.dns.http3_echconfig.enabled", true);
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ `https://foo.example.com:${trrServer.port}/dns-query`
+ );
+ Services.prefs.setBoolPref("network.http.http3.enable", true);
+ Services.prefs.setIntPref(
+ "network.dns.httpssvc.http3_fast_fallback_timeout",
+ 0
+ );
+
+ Services.prefs.setCharPref(
+ "network.http.http3.alt-svc-mapping-for-testing",
+ "www.h3_fail1.org;h3-29=:" + h3Port
+ );
+
+ await trrServer.registerDoHAnswers("www.h3_all_excluded.org", "A", {
+ answers: [
+ {
+ name: "www.h3_all_excluded.org",
+ ttl: 55,
+ type: "A",
+ flush: false,
+ data: "127.0.0.1",
+ },
+ ],
+ });
+
+ // Test we can connect to www.h3_all_excluded.org sucessfully.
+ let chan = makeChan(
+ `https://www.h3_all_excluded.org:${h2Port}/server-timing`
+ );
+
+ let [req] = await channelOpenPromise(chan);
+
+ // Test if this request is done by h2.
+ Assert.equal(req.getResponseHeader("x-connection-http2"), "yes");
+
+ // This will fail because there is no address record for www.h3_fail1.org.
+ chan = makeChan(`https://www.h3_fail1.org`);
+ await channelOpenPromise(chan, CL_EXPECT_LATE_FAILURE | CL_ALLOW_UNKNOWN_CL);
+
+ Services.prefs.setCharPref(
+ "network.http.http3.alt-svc-mapping-for-testing",
+ "www.h3_fail2.org;h3-29=:" + h3Port
+ );
+
+ // This will fail because there is no address record for www.h3_fail2.org.
+ chan = makeChan(`https://www.h3_fail2.org`);
+ await channelOpenPromise(chan, CL_EXPECT_LATE_FAILURE | CL_ALLOW_UNKNOWN_CL);
+
+ await trrServer.registerDoHAnswers("www.h3_all_excluded.org", "HTTPS", {
+ answers: [
+ {
+ name: "www.h3_all_excluded.org",
+ ttl: 55,
+ type: "HTTPS",
+ flush: false,
+ data: {
+ priority: 1,
+ name: "www.h3_fail1.org",
+ values: [
+ { key: "alpn", value: "h3-29" },
+ { key: "port", value: h3Port },
+ { key: "echconfig", value: "456..." },
+ ],
+ },
+ },
+ {
+ name: "www.h3_all_excluded.org",
+ ttl: 55,
+ type: "HTTPS",
+ flush: false,
+ data: {
+ priority: 2,
+ name: "www.h3_fail2.org",
+ values: [
+ { key: "alpn", value: "h3-29" },
+ { key: "port", value: h3Port },
+ { key: "echconfig", value: "456..." },
+ ],
+ },
+ },
+ ],
+ });
+
+ await new TRRDNSListener("www.h3_all_excluded.org", {
+ type: Ci.nsIDNSService.RESOLVE_TYPE_HTTPSSVC,
+ });
+
+ Services.dns.clearCache(true);
+ Services.prefs.setIntPref("network.http.speculative-parallel-limit", 0);
+ Services.obs.notifyObservers(null, "net:prune-all-connections");
+
+ // All HTTPS RRs are in http3 excluded list and all records are failed to
+ // connect, so don't fallback to the origin one.
+ chan = makeChan(`https://www.h3_all_excluded.org:${h2Port}/server-timing`);
+ await channelOpenPromise(chan, CL_EXPECT_LATE_FAILURE | CL_ALLOW_UNKNOWN_CL);
+
+ await trrServer.registerDoHAnswers("www.h3_fail1.org", "A", {
+ answers: [
+ {
+ name: "www.h3_fail1.org",
+ ttl: 55,
+ type: "A",
+ flush: false,
+ data: "127.0.0.1",
+ },
+ ],
+ });
+
+ // The the case that when all records are in http3 excluded list, we still
+ // give the first record one more shot.
+ chan = makeChan(`https://www.h3_all_excluded.org`);
+ [req] = await channelOpenPromise(chan);
+ Assert.equal(req.protocolVersion, "h3-29");
+ let internal = req.QueryInterface(Ci.nsIHttpChannelInternal);
+ Assert.equal(internal.remotePort, h3Port);
+
+ await trrServer.stop();
+});
+
+WebSocketListener.prototype = {
+ onAcknowledge(aContext, aSize) {},
+ onBinaryMessageAvailable(aContext, aMsg) {},
+ onMessageAvailable(aContext, aMsg) {},
+ onServerClose(aContext, aCode, aReason) {},
+ onStart(aContext) {
+ this.finish();
+ },
+ onStop(aContext, aStatusCode) {},
+};
+
+add_task(async function testUpgradeNotUsingHTTPSRR() {
+ trrServer = new TRRServer();
+ await trrServer.start();
+ Services.prefs.setIntPref("network.trr.mode", 3);
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ `https://foo.example.com:${trrServer.port}/dns-query`
+ );
+
+ await trrServer.registerDoHAnswers("test.ws.com", "HTTPS", {
+ answers: [
+ {
+ name: "test.ws.com",
+ ttl: 55,
+ type: "HTTPS",
+ flush: false,
+ data: {
+ priority: 1,
+ name: "test.ws1.com",
+ values: [{ key: "port", value: ["8888"] }],
+ },
+ },
+ ],
+ });
+
+ await new TRRDNSListener("test.ws.com", {
+ type: Ci.nsIDNSService.RESOLVE_TYPE_HTTPSSVC,
+ });
+
+ await trrServer.registerDoHAnswers("test.ws.com", "A", {
+ answers: [
+ {
+ name: "test.ws.com",
+ ttl: 55,
+ type: "A",
+ flush: false,
+ data: "127.0.0.1",
+ },
+ ],
+ });
+
+ let wssUri = "wss://test.ws.com:" + h2Port + "/websocket";
+ let chan = Cc["@mozilla.org/network/protocol;1?name=wss"].createInstance(
+ Ci.nsIWebSocketChannel
+ );
+ chan.initLoadInfo(
+ null, // aLoadingNode
+ Services.scriptSecurityManager.getSystemPrincipal(),
+ null, // aTriggeringPrincipal
+ Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
+ Ci.nsIContentPolicy.TYPE_DOCUMENT
+ );
+
+ var uri = Services.io.newURI(wssUri);
+ var wsListener = new WebSocketListener();
+ certOverrideService.setDisableAllSecurityChecksAndLetAttackersInterceptMyData(
+ false
+ );
+ await new Promise(resolve => {
+ wsListener.finish = resolve;
+ chan.asyncOpen(uri, wssUri, {}, 0, wsListener, null);
+ certOverrideService.setDisableAllSecurityChecksAndLetAttackersInterceptMyData(
+ true
+ );
+ });
+
+ await trrServer.stop();
+});
+
+// Test if we fallback to h2 with echConfig.
+add_task(async function testFallbackToH2WithEchConfig() {
+ trrServer = new TRRServer();
+ await trrServer.start();
+ Services.dns.clearCache(true);
+ Services.prefs.setIntPref("network.trr.mode", 3);
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ `https://foo.example.com:${trrServer.port}/dns-query`
+ );
+ Services.prefs.setBoolPref("network.http.http3.enable", true);
+ Services.prefs.setIntPref(
+ "network.dns.httpssvc.http3_fast_fallback_timeout",
+ 0
+ );
+
+ await trrServer.registerDoHAnswers("test.fallback.org", "HTTPS", {
+ answers: [
+ {
+ name: "test.fallback.org",
+ ttl: 55,
+ type: "HTTPS",
+ flush: false,
+ data: {
+ priority: 1,
+ name: "test.fallback.org",
+ values: [
+ { key: "alpn", value: ["h2", "h3-29"] },
+ { key: "port", value: h2Port },
+ { key: "echconfig", value: "456..." },
+ ],
+ },
+ },
+ ],
+ });
+
+ await trrServer.registerDoHAnswers("test.fallback.org", "A", {
+ answers: [
+ {
+ name: "test.fallback.org",
+ ttl: 55,
+ type: "A",
+ flush: false,
+ data: "127.0.0.1",
+ },
+ ],
+ });
+
+ await new TRRDNSListener("test.fallback.org", {
+ type: Ci.nsIDNSService.RESOLVE_TYPE_HTTPSSVC,
+ });
+
+ await new TRRDNSListener("test.fallback.org", "127.0.0.1");
+
+ let chan = makeChan(`https://test.fallback.org/server-timing`);
+ let [req] = await channelOpenPromise(chan);
+ // Test if this request is done by h2.
+ Assert.equal(req.getResponseHeader("x-connection-http2"), "yes");
+
+ await trrServer.stop();
+});