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/. */
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"]),
35 let cid = MockRegistrar.register(
36 "@mozilla.org/parental-controls-service;1",
37 parentalControlsService
39 Services.dns.reloadParentalControlEnabled();
40 MockRegistrar.unregister(cid);
43 let runningOHTTPTests = false;
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?");
52 Services.prefs.setCharPref("network.trr.odoh.target_path", `${path}`);
55 function setModeAndURIForOHTTP(mode, path, domain) {
56 Services.prefs.setIntPref("network.trr.mode", mode);
58 Services.prefs.setCharPref(
59 "network.trr.ohttp.uri",
60 `https://${domain}:${h2Port}/${path}`
63 Services.prefs.setCharPref(
64 "network.trr.ohttp.uri",
65 `https://${TRR_Domain}:${h2Port}/${path}`
70 function setModeAndURI(mode, path, domain) {
71 if (runningOHTTPTests) {
72 setModeAndURIForOHTTP(mode, path, domain);
74 Services.prefs.setIntPref("network.trr.mode", mode);
76 Services.prefs.setCharPref(
78 `https://${domain}:${h2Port}/${path}`
81 Services.prefs.setCharPref(
83 `https://${TRR_Domain}:${h2Port}/${path}`
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(
131 !Components.isSuccessCode(inStatus),
132 `${inStatus} should be an error code`
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",
169 !Components.isSuccessCode(inStatus),
170 `${inStatus} should be an error code`
172 setModeAndURI(3, "doh?responseIP=::ffff:192.168.0.1");
173 ({ inStatus } = await new TRRDNSListener(
174 "rfc1918-ipv6.example.com",
179 !Components.isSuccessCode(inStatus),
180 `${inStatus} should be an error code`
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);
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",
230 !Components.isSuccessCode(inStatus),
231 `${inStatus} should be an error code`
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",
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);
265 `https://foo.example.com:${h2Port}/reset-doh-request-count`,
266 Ci.nsIRequest.TRR_DISABLED_MODE
268 await new Promise(resolve =>
269 chan.asyncOpen(new ChannelListener(resolve, null))
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",
295 Services.prefs.setBoolPref(
296 "network.trr.strict_native_fallback_allow_timeouts",
300 let { inStatus } = await new TRRDNSListener(
301 "timeout.example.com",
306 !Components.isSuccessCode(inStatus),
307 `${inStatus} should be an error code`
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",
317 await new TRRDNSListener("timeout.example.com", {
318 expectedAnswer: "127.0.0.1",
321 Services.prefs.setBoolPref(
322 "network.trr.strict_native_fallback_allow_timeouts",
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"
334 ({ inStatus } = await new TRRDNSListener("closeme.com", undefined, false));
336 !Components.isSuccessCode(inStatus),
337 `${inStatus} should be an error code`
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(
349 !Components.isSuccessCode(inStatus),
350 `${inStatus} should be an error code`
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
362 Services.dns.currentTrrConfirmationState == 3 ||
363 Services.dns.currentTrrConfirmationState == 4,
364 `Timed out waiting for confirmation failure. Currently ${Services.dns.currentTrrConfirmationState}`,
368 await new TRRDNSListener("bar.example.com", "127.0.0.1"); // Should fallback
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}`,
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",
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"
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"
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,
451 !Components.isSuccessCode(inStatus),
452 `${inStatus} should be an error code`
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");
463 Services.dns.currentTrrMode,
465 "Effective TRR mode should be 5"
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);
483 setModeAndURI(3, "doh?responseIP=none&cnameloop=true");
485 let { inStatus } = await new TRRDNSListener(
486 "test18.example.com",
491 !Components.isSuccessCode(inStatus),
492 `${inStatus} should be an error code`
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(
522 !Components.isSuccessCode(inStatus),
523 `${inStatus} should be an error code`
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",
551 Services.dns.clearCache(true);
552 setModeAndURI(2, "doh?responseIP=192.192.192.192");
553 Services.prefs.setCharPref(
554 "network.trr.excluded-domains",
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"
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"
578 await new TRRDNSListener("bar.example.com", "127.0.0.1");
580 Services.prefs.clearUserPref("network.trr.excluded-domains");
584 function topicObserved(topic) {
585 return new Promise(resolve => {
587 QueryInterface: ChromeUtils.generateQI(["nsIObserver"]),
588 observe(aSubject, aTopic, aData) {
589 if (aTopic == topic) {
590 Services.obs.removeObserver(observer, topic);
595 Services.obs.addObserver(observer, topic);
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",
607 Services.dns.clearCache(true);
608 setModeAndURI(2, "doh?responseIP=2.2.2.2");
610 const cpServer = new HttpServer();
611 cpServer.registerPathHandler(
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);
620 cpServer.identity.setPrimary(
622 "detectportal.firefox.com",
623 cpServer.identity.primaryPort
625 let cpPromise = topicObserved("captive-portal-login");
627 Services.prefs.setCharPref(
628 "captivedetect.canonicalURL",
629 `http://detectportal.firefox.com:${cpServer.identity.primaryPort}/cp`
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.
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));
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",
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",
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",
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"
696 await new TRRDNSListener("bar.example.com", "127.0.0.1");
697 await new TRRDNSListener("foo.test.com", "127.0.0.1");
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");
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");
722 Services.dns.clearCache(true);
723 Services.prefs.setCharPref(
724 "network.trr.excluded-domains",
725 "excluded,local,other"
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(
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);
746 cpServer.identity.setPrimary(
748 "detectportal.firefox.com",
749 cpServer.identity.primaryPort
751 let cpPromise = topicObserved("captive-portal-login");
753 Services.prefs.setCharPref(
754 "captivedetect.canonicalURL",
755 `http://detectportal.firefox.com:${cpServer.identity.primaryPort}/cp`
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.
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",
792 await new TRRDNSListener("excluded", "127.0.0.1");
795 Services.dns.clearCache(true);
796 Services.prefs.setCharPref(
797 "network.trr.builtin-excluded-domains",
801 await new TRRDNSListener("test.local", "127.0.0.1");
804 Services.dns.clearCache(true);
805 Services.prefs.setCharPref(
806 "network.trr.builtin-excluded-domains",
807 "excluded,local,other"
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);
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);
835 !Components.isSuccessCode(inStatus),
836 `${inStatus} should be an error code`
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));
852 !Components.isSuccessCode(inStatus),
853 `${inStatus} should be an error code`
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));
868 !Components.isSuccessCode(inStatus),
869 `${inStatus} should be an error code`
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));
887 !Components.isSuccessCode(inStatus),
888 `${inStatus} should be an error code`
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"
927 for (let strictMode of [true, false]) {
928 info("Strict mode: " + strictMode);
929 Services.prefs.setBoolPref(
930 "network.trr.strict_native_fallback",
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);
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");
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
969 gOverride.addIPOverride("ipv6.host.com", "1:1::2");
971 // Should not fallback to Do53 because A request for ipv6.host.com returns
973 let { inStatus } = await new TRRDNSListener("ipv6.host.com", {
974 flags: Ci.nsIDNSService.RESOLVE_DISABLE_IPV4,
975 expectedSuccess: false,
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
1002 gOverride.addIPOverride("ipv4.host.com", "3.4.5.6");
1004 // Should not fallback to Do53 because A request for ipv4.host.com returns
1006 let { inStatus } = await new TRRDNSListener("ipv4.host.com", {
1007 flags: Ci.nsIDNSService.RESOLVE_DISABLE_IPV6,
1008 expectedSuccess: false,
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 = {
1039 QueryInterface: ChromeUtils.generateQI([
1040 "nsIInterfaceRequestor",
1041 "nsIProgressEventSink",
1044 return this.QueryInterface(iid);
1046 onProgress(request, progress, progressMax) {},
1047 onStatus(request, status, statusArg) {
1048 this.statusCount[status] = 1 + (this.statusCount[status] || 0);
1051 chan.notificationCallbacks = statusCounter;
1052 await new Promise(resolve =>
1053 chan.asyncOpen(new ChannelListener(resolve, null, CL_EXPECT_FAILURE))
1056 statusCounter.statusCount[0x4b000b],
1058 "Expecting only one instance of NS_NET_STATUS_RESOLVED_HOST"
1061 statusCounter.statusCount[0x4b0007],
1063 "Expecting only one instance of NS_NET_STATUS_CONNECTING_TO"
1067 for (let strictMode of [true, false]) {
1068 info("Strict mode: " + strictMode);
1069 Services.prefs.setBoolPref(
1070 "network.trr.strict_native_fallback",
1073 await test(`http://unknown.ipv4.stuff:666/path`, "0.0.0.0");
1074 await test(`http://unknown.ipv6.stuff:666/path`, "::");
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",
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(
1092 () => Services.dns.currentTrrConfirmationState == 2,
1093 `Timed out waiting for confirmation success. Currently ${Services.dns.currentTrrConfirmationState}`,
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(
1104 () => Services.dns.currentTrrConfirmationState == 2,
1105 `Timed out waiting for confirmation success. Currently ${Services.dns.currentTrrConfirmationState}`,
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(
1120 () => Services.dns.currentTrrConfirmationState == 2,
1121 `Timed out waiting for confirmation success. Currently ${Services.dns.currentTrrConfirmationState}`,
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(
1136 () => Services.dns.currentTrrConfirmationState == 2,
1137 `Timed out waiting for confirmation success. Currently ${Services.dns.currentTrrConfirmationState}`,
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
1152 let dohReqPortLog = await new Promise(resolve =>
1154 new ChannelListener((stuff, buffer) => {
1155 resolve(JSON.parse(buffer));
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
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"],
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);
1206 expectedLogTemplate.length,
1207 dohReqPortLog.length,
1208 "Correct number of req log entries"
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);
1230 seenPortsByExpectedPort.get(expectedPort),
1231 "Connection was reused as expected"