Source code
Revision control
Copy as Markdown
Other Tools
Test Info: Warnings
- This test gets skipped with pattern: os == 'android' OR os == 'win'
- 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
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const { NodeHTTP2Server } = ChromeUtils.importESModule(
);
var { setTimeout } = ChromeUtils.importESModule(
"resource://gre/modules/Timer.sys.mjs"
);
let trrServer;
let h2Server;
add_setup(async function () {
let certdb = Cc["@mozilla.org/security/x509certdb;1"].getService(
Ci.nsIX509CertDB
);
addCertFromFile(certdb, "http2-ca.pem", "CTu,u,u");
Services.prefs.setBoolPref("network.http.happy_eyeballs_enabled", true);
trrServer = new TRRServer();
await trrServer.start();
trr_test_setup();
Services.prefs.setIntPref("network.trr.mode", 3);
Services.prefs.setCharPref(
"network.trr.uri",
`https://foo.example.com:${trrServer.port()}/dns-query`
);
h2Server = new NodeHTTP2Server();
await h2Server.start();
await h2Server.registerPathHandler("/", (_req, resp) => {
resp.writeHead(200, { "Content-Type": "text/plain" });
resp.end("ok");
});
registerCleanupFunction(async () => {
Services.prefs.clearUserPref("network.http.happy_eyeballs_enabled");
Services.prefs.clearUserPref("network.http.speculative-parallel-limit");
trr_clear_prefs();
if (trrServer) {
await trrServer.stop();
}
if (h2Server) {
await h2Server.stop();
}
});
});
async function resetConnections() {
Services.obs.notifyObservers(null, "net:cancel-all-connections");
let nssComponent = Cc["@mozilla.org/psm;1"].getService(Ci.nsINSSComponent);
await nssComponent.asyncClearSSLExternalAndInternalSessionCache();
Services.dns.clearCache(true);
// eslint-disable-next-line mozilla/no-arbitrary-setTimeout
await new Promise(resolve => setTimeout(resolve, 1000));
}
async function openChannelAndGetTimings(uri) {
let chan = NetUtil.newChannel({
uri,
loadUsingSystemPrincipal: true,
contentPolicyType: Ci.nsIContentPolicy.TYPE_DOCUMENT,
}).QueryInterface(Ci.nsIHttpChannel);
chan.loadFlags = Ci.nsIChannel.LOAD_INITIAL_DOCUMENT_URI;
let timedChannel = chan.QueryInterface(Ci.nsITimedChannel);
let internalChannel = chan.QueryInterface(Ci.nsIHttpChannelInternal);
let transportStatuses = [];
let activityDistributor = Cc[
"@mozilla.org/network/http-activity-distributor;1"
].getService(Ci.nsIHttpActivityDistributor);
let observer = {
observeActivity(aHttpChannel, aActivityType, aActivitySubtype) {
if (
aActivityType ===
Ci.nsIHttpActivityObserver.ACTIVITY_TYPE_SOCKET_TRANSPORT
) {
try {
let otherChan = aHttpChannel.QueryInterface(Ci.nsIChannel);
if (otherChan.URI.spec === uri) {
transportStatuses.push(aActivitySubtype);
}
} catch (e) {}
}
},
};
activityDistributor.addObserver(observer);
await new Promise(resolve => {
chan.asyncOpen(
new ChannelListener((_req, _buf) => resolve(), null, CL_ALLOW_UNKNOWN_CL)
);
});
activityDistributor.removeObserver(observer);
return { timedChannel, internalChannel, transportStatuses };
}
function logTimings(timedChannel) {
info(`domainLookupStartTime=${timedChannel.domainLookupStartTime}`);
info(`domainLookupEndTime=${timedChannel.domainLookupEndTime}`);
info(`connectStartTime=${timedChannel.connectStartTime}`);
info(`tcpConnectEndTime=${timedChannel.tcpConnectEndTime}`);
info(`secureConnectionStartTime=${timedChannel.secureConnectionStartTime}`);
info(`connectEndTime=${timedChannel.connectEndTime}`);
}
function assertTimingsSet(timedChannel) {
Assert.greater(
timedChannel.domainLookupStartTime,
0,
"domainLookupStartTime should be set"
);
Assert.greater(
timedChannel.domainLookupEndTime,
0,
"domainLookupEndTime should be set"
);
Assert.greater(
timedChannel.connectStartTime,
0,
"connectStartTime should be set"
);
Assert.greater(
timedChannel.connectEndTime,
0,
"connectEndTime should be set"
);
Assert.greater(
timedChannel.secureConnectionStartTime,
0,
"secureConnectionStartTime should be set"
);
}
function assertTransportStatusPresent(transportStatuses, expected) {
for (let status of expected) {
Assert.ok(
transportStatuses.includes(status),
`transport status 0x${status.toString(16)} should be present`
);
}
}
function assertTransportStatusUnique(transportStatuses, expected) {
for (let status of expected) {
Assert.equal(
transportStatuses.filter(s => s === status).length,
1,
`transport status 0x${status.toString(16)} should appear exactly once`
);
}
}
function assertTimingsOrder(timedChannel) {
Assert.lessOrEqual(
timedChannel.domainLookupStartTime,
timedChannel.domainLookupEndTime,
"domainLookupStart <= domainLookupEnd"
);
Assert.lessOrEqual(
timedChannel.connectStartTime,
timedChannel.secureConnectionStartTime,
"connectStart <= secureConnectionStart"
);
Assert.lessOrEqual(
timedChannel.secureConnectionStartTime,
timedChannel.connectEndTime,
"secureConnectionStart <= connectEnd"
);
}
add_task(async function test_tcp_timings_speculative_enabled() {
await resetConnections();
Services.prefs.setIntPref("network.http.speculative-parallel-limit", 6);
let host = "foo.example.com";
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: [{ name: host, ttl: 55, type: "AAAA", flush: false, data: "::1" }],
});
let { timedChannel, internalChannel, transportStatuses } =
await openChannelAndGetTimings(`https://${host}:${h2Server.port()}/`);
logTimings(timedChannel);
assertTimingsSet(timedChannel);
assertTimingsOrder(timedChannel);
Assert.ok(
internalChannel.remoteAddress === "127.0.0.1" ||
internalChannel.remoteAddress === "::1",
`remoteAddress should be 127.0.0.1 or ::1, got ${internalChannel.remoteAddress}`
);
let expectedStatuses = [
Ci.nsISocketTransport.STATUS_RESOLVING,
Ci.nsISocketTransport.STATUS_RESOLVED,
Ci.nsISocketTransport.STATUS_CONNECTING_TO,
Ci.nsISocketTransport.STATUS_CONNECTED_TO,
];
assertTransportStatusPresent(transportStatuses, expectedStatuses);
assertTransportStatusUnique(transportStatuses, expectedStatuses);
});
add_task(async function test_tcp_timings_speculative_disabled() {
await resetConnections();
Services.prefs.setIntPref("network.http.speculative-parallel-limit", 0);
let host = "alt1.example.com";
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: [{ name: host, ttl: 55, type: "AAAA", flush: false, data: "::1" }],
});
let { timedChannel, internalChannel, transportStatuses } =
await openChannelAndGetTimings(`https://${host}:${h2Server.port()}/`);
logTimings(timedChannel);
assertTimingsSet(timedChannel);
assertTimingsOrder(timedChannel);
Assert.ok(
internalChannel.remoteAddress === "127.0.0.1" ||
internalChannel.remoteAddress === "::1",
`remoteAddress should be 127.0.0.1 or ::1, got ${internalChannel.remoteAddress}`
);
let expectedStatuses = [
Ci.nsISocketTransport.STATUS_RESOLVING,
Ci.nsISocketTransport.STATUS_RESOLVED,
Ci.nsISocketTransport.STATUS_CONNECTING_TO,
Ci.nsISocketTransport.STATUS_CONNECTED_TO,
];
assertTransportStatusPresent(transportStatuses, expectedStatuses);
assertTransportStatusUnique(transportStatuses, expectedStatuses);
});
function assertH3Timings(timedChannel) {
Assert.greater(
timedChannel.domainLookupStartTime,
0,
"domainLookupStartTime should be set"
);
Assert.greater(
timedChannel.domainLookupEndTime,
0,
"domainLookupEndTime should be set"
);
Assert.greater(
timedChannel.connectStartTime,
0,
"connectStartTime should be set"
);
Assert.greater(
timedChannel.connectEndTime,
0,
"connectEndTime should be set"
);
Assert.lessOrEqual(
timedChannel.domainLookupStartTime,
timedChannel.domainLookupEndTime,
"domainLookupStart <= domainLookupEnd"
);
Assert.lessOrEqual(
timedChannel.domainLookupEndTime,
timedChannel.connectStartTime,
"domainLookupEnd <= connectStart"
);
// For HTTP/3, connectStart == secureConnectionStart.
Assert.equal(
timedChannel.connectStartTime,
timedChannel.secureConnectionStartTime,
"connectStart == secureConnectionStart for HTTP/3"
);
}
async function do_test_h3_timings(host) {
await resetConnections();
let { timedChannel, internalChannel, transportStatuses } =
await openChannelAndGetTimings(`https://${host}/`);
logTimings(timedChannel);
assertH3Timings(timedChannel);
Assert.equal(
internalChannel.remoteAddress,
"127.0.0.1",
"remoteAddress should be 127.0.0.1 for HTTP/3"
);
let expectedStatuses = [
Ci.nsISocketTransport.STATUS_RESOLVING,
Ci.nsISocketTransport.STATUS_RESOLVED,
];
assertTransportStatusPresent(transportStatuses, expectedStatuses);
assertTransportStatusUnique(transportStatuses, expectedStatuses);
}
add_task(async function test_h3_timings() {
let h3Port = Services.env.get("MOZHTTP3_PORT");
Assert.notEqual(h3Port, null);
Assert.notEqual(h3Port, "");
await resetConnections();
Services.prefs.setBoolPref("network.http.http3.enable", true);
Services.prefs.setBoolPref("network.dns.disableIPv6", true);
let host = "alt2.example.com";
Services.prefs.setCharPref(
"network.http.http3.alt-svc-mapping-for-testing",
`${host};h3=:${h3Port}`
);
await trrServer.registerDoHAnswers(host, "A", {
answers: [
{ name: host, ttl: 55, type: "A", flush: false, data: "127.0.0.1" },
],
});
Services.prefs.setIntPref("network.http.speculative-parallel-limit", 0);
await do_test_h3_timings(host);
Services.prefs.setIntPref("network.http.speculative-parallel-limit", 6);
await do_test_h3_timings(host);
Services.prefs.clearUserPref("network.http.http3.enable");
Services.prefs.clearUserPref("network.dns.disableIPv6");
Services.prefs.clearUserPref(
"network.http.http3.alt-svc-mapping-for-testing"
);
});