Source code
Revision control
Copy as Markdown
Other Tools
Test Info: Warnings
- This test gets skipped with pattern: os == 'android' OR os == 'win' && msix
- Manifest: netwerk/test/unit/xpcshell.toml
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
// fails to connect, we should re-resolve with TRR disabled (native DNS) and
// retry -- except in TRR_ONLY mode. TRR resolves the origin to ::1, whose
// connection is blocked; native resolution points at 127.0.0.1 where a working
// H2 server lives. In TRR-first mode the request must succeed via the native
// fallback; in TRR-only mode it must hard-fail.
"use strict";
var { setTimeout } = ChromeUtils.importESModule(
"resource://gre/modules/Timer.sys.mjs"
);
const { NodeHTTP2Server } = ChromeUtils.importESModule(
);
const override = Cc["@mozilla.org/network/native-dns-override;1"].getService(
Ci.nsINativeDNSResolverOverride
);
const mockController = Cc[
"@mozilla.org/network/mock-network-controller;1"
].getService(Ci.nsIMockNetworkLayerController);
const HOST = "trr-connect-fallback.example.com";
let trrServer;
let server;
let originURL;
let originPort;
function openChan(expectFailure) {
let chan = NetUtil.newChannel({
uri: originURL,
loadUsingSystemPrincipal: true,
contentPolicyType: Ci.nsIContentPolicy.TYPE_DOCUMENT,
}).QueryInterface(Ci.nsIHttpChannel);
chan.loadFlags = Ci.nsIChannel.LOAD_INITIAL_DOCUMENT_URI;
return new Promise(resolve => {
chan.asyncOpen(
new ChannelListener(
req => resolve(req),
null,
(expectFailure ? CL_EXPECT_FAILURE : 0) | CL_ALLOW_UNKNOWN_CL
)
);
});
}
async function resetConnections() {
Services.obs.notifyObservers(null, "net:cancel-all-connections");
let nssComponent = Cc["@mozilla.org/network/ssl-tokens-cache;1"].getService(
Ci.nsISSLTokensCache
);
await nssComponent.asyncClearSSLExternalAndInternalSessionCache();
override.clearOverrides();
mockController.clearBlockedTCPConnect();
Services.dns.clearCache(true);
// eslint-disable-next-line mozilla/no-arbitrary-setTimeout
await new Promise(resolve => setTimeout(resolve, 1000));
}
// Route: TRR -> 127.0.0.1 (blocked), native -> ::1 (working server).
function armFallbackRouting() {
override.addIPOverride(HOST, "::1");
let blocked = mockController.createScriptableNetAddr("127.0.0.1", originPort);
mockController.blockTCPConnect(blocked);
}
add_setup(async function setup() {
trr_test_setup(); // native-is-localhost, bootstrapAddr, skip confirmation, profile
Services.prefs.setBoolPref("network.http.happy_eyeballs_enabled", true);
Services.prefs.setBoolPref("network.socket.attach_mock_network_layer", true);
let certdb = Cc["@mozilla.org/security/x509certdb;1"].getService(
Ci.nsIX509CertDB
);
addCertFromFile(certdb, "http2-ca.pem", "CTu,u,u");
server = new NodeHTTP2Server();
await server.start(0, [HOST]);
originPort = server.port();
await server.registerPathHandler("/", (req, resp) => {
resp.writeHead(200, { "Content-Type": "text/plain" });
resp.end("hello");
});
trrServer = new TRRServer();
await trrServer.start();
Services.prefs.setCharPref(
"network.trr.uri",
);
// TRR resolves the origin to 127.0.0.1 only; the connection to it is blocked
// below. (An A record is used so mDnsMetadata records the TRR resolution.)
await trrServer.registerDoHAnswers(HOST, "A", {
answers: [
{ name: HOST, ttl: 55, type: "A", flush: false, data: "127.0.0.1" },
],
});
await trrServer.registerDoHAnswers(HOST, "AAAA", { answers: [] });
registerCleanupFunction(async () => {
trr_clear_prefs();
Services.prefs.clearUserPref("network.http.happy_eyeballs_enabled");
Services.prefs.clearUserPref("network.socket.attach_mock_network_layer");
Services.prefs.clearUserPref("network.trr.uri");
Services.prefs.clearUserPref("network.trr.mode");
override.clearOverrides();
mockController.clearBlockedTCPConnect();
try {
if (trrServer) {
await trrServer.stop();
}
await server.stop();
} catch (e) {
info("Error stopping servers: " + e);
}
});
});
// TRR-first: the TRR-resolved address (127.0.0.1) fails to connect, so we must
// fall back to native DNS (::1) and succeed.
add_task(async function trr_first_falls_back_to_native() {
await resetConnections();
Services.prefs.setIntPref("network.trr.mode", 2);
armFallbackRouting();
let req = await openChan(false);
Assert.ok(
Components.isSuccessCode(req.status),
`request should succeed via native fallback, status=0x${req.status.toString(
16
)}`
);
Assert.equal(
req.QueryInterface(Ci.nsIHttpChannel).responseStatus,
200,
"should get 200 from the native server"
);
Assert.equal(
req.QueryInterface(Ci.nsIHttpChannelInternal).remoteAddress,
"::1",
"should have connected to the native-resolved address"
);
Assert.ok(
!req.QueryInterface(Ci.nsIHttpChannelInternal).isResolvedByTRR,
"the successful connection must have used native DNS, not TRR"
);
});
// TRR-only: native fallback is not allowed, so the request must fail.
add_task(async function trr_only_does_not_fall_back() {
await resetConnections();
Services.prefs.setIntPref("network.trr.mode", 3);
armFallbackRouting();
let req = await openChan(true);
Assert.ok(
!Components.isSuccessCode(req.status),
`TRR-only request must not fall back to native, status=0x${req.status.toString(
16
)}`
);
});