/* 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);