Bug 1857386 [wpt PR 42383] - Update wpt metadata, a=testonly
[gecko.git] / netwerk / test / unit / trr_common.js
blob2ddd55698302cd95261cd801055bd094a238c22b
1 /* This Source Code Form is subject to the terms of the Mozilla Public
2  * License, v. 2.0. If a copy of the MPL was not distributed with this
3  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 "use strict";
7 /* import-globals-from head_cache.js */
8 /* import-globals-from head_cookies.js */
9 /* import-globals-from head_trr.js */
10 /* import-globals-from head_http3.js */
12 const { TestUtils } = ChromeUtils.importESModule(
13   "resource://testing-common/TestUtils.sys.mjs"
16 const { HttpServer } = ChromeUtils.importESModule(
17   "resource://testing-common/httpd.sys.mjs"
20 const TRR_Domain = "foo.example.com";
22 const { MockRegistrar } = ChromeUtils.importESModule(
23   "resource://testing-common/MockRegistrar.sys.mjs"
26 const gOverride = Cc["@mozilla.org/network/native-dns-override;1"].getService(
27   Ci.nsINativeDNSResolverOverride
30 async function SetParentalControlEnabled(aEnabled) {
31   let parentalControlsService = {
32     parentalControlsEnabled: aEnabled,
33     QueryInterface: ChromeUtils.generateQI(["nsIParentalControlsService"]),
34   };
35   let cid = MockRegistrar.register(
36     "@mozilla.org/parental-controls-service;1",
37     parentalControlsService
38   );
39   Services.dns.reloadParentalControlEnabled();
40   MockRegistrar.unregister(cid);
43 let runningOHTTPTests = false;
44 let h2Port;
46 function setModeAndURIForODoH(mode, path) {
47   Services.prefs.setIntPref("network.trr.mode", mode);
48   if (path.substr(0, 4) == "doh?") {
49     path = path.replace("doh?", "odoh?");
50   }
52   Services.prefs.setCharPref("network.trr.odoh.target_path", `${path}`);
55 function setModeAndURIForOHTTP(mode, path, domain) {
56   Services.prefs.setIntPref("network.trr.mode", mode);
57   if (domain) {
58     Services.prefs.setCharPref(
59       "network.trr.ohttp.uri",
60       `https://${domain}:${h2Port}/${path}`
61     );
62   } else {
63     Services.prefs.setCharPref(
64       "network.trr.ohttp.uri",
65       `https://${TRR_Domain}:${h2Port}/${path}`
66     );
67   }
70 function setModeAndURI(mode, path, domain) {
71   if (runningOHTTPTests) {
72     setModeAndURIForOHTTP(mode, path, domain);
73   } else {
74     Services.prefs.setIntPref("network.trr.mode", mode);
75     if (domain) {
76       Services.prefs.setCharPref(
77         "network.trr.uri",
78         `https://${domain}:${h2Port}/${path}`
79       );
80     } else {
81       Services.prefs.setCharPref(
82         "network.trr.uri",
83         `https://${TRR_Domain}:${h2Port}/${path}`
84       );
85     }
86   }
89 async function test_A_record() {
90   info("Verifying a basic A record");
91   Services.dns.clearCache(true);
92   setModeAndURI(2, "doh?responseIP=2.2.2.2"); // TRR-first
93   await new TRRDNSListener("bar.example.com", "2.2.2.2");
95   info("Verifying a basic A record - without bootstrapping");
96   Services.dns.clearCache(true);
97   setModeAndURI(3, "doh?responseIP=3.3.3.3"); // TRR-only
99   // Clear bootstrap address and add DoH endpoint hostname to local domains
100   Services.prefs.clearUserPref("network.trr.bootstrapAddr");
101   Services.prefs.setCharPref("network.dns.localDomains", TRR_Domain);
103   await new TRRDNSListener("bar.example.com", "3.3.3.3");
105   Services.prefs.setCharPref("network.trr.bootstrapAddr", "127.0.0.1");
106   Services.prefs.clearUserPref("network.dns.localDomains");
108   info("Verify that the cached record is used when DoH endpoint is down");
109   // Don't clear the cache. That is what we're checking.
110   setModeAndURI(3, "404");
112   await new TRRDNSListener("bar.example.com", "3.3.3.3");
113   info("verify working credentials in DOH request");
114   Services.dns.clearCache(true);
115   setModeAndURI(3, "doh?responseIP=4.4.4.4&auth=true");
116   Services.prefs.setCharPref("network.trr.credentials", "user:password");
118   await new TRRDNSListener("bar.example.com", "4.4.4.4");
120   info("Verify failing credentials in DOH request");
121   Services.dns.clearCache(true);
122   setModeAndURI(3, "doh?responseIP=4.4.4.4&auth=true");
123   Services.prefs.setCharPref("network.trr.credentials", "evil:person");
125   let { inStatus } = await new TRRDNSListener(
126     "wrong.example.com",
127     undefined,
128     false
129   );
130   Assert.ok(
131     !Components.isSuccessCode(inStatus),
132     `${inStatus} should be an error code`
133   );
135   Services.prefs.clearUserPref("network.trr.credentials");
138 async function test_AAAA_records() {
139   info("Verifying AAAA record");
141   Services.dns.clearCache(true);
142   setModeAndURI(3, "doh?responseIP=2020:2020::2020&delayIPv4=100");
144   await new TRRDNSListener("aaaa.example.com", "2020:2020::2020");
146   Services.dns.clearCache(true);
147   setModeAndURI(3, "doh?responseIP=2020:2020::2020&delayIPv6=100");
149   await new TRRDNSListener("aaaa.example.com", "2020:2020::2020");
151   Services.dns.clearCache(true);
152   setModeAndURI(3, "doh?responseIP=2020:2020::2020");
154   await new TRRDNSListener("aaaa.example.com", "2020:2020::2020");
157 async function test_RFC1918() {
158   info("Verifying that RFC1918 address from the server is rejected by default");
159   Services.dns.clearCache(true);
160   setModeAndURI(3, "doh?responseIP=192.168.0.1");
162   let { inStatus } = await new TRRDNSListener(
163     "rfc1918.example.com",
164     undefined,
165     false
166   );
168   Assert.ok(
169     !Components.isSuccessCode(inStatus),
170     `${inStatus} should be an error code`
171   );
172   setModeAndURI(3, "doh?responseIP=::ffff:192.168.0.1");
173   ({ inStatus } = await new TRRDNSListener(
174     "rfc1918-ipv6.example.com",
175     undefined,
176     false
177   ));
178   Assert.ok(
179     !Components.isSuccessCode(inStatus),
180     `${inStatus} should be an error code`
181   );
183   info("Verify RFC1918 address from the server is fine when told so");
184   Services.dns.clearCache(true);
185   setModeAndURI(3, "doh?responseIP=192.168.0.1");
186   Services.prefs.setBoolPref("network.trr.allow-rfc1918", true);
187   await new TRRDNSListener("rfc1918.example.com", "192.168.0.1");
188   setModeAndURI(3, "doh?responseIP=::ffff:192.168.0.1");
190   await new TRRDNSListener("rfc1918-ipv6.example.com", "::ffff:192.168.0.1");
192   Services.prefs.clearUserPref("network.trr.allow-rfc1918");
195 async function test_GET_ECS() {
196   info("Verifying resolution via GET with ECS disabled");
197   Services.dns.clearCache(true);
198   // The template part should be discarded
199   setModeAndURI(3, "doh{?dns}");
200   Services.prefs.setBoolPref("network.trr.useGET", true);
201   Services.prefs.setBoolPref("network.trr.disable-ECS", true);
203   await new TRRDNSListener("ecs.example.com", "5.5.5.5");
205   info("Verifying resolution via GET with ECS enabled");
206   Services.dns.clearCache(true);
207   setModeAndURI(3, "doh");
208   Services.prefs.setBoolPref("network.trr.disable-ECS", false);
210   await new TRRDNSListener("get.example.com", "5.5.5.5");
212   Services.prefs.clearUserPref("network.trr.useGET");
213   Services.prefs.clearUserPref("network.trr.disable-ECS");
216 async function test_timeout_mode3() {
217   info("Verifying that a short timeout causes failure with a slow server");
218   Services.dns.clearCache(true);
219   // First, mode 3.
220   setModeAndURI(3, "doh?noResponse=true");
221   Services.prefs.setIntPref("network.trr.request_timeout_ms", 10);
222   Services.prefs.setIntPref("network.trr.request_timeout_mode_trronly_ms", 10);
224   let { inStatus } = await new TRRDNSListener(
225     "timeout.example.com",
226     undefined,
227     false
228   );
229   Assert.ok(
230     !Components.isSuccessCode(inStatus),
231     `${inStatus} should be an error code`
232   );
234   // Now for mode 2
235   Services.dns.clearCache(true);
236   setModeAndURI(2, "doh?noResponse=true");
238   await new TRRDNSListener("timeout.example.com", "127.0.0.1"); // Should fallback
240   Services.prefs.clearUserPref("network.trr.request_timeout_ms");
241   Services.prefs.clearUserPref("network.trr.request_timeout_mode_trronly_ms");
244 async function test_trr_retry() {
245   Services.dns.clearCache(true);
246   Services.prefs.setBoolPref("network.trr.strict_native_fallback", false);
248   info("Test fallback to native");
249   Services.prefs.setBoolPref("network.trr.retry_on_recoverable_errors", false);
250   setModeAndURI(2, "doh?noResponse=true");
251   Services.prefs.setIntPref("network.trr.request_timeout_ms", 10);
252   Services.prefs.setIntPref("network.trr.request_timeout_mode_trronly_ms", 10);
254   await new TRRDNSListener("timeout.example.com", {
255     expectedAnswer: "127.0.0.1",
256   });
258   Services.prefs.clearUserPref("network.trr.request_timeout_ms");
259   Services.prefs.clearUserPref("network.trr.request_timeout_mode_trronly_ms");
261   info("Test Retry Success");
262   Services.prefs.setBoolPref("network.trr.retry_on_recoverable_errors", true);
264   let chan = makeChan(
265     `https://foo.example.com:${h2Port}/reset-doh-request-count`,
266     Ci.nsIRequest.TRR_DISABLED_MODE
267   );
268   await new Promise(resolve =>
269     chan.asyncOpen(new ChannelListener(resolve, null))
270   );
272   setModeAndURI(2, "doh?responseIP=2.2.2.2&retryOnDecodeFailure=true");
273   await new TRRDNSListener("retry_ok.example.com", "2.2.2.2");
275   info("Test Retry Failed");
276   Services.dns.clearCache(true);
277   setModeAndURI(2, "doh?responseIP=2.2.2.2&corruptedAnswer=true");
278   await new TRRDNSListener("retry_ng.example.com", "127.0.0.1");
281 async function test_strict_native_fallback() {
282   Services.dns.clearCache(true);
283   Services.prefs.setBoolPref("network.trr.retry_on_recoverable_errors", true);
284   Services.prefs.setBoolPref("network.trr.strict_native_fallback", true);
286   info("First a timeout case");
287   setModeAndURI(2, "doh?noResponse=true");
288   Services.prefs.setIntPref("network.trr.request_timeout_ms", 10);
289   Services.prefs.setIntPref("network.trr.request_timeout_mode_trronly_ms", 10);
290   Services.prefs.setIntPref(
291     "network.trr.strict_fallback_request_timeout_ms",
292     10
293   );
295   Services.prefs.setBoolPref(
296     "network.trr.strict_native_fallback_allow_timeouts",
297     false
298   );
300   let { inStatus } = await new TRRDNSListener(
301     "timeout.example.com",
302     undefined,
303     false
304   );
305   Assert.ok(
306     !Components.isSuccessCode(inStatus),
307     `${inStatus} should be an error code`
308   );
309   Services.dns.clearCache(true);
310   await new TRRDNSListener("timeout.example.com", undefined, false);
312   Services.dns.clearCache(true);
313   Services.prefs.setBoolPref(
314     "network.trr.strict_native_fallback_allow_timeouts",
315     true
316   );
317   await new TRRDNSListener("timeout.example.com", {
318     expectedAnswer: "127.0.0.1",
319   });
321   Services.prefs.setBoolPref(
322     "network.trr.strict_native_fallback_allow_timeouts",
323     false
324   );
326   info("Now a connection error");
327   Services.dns.clearCache(true);
328   setModeAndURI(2, "doh?responseIP=2.2.2.2");
329   Services.prefs.clearUserPref("network.trr.request_timeout_ms");
330   Services.prefs.clearUserPref("network.trr.request_timeout_mode_trronly_ms");
331   Services.prefs.clearUserPref(
332     "network.trr.strict_fallback_request_timeout_ms"
333   );
334   ({ inStatus } = await new TRRDNSListener("closeme.com", undefined, false));
335   Assert.ok(
336     !Components.isSuccessCode(inStatus),
337     `${inStatus} should be an error code`
338   );
340   info("Now a decode error");
341   Services.dns.clearCache(true);
342   setModeAndURI(2, "doh?responseIP=2.2.2.2&corruptedAnswer=true");
343   ({ inStatus } = await new TRRDNSListener(
344     "bar.example.com",
345     undefined,
346     false
347   ));
348   Assert.ok(
349     !Components.isSuccessCode(inStatus),
350     `${inStatus} should be an error code`
351   );
353   if (!mozinfo.socketprocess_networking) {
354     // Confirmation state isn't passed cross-process.
355     info("Now with confirmation failed - should fallback");
356     Services.dns.clearCache(true);
357     setModeAndURI(2, "doh?responseIP=2.2.2.2&corruptedAnswer=true");
358     Services.prefs.setCharPref("network.trr.confirmationNS", "example.com");
359     await TestUtils.waitForCondition(
360       // 3 => CONFIRM_FAILED, 4 => CONFIRM_TRYING_FAILED
361       () =>
362         Services.dns.currentTrrConfirmationState == 3 ||
363         Services.dns.currentTrrConfirmationState == 4,
364       `Timed out waiting for confirmation failure. Currently ${Services.dns.currentTrrConfirmationState}`,
365       1,
366       5000
367     );
368     await new TRRDNSListener("bar.example.com", "127.0.0.1"); // Should fallback
369   }
371   info("Now a successful case.");
372   Services.dns.clearCache(true);
373   setModeAndURI(2, "doh?responseIP=2.2.2.2");
374   if (!mozinfo.socketprocess_networking) {
375     // Only need to reset confirmation state if we messed with it before.
376     Services.prefs.setCharPref("network.trr.confirmationNS", "skip");
377     await TestUtils.waitForCondition(
378       // 5 => CONFIRM_DISABLED
379       () => Services.dns.currentTrrConfirmationState == 5,
380       `Timed out waiting for confirmation disabled. Currently ${Services.dns.currentTrrConfirmationState}`,
381       1,
382       5000
383     );
384   }
385   await new TRRDNSListener("bar.example.com", "2.2.2.2");
387   info("Now without strict fallback mode, timeout case");
388   Services.dns.clearCache(true);
389   setModeAndURI(2, "doh?noResponse=true");
390   Services.prefs.setIntPref("network.trr.request_timeout_ms", 10);
391   Services.prefs.setIntPref("network.trr.request_timeout_mode_trronly_ms", 10);
392   Services.prefs.setIntPref(
393     "network.trr.strict_fallback_request_timeout_ms",
394     10
395   );
396   Services.prefs.setBoolPref("network.trr.strict_native_fallback", false);
398   await new TRRDNSListener("timeout.example.com", "127.0.0.1"); // Should fallback
400   info("Now a connection error");
401   Services.dns.clearCache(true);
402   setModeAndURI(2, "doh?responseIP=2.2.2.2");
403   Services.prefs.clearUserPref("network.trr.request_timeout_ms");
404   Services.prefs.clearUserPref("network.trr.request_timeout_mode_trronly_ms");
405   Services.prefs.clearUserPref(
406     "network.trr.strict_fallback_request_timeout_ms"
407   );
408   await new TRRDNSListener("closeme.com", "127.0.0.1"); // Should fallback
410   info("Now a decode error");
411   Services.dns.clearCache(true);
412   setModeAndURI(2, "doh?responseIP=2.2.2.2&corruptedAnswer=true");
413   await new TRRDNSListener("bar.example.com", "127.0.0.1"); // Should fallback
415   Services.prefs.setBoolPref("network.trr.strict_native_fallback", false);
416   Services.prefs.clearUserPref("network.trr.request_timeout_ms");
417   Services.prefs.clearUserPref("network.trr.request_timeout_mode_trronly_ms");
418   Services.prefs.clearUserPref(
419     "network.trr.strict_fallback_request_timeout_ms"
420   );
423 async function test_no_answers_fallback() {
424   info("Verfiying that we correctly fallback to Do53 when no answers from DoH");
425   Services.dns.clearCache(true);
426   setModeAndURI(2, "doh?responseIP=none"); // TRR-first
428   await new TRRDNSListener("confirm.example.com", "127.0.0.1");
430   info("Now in strict mode - no fallback");
431   Services.prefs.setBoolPref("network.trr.strict_native_fallback", true);
432   Services.dns.clearCache(true);
433   await new TRRDNSListener("confirm.example.com", "127.0.0.1");
434   Services.prefs.setBoolPref("network.trr.strict_native_fallback", false);
437 async function test_404_fallback() {
438   info("Verfiying that we correctly fallback to Do53 when DoH sends 404");
439   Services.dns.clearCache(true);
440   setModeAndURI(2, "404"); // TRR-first
442   await new TRRDNSListener("test404.example.com", "127.0.0.1");
444   info("Now in strict mode - no fallback");
445   Services.prefs.setBoolPref("network.trr.strict_native_fallback", true);
446   Services.dns.clearCache(true);
447   let { inStatus } = await new TRRDNSListener("test404.example.com", {
448     expectedSuccess: false,
449   });
450   Assert.ok(
451     !Components.isSuccessCode(inStatus),
452     `${inStatus} should be an error code`
453   );
454   Services.prefs.setBoolPref("network.trr.strict_native_fallback", false);
457 async function test_mode_1_and_4() {
458   info("Verifying modes 1 and 4 are treated as TRR-off");
459   for (let mode of [1, 4]) {
460     Services.dns.clearCache(true);
461     setModeAndURI(mode, "doh?responseIP=2.2.2.2");
462     Assert.equal(
463       Services.dns.currentTrrMode,
464       5,
465       "Effective TRR mode should be 5"
466     );
467   }
470 async function test_CNAME() {
471   info("Checking that we follow a CNAME correctly");
472   Services.dns.clearCache(true);
473   // The dns-cname path alternates between sending us a CNAME pointing to
474   // another domain, and an A record. If we follow the cname correctly, doing
475   // a lookup with this path as the DoH URI should resolve to that A record.
476   setModeAndURI(3, "dns-cname");
478   await new TRRDNSListener("cname.example.com", "99.88.77.66");
480   info("Verifying that we bail out when we're thrown into a CNAME loop");
481   Services.dns.clearCache(true);
482   // First mode 3.
483   setModeAndURI(3, "doh?responseIP=none&cnameloop=true");
485   let { inStatus } = await new TRRDNSListener(
486     "test18.example.com",
487     undefined,
488     false
489   );
490   Assert.ok(
491     !Components.isSuccessCode(inStatus),
492     `${inStatus} should be an error code`
493   );
495   // Now mode 2.
496   Services.dns.clearCache(true);
497   setModeAndURI(2, "doh?responseIP=none&cnameloop=true");
499   await new TRRDNSListener("test20.example.com", "127.0.0.1"); // Should fallback
501   info("Check that we correctly handle CNAME bundled with an A record");
502   Services.dns.clearCache(true);
503   // "dns-cname-a" path causes server to send a CNAME as well as an A record
504   setModeAndURI(3, "dns-cname-a");
506   await new TRRDNSListener("cname-a.example.com", "9.8.7.6");
509 async function test_name_mismatch() {
510   info("Verify that records that don't match the requested name are rejected");
511   Services.dns.clearCache(true);
512   // Setting hostname param tells server to always send record for bar.example.com
513   // regardless of what was requested.
514   setModeAndURI(3, "doh?hostname=mismatch.example.com");
516   let { inStatus } = await new TRRDNSListener(
517     "bar.example.com",
518     undefined,
519     false
520   );
521   Assert.ok(
522     !Components.isSuccessCode(inStatus),
523     `${inStatus} should be an error code`
524   );
527 async function test_mode_2() {
528   info("Checking that TRR result is used in mode 2");
529   Services.dns.clearCache(true);
530   setModeAndURI(2, "doh?responseIP=192.192.192.192");
531   Services.prefs.setCharPref("network.trr.excluded-domains", "");
532   Services.prefs.setCharPref("network.trr.builtin-excluded-domains", "");
534   await new TRRDNSListener("bar.example.com", "192.192.192.192");
536   info("Now in strict mode");
537   Services.prefs.setBoolPref("network.trr.strict_native_fallback", true);
538   Services.dns.clearCache(true);
539   await new TRRDNSListener("bar.example.com", "192.192.192.192");
540   Services.prefs.setBoolPref("network.trr.strict_native_fallback", false);
543 async function test_excluded_domains() {
544   info("Checking that Do53 is used for names in excluded-domains list");
545   for (let strictMode of [true, false]) {
546     info("Strict mode: " + strictMode);
547     Services.prefs.setBoolPref(
548       "network.trr.strict_native_fallback",
549       strictMode
550     );
551     Services.dns.clearCache(true);
552     setModeAndURI(2, "doh?responseIP=192.192.192.192");
553     Services.prefs.setCharPref(
554       "network.trr.excluded-domains",
555       "bar.example.com"
556     );
558     await new TRRDNSListener("bar.example.com", "127.0.0.1"); // Do53 result
560     Services.dns.clearCache(true);
561     Services.prefs.setCharPref("network.trr.excluded-domains", "example.com");
563     await new TRRDNSListener("bar.example.com", "127.0.0.1");
565     Services.dns.clearCache(true);
566     Services.prefs.setCharPref(
567       "network.trr.excluded-domains",
568       "foo.test.com, bar.example.com"
569     );
570     await new TRRDNSListener("bar.example.com", "127.0.0.1");
572     Services.dns.clearCache(true);
573     Services.prefs.setCharPref(
574       "network.trr.excluded-domains",
575       "bar.example.com, foo.test.com"
576     );
578     await new TRRDNSListener("bar.example.com", "127.0.0.1");
580     Services.prefs.clearUserPref("network.trr.excluded-domains");
581   }
584 function topicObserved(topic) {
585   return new Promise(resolve => {
586     let observer = {
587       QueryInterface: ChromeUtils.generateQI(["nsIObserver"]),
588       observe(aSubject, aTopic, aData) {
589         if (aTopic == topic) {
590           Services.obs.removeObserver(observer, topic);
591           resolve(aData);
592         }
593       },
594     };
595     Services.obs.addObserver(observer, topic);
596   });
599 async function test_captiveportal_canonicalURL() {
600   info("Check that captivedetect.canonicalURL is resolved via native DNS");
601   for (let strictMode of [true, false]) {
602     info("Strict mode: " + strictMode);
603     Services.prefs.setBoolPref(
604       "network.trr.strict_native_fallback",
605       strictMode
606     );
607     Services.dns.clearCache(true);
608     setModeAndURI(2, "doh?responseIP=2.2.2.2");
610     const cpServer = new HttpServer();
611     cpServer.registerPathHandler(
612       "/cp",
613       function handleRawData(request, response) {
614         response.setHeader("Content-Type", "text/plain", false);
615         response.setHeader("Cache-Control", "no-cache", false);
616         response.bodyOutputStream.write("data", 4);
617       }
618     );
619     cpServer.start(-1);
620     cpServer.identity.setPrimary(
621       "http",
622       "detectportal.firefox.com",
623       cpServer.identity.primaryPort
624     );
625     let cpPromise = topicObserved("captive-portal-login");
627     Services.prefs.setCharPref(
628       "captivedetect.canonicalURL",
629       `http://detectportal.firefox.com:${cpServer.identity.primaryPort}/cp`
630     );
631     Services.prefs.setBoolPref("network.captive-portal-service.testMode", true);
632     Services.prefs.setBoolPref("network.captive-portal-service.enabled", true);
634     // The captive portal has to have used native DNS, otherwise creating
635     // a socket to a non-local IP would trigger a crash.
636     await cpPromise;
637     // Simply resolving the captive portal domain should still use TRR
638     await new TRRDNSListener("detectportal.firefox.com", "2.2.2.2");
640     Services.prefs.clearUserPref("network.captive-portal-service.enabled");
641     Services.prefs.clearUserPref("network.captive-portal-service.testMode");
642     Services.prefs.clearUserPref("captivedetect.canonicalURL");
644     await new Promise(resolve => cpServer.stop(resolve));
645   }
648 async function test_parentalcontrols() {
649   info("Check that DoH isn't used when parental controls are enabled");
650   Services.dns.clearCache(true);
651   setModeAndURI(2, "doh?responseIP=2.2.2.2");
652   await SetParentalControlEnabled(true);
653   await new TRRDNSListener("www.example.com", "127.0.0.1");
654   await SetParentalControlEnabled(false);
656   info("Now in strict mode");
657   Services.prefs.setBoolPref("network.trr.strict_native_fallback", true);
658   Services.dns.clearCache(true);
659   setModeAndURI(2, "doh?responseIP=2.2.2.2");
660   await SetParentalControlEnabled(true);
661   await new TRRDNSListener("www.example.com", "127.0.0.1");
662   await SetParentalControlEnabled(false);
663   Services.prefs.setBoolPref("network.trr.strict_native_fallback", false);
666 async function test_builtin_excluded_domains() {
667   info("Verifying Do53 is used for domains in builtin-excluded-domians list");
668   for (let strictMode of [true, false]) {
669     info("Strict mode: " + strictMode);
670     Services.prefs.setBoolPref(
671       "network.trr.strict_native_fallback",
672       strictMode
673     );
674     Services.dns.clearCache(true);
675     setModeAndURI(2, "doh?responseIP=2.2.2.2");
677     Services.prefs.setCharPref("network.trr.excluded-domains", "");
678     Services.prefs.setCharPref(
679       "network.trr.builtin-excluded-domains",
680       "bar.example.com"
681     );
682     await new TRRDNSListener("bar.example.com", "127.0.0.1");
684     Services.dns.clearCache(true);
685     Services.prefs.setCharPref(
686       "network.trr.builtin-excluded-domains",
687       "example.com"
688     );
689     await new TRRDNSListener("bar.example.com", "127.0.0.1");
691     Services.dns.clearCache(true);
692     Services.prefs.setCharPref(
693       "network.trr.builtin-excluded-domains",
694       "foo.test.com, bar.example.com"
695     );
696     await new TRRDNSListener("bar.example.com", "127.0.0.1");
697     await new TRRDNSListener("foo.test.com", "127.0.0.1");
698   }
701 async function test_excluded_domains_mode3() {
702   info("Checking  Do53 is used for names in excluded-domains list in mode 3");
703   Services.dns.clearCache(true);
704   setModeAndURI(3, "doh?responseIP=192.192.192.192");
705   Services.prefs.setCharPref("network.trr.excluded-domains", "");
706   Services.prefs.setCharPref("network.trr.builtin-excluded-domains", "");
708   await new TRRDNSListener("excluded", "192.192.192.192", true);
710   Services.dns.clearCache(true);
711   Services.prefs.setCharPref("network.trr.excluded-domains", "excluded");
713   await new TRRDNSListener("excluded", "127.0.0.1");
715   // Test .local
716   Services.dns.clearCache(true);
717   Services.prefs.setCharPref("network.trr.excluded-domains", "excluded,local");
719   await new TRRDNSListener("test.local", "127.0.0.1");
721   // Test .other
722   Services.dns.clearCache(true);
723   Services.prefs.setCharPref(
724     "network.trr.excluded-domains",
725     "excluded,local,other"
726   );
728   await new TRRDNSListener("domain.other", "127.0.0.1");
731 async function test25e() {
732   info("Check captivedetect.canonicalURL is resolved via native DNS in mode 3");
733   Services.dns.clearCache(true);
734   setModeAndURI(3, "doh?responseIP=192.192.192.192");
736   const cpServer = new HttpServer();
737   cpServer.registerPathHandler(
738     "/cp",
739     function handleRawData(request, response) {
740       response.setHeader("Content-Type", "text/plain", false);
741       response.setHeader("Cache-Control", "no-cache", false);
742       response.bodyOutputStream.write("data", 4);
743     }
744   );
745   cpServer.start(-1);
746   cpServer.identity.setPrimary(
747     "http",
748     "detectportal.firefox.com",
749     cpServer.identity.primaryPort
750   );
751   let cpPromise = topicObserved("captive-portal-login");
753   Services.prefs.setCharPref(
754     "captivedetect.canonicalURL",
755     `http://detectportal.firefox.com:${cpServer.identity.primaryPort}/cp`
756   );
757   Services.prefs.setBoolPref("network.captive-portal-service.testMode", true);
758   Services.prefs.setBoolPref("network.captive-portal-service.enabled", true);
760   // The captive portal has to have used native DNS, otherwise creating
761   // a socket to a non-local IP would trigger a crash.
762   await cpPromise;
763   // // Simply resolving the captive portal domain should still use TRR
764   await new TRRDNSListener("detectportal.firefox.com", "192.192.192.192");
766   Services.prefs.clearUserPref("network.captive-portal-service.enabled");
767   Services.prefs.clearUserPref("network.captive-portal-service.testMode");
768   Services.prefs.clearUserPref("captivedetect.canonicalURL");
770   await new Promise(resolve => cpServer.stop(resolve));
773 async function test_parentalcontrols_mode3() {
774   info("Check DoH isn't used when parental controls are enabled in mode 3");
775   Services.dns.clearCache(true);
776   setModeAndURI(3, "doh?responseIP=192.192.192.192");
777   await SetParentalControlEnabled(true);
778   await new TRRDNSListener("www.example.com", "127.0.0.1");
779   await SetParentalControlEnabled(false);
782 async function test_builtin_excluded_domains_mode3() {
783   info("Check Do53 used for domains in builtin-excluded-domians list, mode 3");
784   Services.dns.clearCache(true);
785   setModeAndURI(3, "doh?responseIP=192.192.192.192");
786   Services.prefs.setCharPref("network.trr.excluded-domains", "");
787   Services.prefs.setCharPref(
788     "network.trr.builtin-excluded-domains",
789     "excluded"
790   );
792   await new TRRDNSListener("excluded", "127.0.0.1");
794   // Test .local
795   Services.dns.clearCache(true);
796   Services.prefs.setCharPref(
797     "network.trr.builtin-excluded-domains",
798     "excluded,local"
799   );
801   await new TRRDNSListener("test.local", "127.0.0.1");
803   // Test .other
804   Services.dns.clearCache(true);
805   Services.prefs.setCharPref(
806     "network.trr.builtin-excluded-domains",
807     "excluded,local,other"
808   );
810   await new TRRDNSListener("domain.other", "127.0.0.1");
813 async function count_cookies() {
814   info("Check that none of the requests have set any cookies.");
815   Assert.equal(Services.cookies.countCookiesFromHost("example.com"), 0);
816   Assert.equal(Services.cookies.countCookiesFromHost("foo.example.com."), 0);
819 async function test_connection_closed() {
820   info("Check we handle it correctly when the connection is closed");
821   Services.dns.clearCache(true);
822   setModeAndURI(3, "doh?responseIP=2.2.2.2");
823   Services.prefs.setCharPref("network.trr.excluded-domains", "");
824   // We don't need to wait for 30 seconds for the request to fail
825   Services.prefs.setIntPref("network.trr.request_timeout_mode_trronly_ms", 500);
826   // bootstrap
827   Services.prefs.clearUserPref("network.dns.localDomains");
828   Services.prefs.setCharPref("network.trr.bootstrapAddr", "127.0.0.1");
830   await new TRRDNSListener("bar.example.com", "2.2.2.2");
832   // makes the TRR connection shut down.
833   let { inStatus } = await new TRRDNSListener("closeme.com", undefined, false);
834   Assert.ok(
835     !Components.isSuccessCode(inStatus),
836     `${inStatus} should be an error code`
837   );
838   await new TRRDNSListener("bar2.example.com", "2.2.2.2");
840   // No bootstrap this time
841   Services.prefs.clearUserPref("network.trr.bootstrapAddr");
843   Services.dns.clearCache(true);
844   Services.prefs.setCharPref("network.trr.excluded-domains", "excluded,local");
845   Services.prefs.setCharPref("network.dns.localDomains", TRR_Domain);
847   await new TRRDNSListener("bar.example.com", "2.2.2.2");
849   // makes the TRR connection shut down.
850   ({ inStatus } = await new TRRDNSListener("closeme.com", undefined, false));
851   Assert.ok(
852     !Components.isSuccessCode(inStatus),
853     `${inStatus} should be an error code`
854   );
855   await new TRRDNSListener("bar2.example.com", "2.2.2.2");
857   // No local domains either
858   Services.dns.clearCache(true);
859   Services.prefs.setCharPref("network.trr.excluded-domains", "excluded");
860   Services.prefs.clearUserPref("network.dns.localDomains");
861   Services.prefs.clearUserPref("network.trr.bootstrapAddr");
863   await new TRRDNSListener("bar.example.com", "2.2.2.2");
865   // makes the TRR connection shut down.
866   ({ inStatus } = await new TRRDNSListener("closeme.com", undefined, false));
867   Assert.ok(
868     !Components.isSuccessCode(inStatus),
869     `${inStatus} should be an error code`
870   );
871   await new TRRDNSListener("bar2.example.com", "2.2.2.2");
873   // Now make sure that even in mode 3 without a bootstrap address
874   // we are able to restart the TRR connection if it drops - the TRR service
875   // channel will use regular DNS to resolve the TRR address.
876   Services.dns.clearCache(true);
877   Services.prefs.setCharPref("network.trr.excluded-domains", "");
878   Services.prefs.setCharPref("network.trr.builtin-excluded-domains", "");
879   Services.prefs.clearUserPref("network.dns.localDomains");
880   Services.prefs.clearUserPref("network.trr.bootstrapAddr");
882   await new TRRDNSListener("bar.example.com", "2.2.2.2");
884   // makes the TRR connection shut down.
885   ({ inStatus } = await new TRRDNSListener("closeme.com", undefined, false));
886   Assert.ok(
887     !Components.isSuccessCode(inStatus),
888     `${inStatus} should be an error code`
889   );
890   Services.dns.clearCache(true);
891   await new TRRDNSListener("bar2.example.com", "2.2.2.2");
893   // This test exists to document what happens when we're in TRR only mode
894   // and we don't set a bootstrap address. We use DNS to resolve the
895   // initial URI, but if the connection fails, we don't fallback to DNS
896   Services.dns.clearCache(true);
897   setModeAndURI(2, "doh?responseIP=9.9.9.9");
898   Services.prefs.setCharPref("network.dns.localDomains", "closeme.com");
899   Services.prefs.clearUserPref("network.trr.bootstrapAddr");
901   await new TRRDNSListener("bar.example.com", "9.9.9.9");
903   // makes the TRR connection shut down. Should fallback to DNS
904   await new TRRDNSListener("closeme.com", "127.0.0.1");
905   // TRR should be back up again
906   await new TRRDNSListener("bar2.example.com", "9.9.9.9");
909 async function test_fetch_time() {
910   info("Verifying timing");
911   Services.dns.clearCache(true);
912   setModeAndURI(2, "doh?responseIP=2.2.2.2&delayIPv4=20");
914   await new TRRDNSListener("bar_time.example.com", "2.2.2.2", true, 20);
916   // gets an error from DoH. It will fall back to regular DNS. The TRR timing should be 0.
917   Services.dns.clearCache(true);
918   setModeAndURI(2, "404&delayIPv4=20");
920   await new TRRDNSListener("bar_time1.example.com", "127.0.0.1", true, 0);
922   // check an excluded domain. It should fall back to regular DNS. The TRR timing should be 0.
923   Services.prefs.setCharPref(
924     "network.trr.excluded-domains",
925     "bar_time2.example.com"
926   );
927   for (let strictMode of [true, false]) {
928     info("Strict mode: " + strictMode);
929     Services.prefs.setBoolPref(
930       "network.trr.strict_native_fallback",
931       strictMode
932     );
933     Services.dns.clearCache(true);
934     setModeAndURI(2, "doh?responseIP=2.2.2.2&delayIPv4=20");
935     await new TRRDNSListener("bar_time2.example.com", "127.0.0.1", true, 0);
936   }
938   Services.prefs.setCharPref("network.trr.excluded-domains", "");
940   // verify RFC1918 address from the server is rejected and the TRR timing will be not set because the response will be from the native resolver.
941   Services.dns.clearCache(true);
942   setModeAndURI(2, "doh?responseIP=192.168.0.1&delayIPv4=20");
943   await new TRRDNSListener("rfc1918_time.example.com", "127.0.0.1", true, 0);
946 async function test_fqdn() {
947   info("Test that we handle FQDN encoding and decoding properly");
948   Services.dns.clearCache(true);
949   setModeAndURI(3, "doh?responseIP=9.8.7.6");
951   await new TRRDNSListener("fqdn.example.org.", "9.8.7.6");
953   // GET
954   Services.dns.clearCache(true);
955   Services.prefs.setBoolPref("network.trr.useGET", true);
956   await new TRRDNSListener("fqdn_get.example.org.", "9.8.7.6");
958   Services.prefs.clearUserPref("network.trr.useGET");
961 async function test_ipv6_trr_fallback() {
962   info("Testing fallback with ipv6");
963   Services.dns.clearCache(true);
965   setModeAndURI(2, "doh?responseIP=4.4.4.4");
966   const override = Cc["@mozilla.org/network/native-dns-override;1"].getService(
967     Ci.nsINativeDNSResolverOverride
968   );
969   gOverride.addIPOverride("ipv6.host.com", "1:1::2");
971   // Should not fallback to Do53 because A request for ipv6.host.com returns
972   // 4.4.4.4
973   let { inStatus } = await new TRRDNSListener("ipv6.host.com", {
974     flags: Ci.nsIDNSService.RESOLVE_DISABLE_IPV4,
975     expectedSuccess: false,
976   });
977   equal(inStatus, Cr.NS_ERROR_UNKNOWN_HOST);
979   // This time both requests fail, so we do fall back
980   Services.dns.clearCache(true);
981   setModeAndURI(2, "doh?responseIP=none");
982   await new TRRDNSListener("ipv6.host.com", "1:1::2");
984   info("In strict mode, the lookup should fail when both reqs fail.");
985   Services.dns.clearCache(true);
986   Services.prefs.setBoolPref("network.trr.strict_native_fallback", true);
987   setModeAndURI(2, "doh?responseIP=none");
988   await new TRRDNSListener("ipv6.host.com", "1:1::2");
989   Services.prefs.setBoolPref("network.trr.strict_native_fallback", false);
991   override.clearOverrides();
994 async function test_ipv4_trr_fallback() {
995   info("Testing fallback with ipv4");
996   Services.dns.clearCache(true);
998   setModeAndURI(2, "doh?responseIP=1:2::3");
999   const override = Cc["@mozilla.org/network/native-dns-override;1"].getService(
1000     Ci.nsINativeDNSResolverOverride
1001   );
1002   gOverride.addIPOverride("ipv4.host.com", "3.4.5.6");
1004   // Should not fallback to Do53 because A request for ipv4.host.com returns
1005   // 1:2::3
1006   let { inStatus } = await new TRRDNSListener("ipv4.host.com", {
1007     flags: Ci.nsIDNSService.RESOLVE_DISABLE_IPV6,
1008     expectedSuccess: false,
1009   });
1010   equal(inStatus, Cr.NS_ERROR_UNKNOWN_HOST);
1012   // This time both requests fail, so we do fall back
1013   Services.dns.clearCache(true);
1014   setModeAndURI(2, "doh?responseIP=none");
1015   await new TRRDNSListener("ipv4.host.com", "3.4.5.6");
1017   // No fallback with strict mode.
1018   Services.dns.clearCache(true);
1019   Services.prefs.setBoolPref("network.trr.strict_native_fallback", true);
1020   setModeAndURI(2, "doh?responseIP=none");
1021   await new TRRDNSListener("ipv4.host.com", "3.4.5.6");
1022   Services.prefs.setBoolPref("network.trr.strict_native_fallback", false);
1024   override.clearOverrides();
1027 async function test_no_retry_without_doh() {
1028   info("Bug 1648147 - if the TRR returns 0.0.0.0 we should not retry with DNS");
1029   Services.prefs.setBoolPref("network.trr.fallback-on-zero-response", false);
1031   async function test(url, ip) {
1032     setModeAndURI(2, `doh?responseIP=${ip}`);
1034     // Requests to 0.0.0.0 are usually directed to localhost, so let's use a port
1035     // we know isn't being used - 666 (Doom)
1036     let chan = makeChan(url, Ci.nsIRequest.TRR_DEFAULT_MODE);
1037     let statusCounter = {
1038       statusCount: {},
1039       QueryInterface: ChromeUtils.generateQI([
1040         "nsIInterfaceRequestor",
1041         "nsIProgressEventSink",
1042       ]),
1043       getInterface(iid) {
1044         return this.QueryInterface(iid);
1045       },
1046       onProgress(request, progress, progressMax) {},
1047       onStatus(request, status, statusArg) {
1048         this.statusCount[status] = 1 + (this.statusCount[status] || 0);
1049       },
1050     };
1051     chan.notificationCallbacks = statusCounter;
1052     await new Promise(resolve =>
1053       chan.asyncOpen(new ChannelListener(resolve, null, CL_EXPECT_FAILURE))
1054     );
1055     equal(
1056       statusCounter.statusCount[0x4b000b],
1057       1,
1058       "Expecting only one instance of NS_NET_STATUS_RESOLVED_HOST"
1059     );
1060     equal(
1061       statusCounter.statusCount[0x4b0007],
1062       1,
1063       "Expecting only one instance of NS_NET_STATUS_CONNECTING_TO"
1064     );
1065   }
1067   for (let strictMode of [true, false]) {
1068     info("Strict mode: " + strictMode);
1069     Services.prefs.setBoolPref(
1070       "network.trr.strict_native_fallback",
1071       strictMode
1072     );
1073     await test(`http://unknown.ipv4.stuff:666/path`, "0.0.0.0");
1074     await test(`http://unknown.ipv6.stuff:666/path`, "::");
1075   }
1078 async function test_connection_reuse_and_cycling() {
1079   Services.dns.clearCache(true);
1080   Services.prefs.setIntPref("network.trr.request_timeout_ms", 500);
1081   Services.prefs.setIntPref(
1082     "network.trr.strict_fallback_request_timeout_ms",
1083     500
1084   );
1085   Services.prefs.setIntPref("network.trr.request_timeout_mode_trronly_ms", 500);
1087   setModeAndURI(2, `doh?responseIP=9.8.7.6`);
1088   Services.prefs.setBoolPref("network.trr.strict_native_fallback", true);
1089   Services.prefs.setCharPref("network.trr.confirmationNS", "example.com");
1090   await TestUtils.waitForCondition(
1091     // 2 => CONFIRM_OK
1092     () => Services.dns.currentTrrConfirmationState == 2,
1093     `Timed out waiting for confirmation success. Currently ${Services.dns.currentTrrConfirmationState}`,
1094     1,
1095     5000
1096   );
1098   // Setting conncycle=true in the URI. Server will start logging reqs.
1099   // We will do a specific sequence of lookups, then fetch the log from
1100   // the server and check that it matches what we'd expect.
1101   setModeAndURI(2, `doh?responseIP=9.8.7.6&conncycle=true`);
1102   await TestUtils.waitForCondition(
1103     // 2 => CONFIRM_OK
1104     () => Services.dns.currentTrrConfirmationState == 2,
1105     `Timed out waiting for confirmation success. Currently ${Services.dns.currentTrrConfirmationState}`,
1106     1,
1107     5000
1108   );
1109   // Confirmation upon uri-change will have created one req.
1111   // Two reqs for each bar1 and bar2 - A + AAAA.
1112   await new TRRDNSListener("bar1.example.org.", "9.8.7.6");
1113   await new TRRDNSListener("bar2.example.org.", "9.8.7.6");
1114   // Total so far: (1) + 2 + 2 = 5
1116   // Two reqs that fail, one Confirmation req, two retried reqs that succeed.
1117   await new TRRDNSListener("newconn.example.org.", "9.8.7.6");
1118   await TestUtils.waitForCondition(
1119     // 2 => CONFIRM_OK
1120     () => Services.dns.currentTrrConfirmationState == 2,
1121     `Timed out waiting for confirmation success. Currently ${Services.dns.currentTrrConfirmationState}`,
1122     1,
1123     5000
1124   );
1125   // Total so far: (5) + 2 + 1 + 2 = 10
1127   // Two reqs for each bar3 and bar4 .
1128   await new TRRDNSListener("bar3.example.org.", "9.8.7.6");
1129   await new TRRDNSListener("bar4.example.org.", "9.8.7.6");
1130   // Total so far: (10) + 2 + 2 = 14.
1132   // Two reqs that fail, one Confirmation req, two retried reqs that succeed.
1133   await new TRRDNSListener("newconn2.example.org.", "9.8.7.6");
1134   await TestUtils.waitForCondition(
1135     // 2 => CONFIRM_OK
1136     () => Services.dns.currentTrrConfirmationState == 2,
1137     `Timed out waiting for confirmation success. Currently ${Services.dns.currentTrrConfirmationState}`,
1138     1,
1139     5000
1140   );
1141   // Total so far: (14) + 2 + 1 + 2 = 19
1143   // Two reqs for each bar5 and bar6 .
1144   await new TRRDNSListener("bar5.example.org.", "9.8.7.6");
1145   await new TRRDNSListener("bar6.example.org.", "9.8.7.6");
1146   // Total so far: (19) + 2 + 2 = 23
1148   let chan = makeChan(
1149     `https://foo.example.com:${h2Port}/get-doh-req-port-log`,
1150     Ci.nsIRequest.TRR_DISABLED_MODE
1151   );
1152   let dohReqPortLog = await new Promise(resolve =>
1153     chan.asyncOpen(
1154       new ChannelListener((stuff, buffer) => {
1155         resolve(JSON.parse(buffer));
1156       })
1157     )
1158   );
1160   // Since the actual ports seen will vary at runtime, we use placeholders
1161   // instead in our expected output definition. For example, if two entries
1162   // both have "port1", it means they both should have the same port in the
1163   // server's log.
1164   // For reqs that fail and trigger a Confirmation + retry, the retried reqs
1165   // might not re-use the new connection created for Confirmation due to a
1166   // race, so we have an extra alternate expected port for them. This lets
1167   // us test that they use *a* new port even if it's not *the* new port.
1168   // Subsequent lookups are not affected, they will use the same conn as
1169   // the Confirmation req.
1170   let expectedLogTemplate = [
1171     ["example.com", "port1"],
1172     ["bar1.example.org", "port1"],
1173     ["bar1.example.org", "port1"],
1174     ["bar2.example.org", "port1"],
1175     ["bar2.example.org", "port1"],
1176     ["newconn.example.org", "port1"],
1177     ["newconn.example.org", "port1"],
1178     ["example.com", "port2"],
1179     ["newconn.example.org", "port2"],
1180     ["newconn.example.org", "port2"],
1181     ["bar3.example.org", "port2"],
1182     ["bar3.example.org", "port2"],
1183     ["bar4.example.org", "port2"],
1184     ["bar4.example.org", "port2"],
1185     ["newconn2.example.org", "port2"],
1186     ["newconn2.example.org", "port2"],
1187     ["example.com", "port3"],
1188     ["newconn2.example.org", "port3"],
1189     ["newconn2.example.org", "port3"],
1190     ["bar5.example.org", "port3"],
1191     ["bar5.example.org", "port3"],
1192     ["bar6.example.org", "port3"],
1193     ["bar6.example.org", "port3"],
1194   ];
1196   if (expectedLogTemplate.length != dohReqPortLog.length) {
1197     // This shouldn't happen, and if it does, we'll fail the assertion
1198     // below. But first dump the whole server-side log to help with
1199     // debugging should we see a failure. Most likely cause would be
1200     // that another consumer of TRR happened to make a request while
1201     // the test was running and polluted the log.
1202     info(dohReqPortLog);
1203   }
1205   equal(
1206     expectedLogTemplate.length,
1207     dohReqPortLog.length,
1208     "Correct number of req log entries"
1209   );
1211   let seenPorts = new Set();
1212   // This is essentially a symbol table - as we iterate through the log
1213   // we will assign the actual seen port numbers to the placeholders.
1214   let seenPortsByExpectedPort = new Map();
1216   for (let i = 0; i < expectedLogTemplate.length; i++) {
1217     let expectedName = expectedLogTemplate[i][0];
1218     let expectedPort = expectedLogTemplate[i][1];
1219     let seenName = dohReqPortLog[i][0];
1220     let seenPort = dohReqPortLog[i][1];
1221     info(`Checking log entry. Name: ${seenName}, Port: ${seenPort}`);
1222     equal(expectedName, seenName, "Name matches for entry " + i);
1223     if (!seenPortsByExpectedPort.has(expectedPort)) {
1224       ok(!seenPorts.has(seenPort), "Port should not have been previously used");
1225       seenPorts.add(seenPort);
1226       seenPortsByExpectedPort.set(expectedPort, seenPort);
1227     } else {
1228       equal(
1229         seenPort,
1230         seenPortsByExpectedPort.get(expectedPort),
1231         "Connection was reused as expected"
1232       );
1233     }
1234   }