Source code

Revision control

Copy as Markdown

Other Tools

Test Info: Warnings

"use strict";
/* import-globals-from trr_common.js */
/* import-globals-from head_trr.js */
const { TestUtils } = ChromeUtils.importESModule(
);
function setLocalModeAndURI(mode, url) {
Services.prefs.setCharPref("network.trr.uri", url);
Services.prefs.setIntPref("network.trr.mode", mode);
}
async function registerNS() {
await trrServer.registerDoHAnswers("confirm.example.com", "NS", {
answers: [
{
name: "confirm.example.com",
ttl: 55,
type: "NS",
flush: false,
data: "test.com",
},
],
});
}
async function unregisterNS() {
await trrServer.registerDoHAnswers("confirm.example.com", "NS", {
answers: [],
error: 500, // Server error
});
}
async function registerDomain(domain) {
await trrServer.registerDoHAnswers(domain, "A", {
answers: [
{
name: domain,
ttl: 55,
type: "A",
flush: false,
data: "9.8.7.6",
},
],
});
}
let trrServer;
add_setup(async function setup() {
trr_test_setup();
Services.prefs.setBoolPref("network.dns.native-is-localhost", true);
Services.dns.clearCache(true);
Services.prefs.setIntPref("network.trr.request_timeout_ms", 500);
Services.prefs.setIntPref(
"network.trr.strict_fallback_request_timeout_ms",
500
);
Services.prefs.setIntPref("network.trr.request_timeout_mode_trronly_ms", 500);
trrServer = new TRRServer();
registerCleanupFunction(async () => {
await trrServer.stop();
});
await trrServer.start();
dump(`port = ${trrServer.port()}\n`);
await registerNS();
});
// This test checks that connection is cycled on every failure when network.trr.retry_on_recoverable_errors is true.
add_task(async function test_connection_reuse_and_cycling() {
Services.prefs.setCharPref(
"network.trr.confirmationNS",
"confirm.example.com"
);
setLocalModeAndURI(
2,
`https://foo.example.com:${trrServer.port()}/dns-query`
);
Services.prefs.setBoolPref("network.trr.strict_native_fallback", true);
Services.prefs.setBoolPref("network.trr.retry_on_recoverable_errors", true);
await TestUtils.waitForCondition(
// 2 => CONFIRM_OK
() => Services.dns.currentTrrConfirmationState == 2,
`Timed out waiting for confirmation success. Currently ${Services.dns.currentTrrConfirmationState}`,
1,
5000
);
// Setting conncycle=true in the URI. Server will start logging reqs.
// We will do a specific sequence of lookups, then fetch the log from
// the server and check that it matches what we'd expect.
setLocalModeAndURI(
2,
`https://foo.example.com:${trrServer.port()}/dns-query?conncycle=true`
);
await TestUtils.waitForCondition(
// 2 => CONFIRM_OK
() => Services.dns.currentTrrConfirmationState == 2,
`Timed out waiting for confirmation success. Currently ${Services.dns.currentTrrConfirmationState}`,
1,
5000
);
// Confirmation upon uri-change will have created one req.
for (let i = 1; i <= 6; i++) {
await registerDomain(`bar${i}.example.org`);
}
await registerDomain("newconn.example.org");
await registerDomain("newconn2.example.org");
// Two reqs for each bar1 and bar2 - A + AAAA.
await new TRRDNSListener("bar1.example.org", "9.8.7.6");
await new TRRDNSListener("bar2.example.org", "9.8.7.6");
// Total so far: (1) + 2 + 2 = 5
// Two reqs that fail, one Confirmation req, two retried reqs that succeed.
await new TRRDNSListener("newconn.example.org", "9.8.7.6");
await TestUtils.waitForCondition(
// 2 => CONFIRM_OK
() => Services.dns.currentTrrConfirmationState == 2,
`Timed out waiting for confirmation success. Currently ${Services.dns.currentTrrConfirmationState}`,
1,
5000
);
// Total so far: (5) + 2 + 1 + 2 = 10
// Two reqs for each bar3 and bar4 .
await new TRRDNSListener("bar3.example.org", "9.8.7.6");
await new TRRDNSListener("bar4.example.org", "9.8.7.6");
// Total so far: (10) + 2 + 2 = 14.
// Two reqs that fail, one Confirmation req, two retried reqs that succeed.
await new TRRDNSListener("newconn2.example.org", "9.8.7.6");
await TestUtils.waitForCondition(
// 2 => CONFIRM_OK
() => Services.dns.currentTrrConfirmationState == 2,
`Timed out waiting for confirmation success. Currently ${Services.dns.currentTrrConfirmationState}`,
1,
5000
);
// Total so far: (14) + 2 + 1 + 2 = 19
// Two reqs for each bar5 and bar6 .
await new TRRDNSListener("bar5.example.org", "9.8.7.6");
await new TRRDNSListener("bar6.example.org", "9.8.7.6");
// Total so far: (19) + 2 + 2 = 23
let dohReqPortLog = await trrServer.execute(`global.gDoHPortsLog`);
info(JSON.stringify(dohReqPortLog));
// Since the actual ports seen will vary at runtime, we use placeholders
// instead in our expected output definition. For example, if two entries
// both have "port1", it means they both should have the same port in the
// server's log.
// For reqs that fail and trigger a Confirmation + retry, the retried reqs
// might not re-use the new connection created for Confirmation due to a
// race, so we have an extra alternate expected port for them. This lets
// us test that they use *a* new port even if it's not *the* new port.
// Subsequent lookups are not affected, they will use the same conn as
// the Confirmation req.
let expectedLogTemplate = [
["confirm.example.com", "port1"],
["bar1.example.org", "port1"],
["bar1.example.org", "port1"],
["bar2.example.org", "port1"],
["bar2.example.org", "port1"],
["newconn.example.org", "port1"],
["newconn.example.org", "port1"],
["confirm.example.com", "port2"],
["newconn.example.org", "port2"],
["newconn.example.org", "port2"],
["bar3.example.org", "port2"],
["bar3.example.org", "port2"],
["bar4.example.org", "port2"],
["bar4.example.org", "port2"],
["newconn2.example.org", "port2"],
["newconn2.example.org", "port2"],
["confirm.example.com", "port3"],
["newconn2.example.org", "port3"],
["newconn2.example.org", "port3"],
["bar5.example.org", "port3"],
["bar5.example.org", "port3"],
["bar6.example.org", "port3"],
["bar6.example.org", "port3"],
];
if (expectedLogTemplate.length != dohReqPortLog.length) {
// This shouldn't happen, and if it does, we'll fail the assertion
// below. But first dump the whole server-side log to help with
// debugging should we see a failure. Most likely cause would be
// that another consumer of TRR happened to make a request while
// the test was running and polluted the log.
info(dohReqPortLog);
}
equal(
expectedLogTemplate.length,
dohReqPortLog.length,
"Correct number of req log entries"
);
let seenPorts = new Set();
// This is essentially a symbol table - as we iterate through the log
// we will assign the actual seen port numbers to the placeholders.
let seenPortsByExpectedPort = new Map();
for (let i = 0; i < expectedLogTemplate.length; i++) {
let expectedName = expectedLogTemplate[i][0];
let expectedPort = expectedLogTemplate[i][1];
let seenName = dohReqPortLog[i][0];
let seenPort = dohReqPortLog[i][1];
info(`Checking log entry. Name: ${seenName}, Port: ${seenPort}`);
equal(expectedName, seenName, "Name matches for entry " + i);
if (!seenPortsByExpectedPort.has(expectedPort)) {
ok(!seenPorts.has(seenPort), "Port should not have been previously used");
seenPorts.add(seenPort);
seenPortsByExpectedPort.set(expectedPort, seenPort);
} else {
equal(
seenPort,
seenPortsByExpectedPort.get(expectedPort),
"Connection was reused as expected"
);
}
}
// Clear log for the next test.
await trrServer.execute(
`global.gDoHPortsLog = []; global.gDoHNewConnLog = {};`
);
});
// network.trr.retry_on_recoverable_errors = false
// This test unregisters the confirmation NS (server will return HTTP error code 500) before newconn resolutions
// The newconn resolutions will fail, triggering a confirmation. The second confirmation will cycle the connection.
// We check that the connection is cycled at least once for every newconn resolution.
add_task(async function test_connection_reuse_and_cycling2() {
Services.prefs.setBoolPref("network.trr.retry_on_recoverable_errors", false);
Services.dns.clearCache(true);
Services.prefs.setCharPref(
"network.trr.confirmationNS",
"confirm.example.com"
);
setLocalModeAndURI(
2,
`https://foo.example.com:${trrServer.port()}/dns-query`
);
Services.prefs.setBoolPref("network.trr.strict_native_fallback", true);
await TestUtils.waitForCondition(
// 2 => CONFIRM_OK
() => Services.dns.currentTrrConfirmationState == 2,
`Timed out waiting for confirmation success. Currently ${Services.dns.currentTrrConfirmationState}`,
1,
5000
);
// Setting conncycle=true in the URI. Server will start logging reqs.
// We will do a specific sequence of lookups, then fetch the log from
// the server and check that it matches what we'd expect.
setLocalModeAndURI(
2,
`https://foo.example.com:${trrServer.port()}/dns-query?conncycle=true`
);
await TestUtils.waitForCondition(
// 2 => CONFIRM_OK
() => Services.dns.currentTrrConfirmationState == 2,
`Timed out waiting for confirmation success. Currently ${Services.dns.currentTrrConfirmationState}`,
1,
5000
);
// Confirmation upon uri-change will have created one req.
for (let i = 1; i <= 6; i++) {
await registerDomain(`bar${i}.example.org`);
}
await registerDomain("newconn.example.org");
await registerDomain("newconn2.example.org");
await new TRRDNSListener("bar1.example.org", "9.8.7.6");
await new TRRDNSListener("bar2.example.org", "9.8.7.6");
let initialPort = await trrServer.execute(
`global.gDoHPortsLog[global.gDoHPortsLog.length-1]`
);
// This one will fallback because of the timeout
await unregisterNS();
await new TRRDNSListener("newconn.example.org", "127.0.0.1");
await TestUtils.waitForCondition(
// 3 => CONFIRM_FAILED
() => Services.dns.currentTrrConfirmationState == 3,
`Timed out waiting for confirmation success. Currently ${Services.dns.currentTrrConfirmationState}`,
1,
5000
);
await registerNS();
await TestUtils.waitForCondition(
// 2 => CONFIRM_OK
() => Services.dns.currentTrrConfirmationState == 2,
`Timed out waiting for confirmation success. Currently ${Services.dns.currentTrrConfirmationState}`,
1,
5000
);
let newConfirmationPort = await trrServer.execute(
`global.gDoHPortsLog[global.gDoHPortsLog.length-1]`
);
// Two reqs for each bar3 and bar4 .
await new TRRDNSListener("bar3.example.org", "9.8.7.6");
await new TRRDNSListener("bar4.example.org", "9.8.7.6");
initialPort = await trrServer.execute(
`global.gDoHPortsLog[global.gDoHPortsLog.length-1]`
);
await unregisterNS();
// This one will fallback because of the timeout
await new TRRDNSListener("newconn2.example.org", "127.0.0.1");
await TestUtils.waitForCondition(
// 3 => CONFIRM_FAILED
() => Services.dns.currentTrrConfirmationState == 3,
`Timed out waiting for confirmation success. Currently ${Services.dns.currentTrrConfirmationState}`,
1,
5000
);
await registerNS();
await TestUtils.waitForCondition(
// 2 => CONFIRM_OK
() => Services.dns.currentTrrConfirmationState == 2,
`Timed out waiting for confirmation success. Currently ${Services.dns.currentTrrConfirmationState}`,
1,
5000
);
newConfirmationPort = await trrServer.execute(
`global.gDoHPortsLog[global.gDoHPortsLog.length-1]`
);
notEqual(
initialPort,
newConfirmationPort,
"Failed confirmation must cycle the connection"
);
// Two reqs for each bar5 and bar6 .
await new TRRDNSListener("bar5.example.org", "9.8.7.6");
await new TRRDNSListener("bar6.example.org", "9.8.7.6");
let dohReqPortLog = await trrServer.execute(`global.gDoHPortsLog`);
const uniquePorts = new Set(dohReqPortLog.map(([_, port]) => port));
if (uniquePorts.size < 3) {
info(JSON.stringify(dohReqPortLog));
}
greaterOrEqual(
uniquePorts.size,
3,
"Connection must be cycled at least twice"
);
});