/* 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";
var { setTimeout } = ChromeUtils.importESModule(
"resource://gre/modules/Timer.sys.mjs"
);
let trrServer;
let h3Port;
let host;
add_setup(async
function setup() {
trr_test_setup();
h3Port = Services.env.get(
"MOZHTTP3_PORT_ECH");
Assert.notEqual(h3Port,
null);
Assert.notEqual(h3Port,
"");
host = `https:
//alt1.example.com:${h3Port}/`;
Services.prefs.setIntPref(
"network.trr.mode", Ci.nsIDNSService.MODE_TRRFIRST);
});
registerCleanupFunction(async () => {
trr_clear_prefs();
if (trrServer) {
await trrServer.stop();
}
});
function makeChan(url) {
let chan = NetUtil.newChannel({
uri: url,
loadUsingSystemPrincipal:
true,
contentPolicyType: Ci.nsIContentPolicy.TYPE_DOCUMENT,
}).QueryInterface(Ci.nsIHttpChannel);
return chan;
}
function channelOpenPromise(chan, flags) {
return new Promise(resolve => {
function finish(req, buffer) {
resolve([req, buffer]);
}
chan.asyncOpen(
new ChannelListener(finish,
null, flags));
});
}
function ActivityObserver() {
this.activites = [];
}
ActivityObserver.prototype = {
activites: [],
observeActivity(
aHttpChannel,
aActivityType,
aActivitySubtype,
aTimestamp,
aExtraSizeData,
aExtraStringData
) {
try {
aHttpChannel.QueryInterface(Ci.nsINullChannel);
aHttpChannel.QueryInterface(Ci.nsIChannel);
if (aHttpChannel.URI.spec === host) {
dump(
"*** HTTP Activity 0x" +
aActivityType.toString(
16) +
" 0x" +
aActivitySubtype.toString(
16) +
" " +
aExtraStringData +
"\n"
);
this.activites.push({ host, subType: aActivitySubtype });
}
}
catch (e) {}
},
};
function checkHttpActivities(activites, expected) {
let foundTransClosed =
false;
for (let activity of activites) {
switch (activity.subType) {
case Ci.nsIHttpActivityObserver.ACTIVITY_SUBTYPE_TRANSACTION_CLOSE:
foundTransClosed =
true;
break;
default:
break;
}
}
Assert.equal(
foundTransClosed,
expected,
"The activity of speculative transaction should match"
);
}
// Test steps:
// 1. Create a TRR server that serves the HTTPS record for the H3 server.
// 2. Create a channel and connect to the H3 server.
// 3. Use nsIHttpActivityObserver to observe if we receive the HTTP activity
// that is sent from the speculative transaction.
async
function doTestAltSVCWithHTTPSRR(foundActivity) {
trrServer =
new TRRServer();
await trrServer.start();
let observerService = Cc[
"@mozilla.org/network/http-activity-distributor;1"
].getService(Ci.nsIHttpActivityDistributor);
Services.prefs.setIntPref(
"network.trr.mode", Ci.nsIDNSService.MODE_TRRONLY);
Services.prefs.setCharPref(
"network.trr.uri",
`https:
//foo.example.com:${trrServer.port()}/dns-query`
);
let portPrefixedName = `_${h3Port}._https.alt1.example.com`;
let vals = [
{ key:
"alpn", value:
"h3" },
{ key:
"port", value: h3Port },
];
await trrServer.registerDoHAnswers(portPrefixedName,
"HTTPS", {
answers: [
{
name: portPrefixedName,
ttl:
55,
type:
"HTTPS",
flush:
false,
data: {
priority:
1,
name:
".",
values: vals,
},
},
],
});
await trrServer.registerDoHAnswers(
"alt1.example.com",
"A", {
answers: [
{
name:
"alt1.example.com",
ttl:
55,
type:
"A",
flush:
false,
data:
"127.0.0.1",
},
],
});
let chan = makeChan(`${host}alt_svc_header`);
let h3AltSvc =
":" + h3Port;
chan.setRequestHeader(
"x-altsvc", h3AltSvc,
false);
let observer =
new ActivityObserver();
let responseObserver = {
QueryInterface: ChromeUtils.generateQI([
"nsIObserver"]),
observe(aSubject, aTopic) {
let channel = aSubject.QueryInterface(Ci.nsIChannel);
if (
aTopic ==
"http-on-examine-response" &&
channel.URI.spec === `${host}alt_svc_header`
) {
Services.obs.removeObserver(
responseObserver,
"http-on-examine-response"
);
observerService.addObserver(observer);
channel.suspend();
// We need to close all connections here, otherwise we are not allowed
// to create a specul connection to validate the Alt-svc header.
Services.obs.notifyObservers(
null,
"net:cancel-all-connections");
// eslint-disable-next-line mozilla/no-arbitrary-setTimeout
setTimeout(
function () {
channel.resume();
},
3000);
}
},
};
Services.obs.addObserver(responseObserver,
"http-on-examine-response");
await channelOpenPromise(chan);
// Some dekay here to collect HTTP activites.
// eslint-disable-next-line mozilla/no-arbitrary-setTimeout
await
new Promise(resolve => setTimeout(resolve,
3000));
checkHttpActivities(observer.activites, foundActivity);
await trrServer.stop();
observerService.removeObserver(observer);
}
add_task(async
function testAltSVCWithHTTPSRR() {
Services.prefs.setBoolPref(
"network.http.skip_alt_svc_validation_on_https_rr",
false
);
await doTestAltSVCWithHTTPSRR(
true);
// Clear the alt-svc mapping.
Services.obs.notifyObservers(
null,
"last-pb-context-exited");
Services.obs.notifyObservers(
null,
"net:cancel-all-connections");
// eslint-disable-next-line mozilla/no-arbitrary-setTimeout
await
new Promise(resolve => setTimeout(resolve,
3000));
Services.prefs.setBoolPref(
"network.http.skip_alt_svc_validation_on_https_rr",
true
);
await doTestAltSVCWithHTTPSRR(
false);
});