diff options
Diffstat (limited to 'netwerk/test/unit/test_dooh.js')
-rw-r--r-- | netwerk/test/unit/test_dooh.js | 356 |
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); |