gecko-dev/netwerk/test/unit/test_dooh.js

355 lines
11 KiB
JavaScript

/* Any copyright is dedicated to the Public Domain.
* https://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
/* import-globals-from trr_common.js */
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 => {
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);