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