summaryrefslogtreecommitdiffstats
path: root/netwerk/test/unit/test_dooh.js
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--netwerk/test/unit/test_dooh.js356
1 files changed, 356 insertions, 0 deletions
diff --git a/netwerk/test/unit/test_dooh.js b/netwerk/test/unit/test_dooh.js
new file mode 100644
index 0000000000..bbcdc5a377
--- /dev/null
+++ b/netwerk/test/unit/test_dooh.js
@@ -0,0 +1,356 @@
+/* Any copyright is dedicated to the Public Domain.
+ * https://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/* import-globals-from trr_common.js */
+
+Cu.importGlobalProperties(["fetch"]);
+
+const { setTimeout } = ChromeUtils.importESModule(
+ "resource://gre/modules/Timer.sys.mjs"
+);
+
+let httpServer;
+let ohttpServer;
+let ohttpEncodedConfig = "not a valid config";
+
+// Decapsulate the request, send it to the actual TRR, receive the response,
+// encapsulate it, and send it back through `response`.
+async function forwardToTRR(request, response) {
+ let inputStream = Cc["@mozilla.org/scriptableinputstream;1"].createInstance(
+ Ci.nsIScriptableInputStream
+ );
+ inputStream.init(request.bodyInputStream);
+ let requestBody = inputStream.readBytes(inputStream.available());
+ let ohttpResponse = ohttpServer.decapsulate(stringToBytes(requestBody));
+ let bhttp = Cc["@mozilla.org/network/binary-http;1"].getService(
+ Ci.nsIBinaryHttp
+ );
+ let decodedRequest = bhttp.decodeRequest(ohttpResponse.request);
+ let headers = {};
+ for (
+ let i = 0;
+ i < decodedRequest.headerNames.length && decodedRequest.headerValues.length;
+ i++
+ ) {
+ headers[decodedRequest.headerNames[i]] = decodedRequest.headerValues[i];
+ }
+ let uri = `${decodedRequest.scheme}://${decodedRequest.authority}${decodedRequest.path}`;
+ let body = new Uint8Array(decodedRequest.content.length);
+ for (let i = 0; i < decodedRequest.content.length; i++) {
+ body[i] = decodedRequest.content[i];
+ }
+ try {
+ // Timeout after 10 seconds.
+ let fetchInProgress = true;
+ let controller = new AbortController();
+ // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
+ setTimeout(() => {
+ if (fetchInProgress) {
+ controller.abort();
+ }
+ }, 10000);
+ let trrResponse = await fetch(uri, {
+ method: decodedRequest.method,
+ headers,
+ body: decodedRequest.method == "POST" ? body : undefined,
+ credentials: "omit",
+ signal: controller.signal,
+ });
+ fetchInProgress = false;
+ let data = new Uint8Array(await trrResponse.arrayBuffer());
+ let trrResponseContent = [];
+ for (let i = 0; i < data.length; i++) {
+ trrResponseContent.push(data[i]);
+ }
+ let trrResponseHeaderNames = [];
+ let trrResponseHeaderValues = [];
+ for (let header of trrResponse.headers) {
+ trrResponseHeaderNames.push(header[0]);
+ trrResponseHeaderValues.push(header[1]);
+ }
+ let binaryResponse = new BinaryHttpResponse(
+ trrResponse.status,
+ trrResponseHeaderNames,
+ trrResponseHeaderValues,
+ trrResponseContent
+ );
+ let responseBytes = bhttp.encodeResponse(binaryResponse);
+ let encResponse = ohttpResponse.encapsulate(responseBytes);
+ response.setStatusLine(request.httpVersion, 200, "OK");
+ response.setHeader("Content-Type", "message/ohttp-res", false);
+ response.write(bytesToString(encResponse));
+ } catch (e) {
+ // Some tests involve the responder either timing out or closing the
+ // connection unexpectedly.
+ }
+}
+
+add_setup(async function setup() {
+ h2Port = trr_test_setup();
+ runningOHTTPTests = true;
+
+ if (mozinfo.socketprocess_networking) {
+ Services.dns; // Needed to trigger socket process.
+ await TestUtils.waitForCondition(() => Services.io.socketProcessLaunched);
+ }
+
+ Services.prefs.setIntPref("network.trr.mode", Ci.nsIDNSService.MODE_TRRONLY);
+
+ let ohttp = Cc["@mozilla.org/network/oblivious-http;1"].getService(
+ Ci.nsIObliviousHttp
+ );
+ ohttpServer = ohttp.server();
+
+ httpServer = new HttpServer();
+ httpServer.registerPathHandler("/relay", function (request, response) {
+ response.processAsync();
+ forwardToTRR(request, response).then(() => {
+ response.finish();
+ });
+ });
+ httpServer.registerPathHandler("/config", function (request, response) {
+ response.setStatusLine(request.httpVersion, 200, "OK");
+ response.setHeader("Content-Type", "application/ohttp-keys", false);
+ response.write(ohttpEncodedConfig);
+ });
+ httpServer.start(-1);
+
+ Services.prefs.setBoolPref("network.trr.use_ohttp", true);
+ // On windows the TTL fetch will race with clearing the cache
+ // to refresh the cache entry.
+ Services.prefs.setBoolPref("network.dns.get-ttl", false);
+
+ registerCleanupFunction(async () => {
+ trr_clear_prefs();
+ Services.prefs.clearUserPref("network.trr.use_ohttp");
+ Services.prefs.clearUserPref("network.trr.ohttp.config_uri");
+ Services.prefs.clearUserPref("network.trr.ohttp.relay_uri");
+ Services.prefs.clearUserPref("network.trr.ohttp.uri");
+ Services.prefs.clearUserPref("network.dns.get-ttl");
+ await new Promise((resolve, reject) => {
+ httpServer.stop(resolve);
+ });
+ });
+});
+
+// Test that if DNS-over-OHTTP isn't configured, the implementation falls back
+// to platform resolution.
+add_task(async function test_ohttp_not_configured() {
+ Services.dns.clearCache(true);
+ setModeAndURI(2, "doh?responseIP=2.2.2.2");
+ await new TRRDNSListener("example.com", "127.0.0.1");
+});
+
+add_task(async function set_ohttp_invalid_prefs() {
+ let configPromise = TestUtils.topicObserved("ohttp-service-config-loaded");
+ Services.prefs.setCharPref(
+ "network.trr.ohttp.relay_uri",
+ "http://nonexistent.test"
+ );
+ Services.prefs.setCharPref(
+ "network.trr.ohttp.config_uri",
+ "http://nonexistent.test"
+ );
+
+ Cc["@mozilla.org/network/oblivious-http-service;1"].getService(
+ Ci.nsIObliviousHttpService
+ );
+ await configPromise;
+});
+
+// Test that if DNS-over-OHTTP has an invalid configuration, the implementation
+// falls back to platform resolution.
+add_task(async function test_ohttp_invalid_prefs_fallback() {
+ Services.dns.clearCache(true);
+ setModeAndURI(2, "doh?responseIP=2.2.2.2");
+ await new TRRDNSListener("example.com", "127.0.0.1");
+});
+
+add_task(async function set_ohttp_prefs_500_error() {
+ let configPromise = TestUtils.topicObserved("ohttp-service-config-loaded");
+ Services.prefs.setCharPref(
+ "network.trr.ohttp.relay_uri",
+ `http://localhost:${httpServer.identity.primaryPort}/relay`
+ );
+ Services.prefs.setCharPref(
+ "network.trr.ohttp.config_uri",
+ `http://localhost:${httpServer.identity.primaryPort}/500error`
+ );
+ await configPromise;
+});
+
+// Test that if DNS-over-OHTTP has an invalid configuration, the implementation
+// falls back to platform resolution.
+add_task(async function test_ohttp_500_error_fallback() {
+ Services.dns.clearCache(true);
+ setModeAndURI(2, "doh?responseIP=2.2.2.2");
+ await new TRRDNSListener("example.com", "127.0.0.1");
+});
+
+add_task(async function retryConfigOnConnectivityChange() {
+ Services.prefs.setCharPref("network.trr.confirmationNS", "skip");
+ // First we make sure the config is properly loaded
+ setModeAndURI(2, "doh?responseIP=2.2.2.2");
+ let ohttpService = Cc[
+ "@mozilla.org/network/oblivious-http-service;1"
+ ].getService(Ci.nsIObliviousHttpService);
+ ohttpService.clearTRRConfig();
+ ohttpEncodedConfig = bytesToString(ohttpServer.encodedConfig);
+ let configPromise = TestUtils.topicObserved("ohttp-service-config-loaded");
+ Services.prefs.setCharPref(
+ "network.trr.ohttp.relay_uri",
+ `http://localhost:${httpServer.identity.primaryPort}/relay`
+ );
+ Services.prefs.setCharPref(
+ "network.trr.ohttp.config_uri",
+ `http://localhost:${httpServer.identity.primaryPort}/config`
+ );
+ let [, status] = await configPromise;
+ equal(status, "success");
+ info("retryConfigOnConnectivityChange setup complete");
+
+ ohttpService.clearTRRConfig();
+
+ let port = httpServer.identity.primaryPort;
+ // Stop the server so getting the config fails.
+ await httpServer.stop();
+
+ configPromise = TestUtils.topicObserved("ohttp-service-config-loaded");
+ Services.obs.notifyObservers(
+ null,
+ "network:captive-portal-connectivity-changed"
+ );
+ [, status] = await configPromise;
+ equal(status, "failed");
+
+ // Should fallback to native DNS since the config is empty
+ Services.dns.clearCache(true);
+ await new TRRDNSListener("example.com", "127.0.0.1");
+
+ // Start the server back again.
+ httpServer.start(port);
+ Assert.equal(
+ port,
+ httpServer.identity.primaryPort,
+ "server should get the same port"
+ );
+
+ // Still the config hasn't been reloaded.
+ await new TRRDNSListener("example2.com", "127.0.0.1");
+
+ // Signal a connectivity change so we reload the config
+ configPromise = TestUtils.topicObserved("ohttp-service-config-loaded");
+ Services.obs.notifyObservers(
+ null,
+ "network:captive-portal-connectivity-changed"
+ );
+ [, status] = await configPromise;
+ equal(status, "success");
+
+ await new TRRDNSListener("example3.com", "2.2.2.2");
+
+ // Now check that we also reload a missing config if a TRR confirmation fails.
+ ohttpService.clearTRRConfig();
+ configPromise = TestUtils.topicObserved("ohttp-service-config-loaded");
+ Services.obs.notifyObservers(
+ null,
+ "network:trr-confirmation",
+ "CONFIRM_FAILED"
+ );
+ [, status] = await configPromise;
+ equal(status, "success");
+ await new TRRDNSListener("example4.com", "2.2.2.2");
+
+ // set the config to an invalid value and check that as long as the URL
+ // doesn't change, we dont reload it again on connectivity notifications.
+ ohttpEncodedConfig = "not a valid config";
+ configPromise = TestUtils.topicObserved("ohttp-service-config-loaded");
+ Services.obs.notifyObservers(
+ null,
+ "network:captive-portal-connectivity-changed"
+ );
+
+ await new TRRDNSListener("example5.com", "2.2.2.2");
+
+ // The change should not cause any config reload because we already have a config.
+ [, status] = await configPromise;
+ equal(status, "no-changes");
+
+ await new TRRDNSListener("example6.com", "2.2.2.2");
+ // Clear the config_uri pref so it gets set to the proper value in the next test.
+ configPromise = TestUtils.topicObserved("ohttp-service-config-loaded");
+ Services.prefs.setCharPref("network.trr.ohttp.config_uri", ``);
+ await configPromise;
+});
+
+add_task(async function set_ohttp_prefs_valid() {
+ let ohttpService = Cc[
+ "@mozilla.org/network/oblivious-http-service;1"
+ ].getService(Ci.nsIObliviousHttpService);
+ ohttpService.clearTRRConfig();
+ let configPromise = TestUtils.topicObserved("ohttp-service-config-loaded");
+ ohttpEncodedConfig = bytesToString(ohttpServer.encodedConfig);
+ Services.prefs.setCharPref(
+ "network.trr.ohttp.config_uri",
+ `http://localhost:${httpServer.identity.primaryPort}/config`
+ );
+ await configPromise;
+});
+
+add_task(test_A_record);
+
+add_task(test_AAAA_records);
+
+add_task(test_RFC1918);
+
+add_task(test_GET_ECS);
+
+add_task(test_timeout_mode3);
+
+add_task(test_strict_native_fallback);
+
+add_task(test_no_answers_fallback);
+
+add_task(test_404_fallback);
+
+add_task(test_mode_1_and_4);
+
+add_task(test_CNAME);
+
+add_task(test_name_mismatch);
+
+add_task(test_mode_2);
+
+add_task(test_excluded_domains);
+
+add_task(test_captiveportal_canonicalURL);
+
+add_task(test_parentalcontrols);
+
+// TRR-first check that DNS result is used if domain is part of the builtin-excluded-domains pref
+add_task(test_builtin_excluded_domains);
+
+add_task(test_excluded_domains_mode3);
+
+add_task(test25e);
+
+add_task(test_parentalcontrols_mode3);
+
+add_task(test_builtin_excluded_domains_mode3);
+
+add_task(count_cookies);
+
+// This test doesn't work with having a JS httpd server as a relay.
+// add_task(test_connection_closed);
+
+add_task(test_fetch_time);
+
+add_task(test_fqdn);
+
+add_task(test_ipv6_trr_fallback);
+
+add_task(test_ipv4_trr_fallback);
+
+add_task(test_no_retry_without_doh);