Bug 1845311 - [Part 2] Use ChromeUtils.defineLazyGetter in more places r=arai,webcomp...
[gecko.git] / netwerk / test / unit / test_trr_httpssvc.js
blobcf57726ab6f9c1a8da5e4a5b9fcc190fab66008c
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 const { TestUtils } = ChromeUtils.importESModule(
8   "resource://testing-common/TestUtils.sys.mjs"
9 );
11 let h2Port;
12 let trrServer;
14 function inChildProcess() {
15   return Services.appinfo.processType != Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT;
18 add_setup(async function setup() {
19   if (inChildProcess()) {
20     return;
21   }
23   trr_test_setup();
24   h2Port = Services.env.get("MOZHTTP2_PORT");
25   Assert.notEqual(h2Port, null);
26   Assert.notEqual(h2Port, "");
28   registerCleanupFunction(async () => {
29     trr_clear_prefs();
30     Services.prefs.clearUserPref("network.dns.port_prefixed_qname_https_rr");
31     await trrServer.stop();
32   });
34   if (mozinfo.socketprocess_networking) {
35     Services.dns; // Needed to trigger socket process.
36     await TestUtils.waitForCondition(() => Services.io.socketProcessLaunched);
37   }
39   Services.prefs.setIntPref("network.trr.mode", 3);
40 });
42 add_task(async function testHTTPSSVC() {
43   // use the h2 server as DOH provider
44   if (!inChildProcess()) {
45     Services.prefs.setCharPref(
46       "network.trr.uri",
47       "https://foo.example.com:" + h2Port + "/httpssvc"
48     );
49   }
51   let { inRecord } = await new TRRDNSListener("test.httpssvc.com", {
52     type: Ci.nsIDNSService.RESOLVE_TYPE_HTTPSSVC,
53   });
54   let answer = inRecord.QueryInterface(Ci.nsIDNSHTTPSSVCRecord).records;
55   Assert.equal(answer[0].priority, 1);
56   Assert.equal(answer[0].name, "h3pool");
57   Assert.equal(answer[0].values.length, 7);
58   Assert.deepEqual(
59     answer[0].values[0].QueryInterface(Ci.nsISVCParamAlpn).alpn,
60     ["h2", "h3"],
61     "got correct answer"
62   );
63   Assert.ok(
64     answer[0].values[1].QueryInterface(Ci.nsISVCParamNoDefaultAlpn),
65     "got correct answer"
66   );
67   Assert.equal(
68     answer[0].values[2].QueryInterface(Ci.nsISVCParamPort).port,
69     8888,
70     "got correct answer"
71   );
72   Assert.equal(
73     answer[0].values[3].QueryInterface(Ci.nsISVCParamIPv4Hint).ipv4Hint[0]
74       .address,
75     "1.2.3.4",
76     "got correct answer"
77   );
78   Assert.equal(
79     answer[0].values[4].QueryInterface(Ci.nsISVCParamEchConfig).echconfig,
80     "123...",
81     "got correct answer"
82   );
83   Assert.equal(
84     answer[0].values[5].QueryInterface(Ci.nsISVCParamIPv6Hint).ipv6Hint[0]
85       .address,
86     "::1",
87     "got correct answer"
88   );
89   Assert.equal(
90     answer[0].values[6].QueryInterface(Ci.nsISVCParamODoHConfig).ODoHConfig,
91     "456...",
92     "got correct answer"
93   );
94   Assert.equal(answer[1].priority, 2);
95   Assert.equal(answer[1].name, "test.httpssvc.com");
96   Assert.equal(answer[1].values.length, 5);
97   Assert.deepEqual(
98     answer[1].values[0].QueryInterface(Ci.nsISVCParamAlpn).alpn,
99     ["h2"],
100     "got correct answer"
101   );
102   Assert.equal(
103     answer[1].values[1].QueryInterface(Ci.nsISVCParamIPv4Hint).ipv4Hint[0]
104       .address,
105     "1.2.3.4",
106     "got correct answer"
107   );
108   Assert.equal(
109     answer[1].values[1].QueryInterface(Ci.nsISVCParamIPv4Hint).ipv4Hint[1]
110       .address,
111     "5.6.7.8",
112     "got correct answer"
113   );
114   Assert.equal(
115     answer[1].values[2].QueryInterface(Ci.nsISVCParamEchConfig).echconfig,
116     "abc...",
117     "got correct answer"
118   );
119   Assert.equal(
120     answer[1].values[3].QueryInterface(Ci.nsISVCParamIPv6Hint).ipv6Hint[0]
121       .address,
122     "::1",
123     "got correct answer"
124   );
125   Assert.equal(
126     answer[1].values[3].QueryInterface(Ci.nsISVCParamIPv6Hint).ipv6Hint[1]
127       .address,
128     "fe80::794f:6d2c:3d5e:7836",
129     "got correct answer"
130   );
131   Assert.equal(
132     answer[1].values[4].QueryInterface(Ci.nsISVCParamODoHConfig).ODoHConfig,
133     "def...",
134     "got correct answer"
135   );
136   Assert.equal(answer[2].priority, 3);
137   Assert.equal(answer[2].name, "hello");
138   Assert.equal(answer[2].values.length, 0);
141 add_task(async function test_aliasform() {
142   trrServer = new TRRServer();
143   await trrServer.start();
144   dump(`port = ${trrServer.port}\n`);
146   if (inChildProcess()) {
147     do_send_remote_message("mode3-port", trrServer.port);
148     await do_await_remote_message("mode3-port-done");
149   } else {
150     Services.prefs.setIntPref("network.trr.mode", 3);
151     Services.prefs.setCharPref(
152       "network.trr.uri",
153       `https://foo.example.com:${trrServer.port}/dns-query`
154     );
155   }
157   // Make sure that HTTPS AliasForm is only treated as a CNAME for HTTPS requests
158   await trrServer.registerDoHAnswers("test1.com", "A", {
159     answers: [
160       {
161         name: "test1.com",
162         ttl: 55,
163         type: "HTTPS",
164         flush: false,
165         data: {
166           priority: 0,
167           name: "something1.com",
168           values: [],
169         },
170       },
171     ],
172   });
173   await trrServer.registerDoHAnswers("something1.com", "A", {
174     answers: [
175       {
176         name: "something1.com",
177         ttl: 55,
178         type: "A",
179         flush: false,
180         data: "1.2.3.4",
181       },
182     ],
183   });
185   {
186     let { inStatus } = await new TRRDNSListener("test1.com", {
187       expectedSuccess: false,
188     });
189     Assert.ok(
190       !Components.isSuccessCode(inStatus),
191       `${inStatus} should be an error code`
192     );
193   }
195   // Test that HTTPS priority = 0 (AliasForm) behaves like a CNAME
196   await trrServer.registerDoHAnswers("test.com", "HTTPS", {
197     answers: [
198       {
199         name: "test.com",
200         ttl: 55,
201         type: "HTTPS",
202         flush: false,
203         data: {
204           priority: 0,
205           name: "something.com",
206           values: [],
207         },
208       },
209     ],
210   });
211   await trrServer.registerDoHAnswers("something.com", "HTTPS", {
212     answers: [
213       {
214         name: "something.com",
215         ttl: 55,
216         type: "HTTPS",
217         flush: false,
218         data: {
219           priority: 1,
220           name: "h3pool",
221           values: [{ key: "alpn", value: ["h2", "h3"] }],
222         },
223       },
224     ],
225   });
227   {
228     let { inStatus, inRecord } = await new TRRDNSListener("test.com", {
229       type: Ci.nsIDNSService.RESOLVE_TYPE_HTTPSSVC,
230       expectedSuccess: false,
231     });
232     Assert.ok(Components.isSuccessCode(inStatus), `${inStatus} should succeed`);
233     let answer = inRecord.QueryInterface(Ci.nsIDNSHTTPSSVCRecord).records;
234     Assert.equal(answer[0].priority, 1);
235     Assert.equal(answer[0].name, "h3pool");
236   }
238   // Test a chain of HTTPSSVC AliasForm and CNAMEs
239   await trrServer.registerDoHAnswers("x.com", "HTTPS", {
240     answers: [
241       {
242         name: "x.com",
243         ttl: 55,
244         type: "HTTPS",
245         flush: false,
246         data: {
247           priority: 0,
248           name: "y.com",
249           values: [],
250         },
251       },
252     ],
253   });
254   await trrServer.registerDoHAnswers("y.com", "HTTPS", {
255     answers: [
256       {
257         name: "y.com",
258         type: "CNAME",
259         ttl: 55,
260         class: "IN",
261         flush: false,
262         data: "z.com",
263       },
264     ],
265   });
266   await trrServer.registerDoHAnswers("z.com", "HTTPS", {
267     answers: [
268       {
269         name: "z.com",
270         ttl: 55,
271         type: "HTTPS",
272         flush: false,
273         data: {
274           priority: 0,
275           name: "target.com",
276           values: [],
277         },
278       },
279     ],
280   });
281   await trrServer.registerDoHAnswers("target.com", "HTTPS", {
282     answers: [
283       {
284         name: "target.com",
285         ttl: 55,
286         type: "HTTPS",
287         flush: false,
288         data: {
289           priority: 1,
290           name: "h3pool",
291           values: [{ key: "alpn", value: ["h2", "h3"] }],
292         },
293       },
294     ],
295   });
297   let { inStatus, inRecord } = await new TRRDNSListener("x.com", {
298     type: Ci.nsIDNSService.RESOLVE_TYPE_HTTPSSVC,
299     expectedSuccess: false,
300   });
301   Assert.ok(Components.isSuccessCode(inStatus), `${inStatus} should succeed`);
302   let answer = inRecord.QueryInterface(Ci.nsIDNSHTTPSSVCRecord).records;
303   Assert.equal(answer[0].priority, 1);
304   Assert.equal(answer[0].name, "h3pool");
306   // We get a ServiceForm instead of a A answer, CNAME or AliasForm
307   await trrServer.registerDoHAnswers("no-ip-host.com", "A", {
308     answers: [
309       {
310         name: "no-ip-host.com",
311         ttl: 55,
312         type: "HTTPS",
313         flush: false,
314         data: {
315           priority: 1,
316           name: "h3pool",
317           values: [
318             { key: "alpn", value: ["h2", "h3"] },
319             { key: "no-default-alpn" },
320             { key: "port", value: 8888 },
321             { key: "ipv4hint", value: "1.2.3.4" },
322             { key: "echconfig", value: "123..." },
323             { key: "ipv6hint", value: "::1" },
324           ],
325         },
326       },
327     ],
328   });
330   ({ inStatus } = await new TRRDNSListener("no-ip-host.com", {
331     expectedSuccess: false,
332   }));
333   Assert.ok(
334     !Components.isSuccessCode(inStatus),
335     `${inStatus} should be an error code`
336   );
338   // Test CNAME/AliasForm loop
339   await trrServer.registerDoHAnswers("loop.com", "HTTPS", {
340     answers: [
341       {
342         name: "loop.com",
343         type: "CNAME",
344         ttl: 55,
345         class: "IN",
346         flush: false,
347         data: "loop2.com",
348       },
349     ],
350   });
351   await trrServer.registerDoHAnswers("loop2.com", "HTTPS", {
352     answers: [
353       {
354         name: "loop2.com",
355         ttl: 55,
356         type: "HTTPS",
357         flush: false,
358         data: {
359           priority: 0,
360           name: "loop.com",
361           values: [],
362         },
363       },
364     ],
365   });
367   // Make sure these are the first requests
368   Assert.equal(await trrServer.requestCount("loop.com", "HTTPS"), 0);
369   Assert.equal(await trrServer.requestCount("loop2.com", "HTTPS"), 0);
371   ({ inStatus } = await new TRRDNSListener("loop.com", {
372     type: Ci.nsIDNSService.RESOLVE_TYPE_HTTPSSVC,
373     expectedSuccess: false,
374   }));
375   Assert.ok(
376     !Components.isSuccessCode(inStatus),
377     `${inStatus} should be an error code`
378   );
379   // Make sure the error was actually triggered by a loop.
380   Assert.greater(await trrServer.requestCount("loop.com", "HTTPS"), 2);
381   Assert.greater(await trrServer.requestCount("loop2.com", "HTTPS"), 2);
383   // Alias form for .
384   await trrServer.registerDoHAnswers("empty.com", "A", {
385     answers: [
386       {
387         name: "empty.com",
388         ttl: 55,
389         type: "HTTPS",
390         flush: false,
391         data: {
392           priority: 0,
393           name: "", // This is not allowed
394           values: [],
395         },
396       },
397     ],
398   });
400   ({ inStatus } = await new TRRDNSListener("empty.com", {
401     expectedSuccess: false,
402   }));
403   Assert.ok(
404     !Components.isSuccessCode(inStatus),
405     `${inStatus} should be an error code`
406   );
408   // We should ignore ServiceForm if an AliasForm record is also present
409   await trrServer.registerDoHAnswers("multi.com", "HTTPS", {
410     answers: [
411       {
412         name: "multi.com",
413         ttl: 55,
414         type: "HTTPS",
415         flush: false,
416         data: {
417           priority: 1,
418           name: "h3pool",
419           values: [
420             { key: "alpn", value: ["h2", "h3"] },
421             { key: "no-default-alpn" },
422             { key: "port", value: 8888 },
423             { key: "ipv4hint", value: "1.2.3.4" },
424             { key: "echconfig", value: "123..." },
425             { key: "ipv6hint", value: "::1" },
426           ],
427         },
428       },
429       {
430         name: "multi.com",
431         ttl: 55,
432         type: "HTTPS",
433         flush: false,
434         data: {
435           priority: 0,
436           name: "example.com",
437           values: [],
438         },
439       },
440     ],
441   });
443   let { inStatus: inStatus2 } = await new TRRDNSListener("multi.com", {
444     type: Ci.nsIDNSService.RESOLVE_TYPE_HTTPSSVC,
445     expectedSuccess: false,
446   });
447   Assert.ok(
448     !Components.isSuccessCode(inStatus2),
449     `${inStatus2} should be an error code`
450   );
452   // the svcparam keys are in reverse order
453   await trrServer.registerDoHAnswers("order.com", "HTTPS", {
454     answers: [
455       {
456         name: "order.com",
457         ttl: 55,
458         type: "HTTPS",
459         flush: false,
460         data: {
461           priority: 1,
462           name: "h3pool",
463           values: [
464             { key: "ipv6hint", value: "::1" },
465             { key: "echconfig", value: "123..." },
466             { key: "ipv4hint", value: "1.2.3.4" },
467             { key: "port", value: 8888 },
468             { key: "no-default-alpn" },
469             { key: "alpn", value: ["h2", "h3"] },
470           ],
471         },
472       },
473     ],
474   });
476   ({ inStatus: inStatus2 } = await new TRRDNSListener("order.com", {
477     type: Ci.nsIDNSService.RESOLVE_TYPE_HTTPSSVC,
478     expectedSuccess: false,
479   }));
480   Assert.ok(
481     !Components.isSuccessCode(inStatus2),
482     `${inStatus2} should be an error code`
483   );
485   // duplicate svcparam keys
486   await trrServer.registerDoHAnswers("duplicate.com", "HTTPS", {
487     answers: [
488       {
489         name: "duplicate.com",
490         ttl: 55,
491         type: "HTTPS",
492         flush: false,
493         data: {
494           priority: 1,
495           name: "h3pool",
496           values: [
497             { key: "alpn", value: ["h2", "h3"] },
498             { key: "alpn", value: ["h2", "h3", "h4"] },
499           ],
500         },
501       },
502     ],
503   });
505   ({ inStatus: inStatus2 } = await new TRRDNSListener("duplicate.com", {
506     type: Ci.nsIDNSService.RESOLVE_TYPE_HTTPSSVC,
507     expectedSuccess: false,
508   }));
509   Assert.ok(
510     !Components.isSuccessCode(inStatus2),
511     `${inStatus2} should be an error code`
512   );
514   // mandatory svcparam
515   await trrServer.registerDoHAnswers("mandatory.com", "HTTPS", {
516     answers: [
517       {
518         name: "mandatory.com",
519         ttl: 55,
520         type: "HTTPS",
521         flush: false,
522         data: {
523           priority: 1,
524           name: "h3pool",
525           values: [
526             { key: "mandatory", value: ["key100"] },
527             { key: "alpn", value: ["h2", "h3"] },
528             { key: "key100" },
529           ],
530         },
531       },
532     ],
533   });
535   ({ inStatus: inStatus2 } = await new TRRDNSListener("mandatory.com", {
536     type: Ci.nsIDNSService.RESOLVE_TYPE_HTTPSSVC,
537     expectedSuccess: false,
538   }));
539   Assert.ok(!Components.isSuccessCode(inStatus2), `${inStatus2} should fail`);
541   // mandatory svcparam
542   await trrServer.registerDoHAnswers("mandatory2.com", "HTTPS", {
543     answers: [
544       {
545         name: "mandatory2.com",
546         ttl: 55,
547         type: "HTTPS",
548         flush: false,
549         data: {
550           priority: 1,
551           name: "h3pool",
552           values: [
553             {
554               key: "mandatory",
555               value: [
556                 "alpn",
557                 "no-default-alpn",
558                 "port",
559                 "ipv4hint",
560                 "echconfig",
561                 "ipv6hint",
562               ],
563             },
564             { key: "alpn", value: ["h2", "h3"] },
565             { key: "no-default-alpn" },
566             { key: "port", value: 8888 },
567             { key: "ipv4hint", value: "1.2.3.4" },
568             { key: "echconfig", value: "123..." },
569             { key: "ipv6hint", value: "::1" },
570           ],
571         },
572       },
573     ],
574   });
576   ({ inStatus: inStatus2 } = await new TRRDNSListener("mandatory2.com", {
577     type: Ci.nsIDNSService.RESOLVE_TYPE_HTTPSSVC,
578   }));
580   Assert.ok(Components.isSuccessCode(inStatus2), `${inStatus2} should succeed`);
582   // alias-mode with . targetName
583   await trrServer.registerDoHAnswers("no-alias.com", "HTTPS", {
584     answers: [
585       {
586         name: "no-alias.com",
587         ttl: 55,
588         type: "HTTPS",
589         flush: false,
590         data: {
591           priority: 0,
592           name: ".",
593           values: [],
594         },
595       },
596     ],
597   });
599   ({ inStatus: inStatus2 } = await new TRRDNSListener("no-alias.com", {
600     type: Ci.nsIDNSService.RESOLVE_TYPE_HTTPSSVC,
601     expectedSuccess: false,
602   }));
604   Assert.ok(!Components.isSuccessCode(inStatus2), `${inStatus2} should fail`);
606   // service-mode with . targetName
607   await trrServer.registerDoHAnswers("service.com", "HTTPS", {
608     answers: [
609       {
610         name: "service.com",
611         ttl: 55,
612         type: "HTTPS",
613         flush: false,
614         data: {
615           priority: 1,
616           name: ".",
617           values: [{ key: "alpn", value: ["h2", "h3"] }],
618         },
619       },
620     ],
621   });
623   ({ inRecord, inStatus: inStatus2 } = await new TRRDNSListener("service.com", {
624     type: Ci.nsIDNSService.RESOLVE_TYPE_HTTPSSVC,
625   }));
626   Assert.ok(Components.isSuccessCode(inStatus2), `${inStatus2} should work`);
627   answer = inRecord.QueryInterface(Ci.nsIDNSHTTPSSVCRecord).records;
628   Assert.equal(answer[0].priority, 1);
629   Assert.equal(answer[0].name, "service.com");
632 add_task(async function testNegativeResponse() {
633   let { inStatus } = await new TRRDNSListener("negative_test.com", {
634     type: Ci.nsIDNSService.RESOLVE_TYPE_HTTPSSVC,
635     expectedSuccess: false,
636   });
637   Assert.ok(
638     !Components.isSuccessCode(inStatus),
639     `${inStatus} should be an error code`
640   );
642   await trrServer.registerDoHAnswers("negative_test.com", "HTTPS", {
643     answers: [
644       {
645         name: "negative_test.com",
646         ttl: 55,
647         type: "HTTPS",
648         flush: false,
649         data: {
650           priority: 1,
651           name: "negative_test.com",
652           values: [{ key: "alpn", value: ["h2", "h3"] }],
653         },
654       },
655     ],
656   });
658   // Should still be failed because a negative response is from DNS cache.
659   ({ inStatus } = await new TRRDNSListener("negative_test.com", {
660     type: Ci.nsIDNSService.RESOLVE_TYPE_HTTPSSVC,
661     expectedSuccess: false,
662   }));
663   Assert.ok(
664     !Components.isSuccessCode(inStatus),
665     `${inStatus} should be an error code`
666   );
668   if (inChildProcess()) {
669     do_send_remote_message("clearCache");
670     await do_await_remote_message("clearCache-done");
671   } else {
672     Services.dns.clearCache(true);
673   }
675   let inRecord;
676   ({ inRecord, inStatus } = await new TRRDNSListener("negative_test.com", {
677     type: Ci.nsIDNSService.RESOLVE_TYPE_HTTPSSVC,
678   }));
679   Assert.ok(Components.isSuccessCode(inStatus), `${inStatus} should work`);
680   let answer = inRecord.QueryInterface(Ci.nsIDNSHTTPSSVCRecord).records;
681   Assert.equal(answer[0].priority, 1);
682   Assert.equal(answer[0].name, "negative_test.com");
685 add_task(async function testPortPrefixedName() {
686   if (inChildProcess()) {
687     do_send_remote_message("set-port-prefixed-pref");
688     await do_await_remote_message("set-port-prefixed-pref-done");
689   } else {
690     Services.prefs.setBoolPref(
691       "network.dns.port_prefixed_qname_https_rr",
692       true
693     );
694   }
696   await trrServer.registerDoHAnswers(
697     "_4433._https.port_prefix.test.com",
698     "HTTPS",
699     {
700       answers: [
701         {
702           name: "_4433._https.port_prefix.test.com",
703           ttl: 55,
704           type: "HTTPS",
705           flush: false,
706           data: {
707             priority: 1,
708             name: "port_prefix.test1.com",
709             values: [{ key: "alpn", value: ["h2", "h3"] }],
710           },
711         },
712       ],
713     }
714   );
716   let { inRecord, inStatus } = await new TRRDNSListener(
717     "port_prefix.test.com",
718     {
719       type: Ci.nsIDNSService.RESOLVE_TYPE_HTTPSSVC,
720       port: 4433,
721     }
722   );
723   Assert.ok(Components.isSuccessCode(inStatus), `${inStatus} should work`);
724   let answer = inRecord.QueryInterface(Ci.nsIDNSHTTPSSVCRecord).records;
725   Assert.equal(answer[0].priority, 1);
726   Assert.equal(answer[0].name, "port_prefix.test1.com");
727   await trrServer.stop();