зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1671885
- Add a test case for ech retry r=necko-reviewers,dragana
Differential Revision: https://phabricator.services.mozilla.com/D97992
This commit is contained in:
Родитель
7fe2cdbb1f
Коммит
af36035662
|
@ -936,5 +936,27 @@ bool ConnectionEntry::RemoveTransFromPendingQ(nsHttpTransaction* aTrans) {
|
|||
return true;
|
||||
}
|
||||
|
||||
void ConnectionEntry::MaybeUpdateEchConfig(nsHttpConnectionInfo* aConnInfo) {
|
||||
if (!mConnInfo->HashKey().Equals(aConnInfo->HashKey())) {
|
||||
return;
|
||||
}
|
||||
|
||||
const nsCString& echConfig = aConnInfo->GetEchConfig();
|
||||
if (mConnInfo->GetEchConfig().Equals(echConfig)) {
|
||||
return;
|
||||
}
|
||||
|
||||
LOG(("ConnectionEntry::MaybeUpdateEchConfig [ci=%s]\n",
|
||||
mConnInfo->HashKey().get()));
|
||||
|
||||
mConnInfo->SetEchConfig(echConfig);
|
||||
|
||||
// If echConfig is changed, we should close all half opens and idle
|
||||
// connections. This is to make sure the new echConfig will be used for the
|
||||
// next connection.
|
||||
CloseAllHalfOpens();
|
||||
CloseIdleConnections();
|
||||
}
|
||||
|
||||
} // namespace net
|
||||
} // namespace mozilla
|
||||
|
|
|
@ -185,6 +185,8 @@ class ConnectionEntry {
|
|||
|
||||
bool RemoveTransFromPendingQ(nsHttpTransaction* aTrans);
|
||||
|
||||
void MaybeUpdateEchConfig(nsHttpConnectionInfo* aConnInfo);
|
||||
|
||||
private:
|
||||
void InsertIntoIdleConnections_internal(nsHttpConnection* conn);
|
||||
void RemoveFromIdleConnectionsIndex(size_t inx);
|
||||
|
|
|
@ -378,7 +378,7 @@ nsHttpConnectionInfo::CloneAndAdoptHTTPSSVCRecord(
|
|||
clone = new nsHttpConnectionInfo(
|
||||
mOrigin, mOriginPort, alpn ? Get<0>(*alpn) : EmptyCString(), mUsername,
|
||||
mTopWindowOrigin, mProxyInfo, mOriginAttributes, name,
|
||||
port ? *port : mRoutedPort, mIsolated, isHttp3);
|
||||
port ? *port : mOriginPort, mIsolated, isHttp3);
|
||||
}
|
||||
|
||||
// Make sure the anonymous, insecure-scheme, and private flags are transferred
|
||||
|
|
|
@ -1627,6 +1627,10 @@ nsresult nsHttpConnectionMgr::ProcessNewTransaction(nsHttpTransaction* trans) {
|
|||
trans->Caps() & NS_HTTP_DISALLOW_HTTP3);
|
||||
MOZ_ASSERT(ent);
|
||||
|
||||
if (gHttpHandler->EchConfigEnabled()) {
|
||||
ent->MaybeUpdateEchConfig(ci);
|
||||
}
|
||||
|
||||
ReportProxyTelemetry(ent);
|
||||
|
||||
// Check if the transaction already has a sticky reference to a connection.
|
||||
|
@ -3444,36 +3448,25 @@ void nsHttpConnectionMgr::MoveToWildCardConnEntry(
|
|||
ent->MoveConnection(proxyConn, wcEnt);
|
||||
}
|
||||
|
||||
bool nsHttpConnectionMgr::MoveTransToNewConnEntry(nsHttpTransaction* aTrans,
|
||||
nsHttpConnectionInfo* aNewCI,
|
||||
bool aNoHttp3ForNewEntry) {
|
||||
bool nsHttpConnectionMgr::MoveTransToNewConnEntry(
|
||||
nsHttpTransaction* aTrans, nsHttpConnectionInfo* aNewCI) {
|
||||
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
|
||||
|
||||
LOG(
|
||||
("nsHttpConnectionMgr::MoveTransToNewConnEntry: trans=%p aNewCI=%s "
|
||||
"aNoHttp3ForNewEntry=%d",
|
||||
aTrans, aNewCI->HashKey().get(), aNoHttp3ForNewEntry));
|
||||
LOG(("nsHttpConnectionMgr::MoveTransToNewConnEntry: trans=%p aNewCI=%s",
|
||||
aTrans, aNewCI->HashKey().get()));
|
||||
|
||||
bool prohibitWildCard = !!aTrans->TunnelProvider();
|
||||
bool noHttp3 = aTrans->Caps() & NS_HTTP_DISALLOW_HTTP3;
|
||||
bool noHttp2 = aTrans->Caps() & NS_HTTP_DISALLOW_SPDY;
|
||||
// Step 1: Check if the new entry is the same as the old one.
|
||||
ConnectionEntry* oldEntry = GetOrCreateConnectionEntry(
|
||||
aTrans->ConnectionInfo(), prohibitWildCard, noHttp2, noHttp3);
|
||||
|
||||
ConnectionEntry* newEntry = GetOrCreateConnectionEntry(
|
||||
aNewCI, prohibitWildCard, noHttp2, noHttp3 || aNoHttp3ForNewEntry);
|
||||
|
||||
if (oldEntry == newEntry) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Step 2: Try to find the undispatched transaction.
|
||||
if (!oldEntry->RemoveTransFromPendingQ(aTrans)) {
|
||||
// Step 1: Get the transaction's connection entry.
|
||||
ConnectionEntry* entry = mCT.GetWeak(aTrans->ConnectionInfo()->HashKey());
|
||||
if (!entry) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Step 3: Add the transaction to the new entry.
|
||||
// Step 2: Try to find the undispatched transaction.
|
||||
if (!entry->RemoveTransFromPendingQ(aTrans)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Step 3: Add the transaction.
|
||||
aTrans->UpdateConnectionInfo(aNewCI);
|
||||
Unused << ProcessNewTransaction(aTrans);
|
||||
return true;
|
||||
|
|
|
@ -75,8 +75,7 @@ class nsHttpConnectionMgr final : public HttpConnectionMgrShell,
|
|||
// one. Returns true if the transaction is moved successfully, otherwise
|
||||
// returns false.
|
||||
bool MoveTransToNewConnEntry(nsHttpTransaction* aTrans,
|
||||
nsHttpConnectionInfo* aNewCI,
|
||||
bool aNoHttp3ForNewEntry = false);
|
||||
nsHttpConnectionInfo* aNewCI);
|
||||
|
||||
// This is used to force an idle connection to be closed and removed from
|
||||
// the idle connection list. It is called when the idle connection detects
|
||||
|
|
|
@ -64,11 +64,6 @@ static NS_DEFINE_CID(kMultiplexInputStream, NS_MULTIPLEXINPUTSTREAM_CID);
|
|||
// looking for a response header
|
||||
#define MAX_INVALID_RESPONSE_BODY_SIZE (1024 * 128)
|
||||
|
||||
// TODO: These values should be removed when bug 1654332 is landed.
|
||||
#define MOCK_SSL_ERROR_ECH_RETRY_WITH_ECH (SSL_ERROR_BASE + 188)
|
||||
#define MOCK_SSL_ERROR_ECH_RETRY_WITHOUT_ECH (SSL_ERROR_BASE + 189)
|
||||
#define MOCK_SSL_ERROR_ECH_FAILED (SSL_ERROR_BASE + 190)
|
||||
|
||||
using namespace mozilla::net;
|
||||
|
||||
namespace mozilla {
|
||||
|
@ -1262,8 +1257,7 @@ void nsHttpTransaction::PrepareConnInfoForRetry(nsresult aReason) {
|
|||
}
|
||||
});
|
||||
|
||||
if (aReason ==
|
||||
psm::GetXPCOMFromNSSError(MOCK_SSL_ERROR_ECH_RETRY_WITHOUT_ECH)) {
|
||||
if (aReason == psm::GetXPCOMFromNSSError(SSL_ERROR_ECH_RETRY_WITHOUT_ECH)) {
|
||||
LOG((" Got SSL_ERROR_ECH_RETRY_WITHOUT_ECH, use empty echConfig to retry"));
|
||||
failedConnInfo->SetEchConfig(EmptyCString());
|
||||
failedConnInfo.swap(mConnInfo);
|
||||
|
@ -1271,8 +1265,8 @@ void nsHttpTransaction::PrepareConnInfoForRetry(nsresult aReason) {
|
|||
return;
|
||||
}
|
||||
|
||||
if (aReason == psm::GetXPCOMFromNSSError(MOCK_SSL_ERROR_ECH_RETRY_WITH_ECH)) {
|
||||
LOG((" Got SSL_ERROR_ECH_RETRY_WITHOUT_ECH, use retry echConfig"));
|
||||
if (aReason == psm::GetXPCOMFromNSSError(SSL_ERROR_ECH_RETRY_WITH_ECH)) {
|
||||
LOG((" Got SSL_ERROR_ECH_RETRY_WITH_ECH, use retry echConfig"));
|
||||
MOZ_ASSERT(mConnection);
|
||||
|
||||
nsCOMPtr<nsISupports> secInfo;
|
||||
|
@ -1297,10 +1291,10 @@ void nsHttpTransaction::PrepareConnInfoForRetry(nsresult aReason) {
|
|||
|
||||
// Note that we retry the connection not only for SSL_ERROR_ECH_FAILED, but
|
||||
// also for all failure cases.
|
||||
if (aReason == psm::GetXPCOMFromNSSError(MOCK_SSL_ERROR_ECH_FAILED) ||
|
||||
if (aReason == psm::GetXPCOMFromNSSError(SSL_ERROR_ECH_FAILED) ||
|
||||
NS_FAILED(aReason)) {
|
||||
LOG((" Got SSL_ERROR_ECH_FAILED, try other records"));
|
||||
if (aReason == psm::GetXPCOMFromNSSError(MOCK_SSL_ERROR_ECH_FAILED)) {
|
||||
if (aReason == psm::GetXPCOMFromNSSError(SSL_ERROR_ECH_FAILED)) {
|
||||
id = Telemetry::TRANSACTION_ECH_RETRY_ECH_FAILED_COUNT;
|
||||
}
|
||||
if (mRecordsForRetry.IsEmpty()) {
|
||||
|
@ -1347,6 +1341,28 @@ void nsHttpTransaction::PrepareConnInfoForRetry(nsresult aReason) {
|
|||
}
|
||||
}
|
||||
|
||||
void nsHttpTransaction::MaybeReportFailedSVCDomain(
|
||||
nsresult aReason, nsHttpConnectionInfo* aFailedConnInfo) {
|
||||
if (aReason == psm::GetXPCOMFromNSSError(SSL_ERROR_ECH_RETRY_WITHOUT_ECH) ||
|
||||
aReason != psm::GetXPCOMFromNSSError(SSL_ERROR_ECH_RETRY_WITH_ECH)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Telemetry::Accumulate(Telemetry::DNS_HTTPSSVC_CONNECTION_FAILED_REASON,
|
||||
ErrorCodeToFailedReason(aReason));
|
||||
|
||||
nsCOMPtr<nsIDNSService> dns = do_GetService(NS_DNSSERVICE_CONTRACTID);
|
||||
if (dns) {
|
||||
const nsCString& failedHost = aFailedConnInfo->GetRoutedHost().IsEmpty()
|
||||
? aFailedConnInfo->GetOrigin()
|
||||
: aFailedConnInfo->GetRoutedHost();
|
||||
LOG(("add failed domain name [%s] -> [%s] to exclusion list",
|
||||
aFailedConnInfo->GetOrigin().get(), failedHost.get()));
|
||||
Unused << dns->ReportFailedSVCDomainName(aFailedConnInfo->GetOrigin(),
|
||||
failedHost);
|
||||
}
|
||||
}
|
||||
|
||||
void nsHttpTransaction::Close(nsresult reason) {
|
||||
LOG(("nsHttpTransaction::Close [this=%p reason=%" PRIx32 "]\n", this,
|
||||
static_cast<uint32_t>(reason)));
|
||||
|
@ -1512,16 +1528,7 @@ void nsHttpTransaction::Close(nsresult reason) {
|
|||
!reallySentData || connReused)) ||
|
||||
restartToFallbackConnInfo) {
|
||||
if (restartToFallbackConnInfo) {
|
||||
Telemetry::Accumulate(Telemetry::DNS_HTTPSSVC_CONNECTION_FAILED_REASON,
|
||||
ErrorCodeToFailedReason(reason));
|
||||
nsCOMPtr<nsIDNSService> dns = do_GetService(NS_DNSSERVICE_CONTRACTID);
|
||||
if (dns) {
|
||||
LOG(("add failed domain name [%s] -> [%s] to exclusion list",
|
||||
mConnInfo->GetOrigin().get(), mConnInfo->GetRoutedHost().get()));
|
||||
Unused << dns->ReportFailedSVCDomainName(mConnInfo->GetOrigin(),
|
||||
mConnInfo->GetRoutedHost());
|
||||
}
|
||||
|
||||
MaybeReportFailedSVCDomain(reason, mConnInfo);
|
||||
PrepareConnInfoForRetry(reason);
|
||||
mDontRetryWithDirectRoute = true;
|
||||
LOG(
|
||||
|
@ -3293,8 +3300,8 @@ void nsHttpTransaction::HandleFallback(
|
|||
LOG(("nsHttpTransaction %p HandleFallback to connInfo[%s]", this,
|
||||
aFallbackConnInfo->HashKey().get()));
|
||||
|
||||
bool foundInPendingQ = gHttpHandler->ConnMgr()->MoveTransToNewConnEntry(
|
||||
this, aFallbackConnInfo, true);
|
||||
bool foundInPendingQ =
|
||||
gHttpHandler->ConnMgr()->MoveTransToNewConnEntry(this, aFallbackConnInfo);
|
||||
if (!foundInPendingQ) {
|
||||
MOZ_ASSERT(false, "transaction not in entry");
|
||||
return;
|
||||
|
|
|
@ -228,6 +228,9 @@ class nsHttpTransaction final : public nsAHttpTransaction,
|
|||
already_AddRefed<nsHttpConnectionInfo> PrepareFastFallbackConnInfo(
|
||||
bool aEchConfigUsed);
|
||||
|
||||
void MaybeReportFailedSVCDomain(nsresult aReason,
|
||||
nsHttpConnectionInfo* aFailedConnInfo);
|
||||
|
||||
already_AddRefed<Http2PushedStreamWrapper> TakePushedStreamById(
|
||||
uint32_t aStreamId);
|
||||
|
||||
|
|
|
@ -351,3 +351,106 @@ function deserialize_from_escaped_string(str) {
|
|||
objectInStream.setInputStream(pipe.inputStream);
|
||||
return objectInStream.readObject(true);
|
||||
}
|
||||
|
||||
// Copied from head_psm.js.
|
||||
function add_tls_server_setup(serverBinName, certsPath, addDefaultRoot = true) {
|
||||
add_test(function() {
|
||||
_setupTLSServerTest(serverBinName, certsPath, addDefaultRoot);
|
||||
});
|
||||
}
|
||||
|
||||
// Do not call this directly; use add_tls_server_setup
|
||||
function _setupTLSServerTest(serverBinName, certsPath, addDefaultRoot) {
|
||||
asyncStartTLSTestServer(serverBinName, certsPath, addDefaultRoot).then(
|
||||
run_next_test
|
||||
);
|
||||
}
|
||||
|
||||
async function asyncStartTLSTestServer(
|
||||
serverBinName,
|
||||
certsPath,
|
||||
addDefaultRoot
|
||||
) {
|
||||
const { HttpServer } = ChromeUtils.import(
|
||||
"resource://testing-common/httpd.js"
|
||||
);
|
||||
let certdb = Cc["@mozilla.org/security/x509certdb;1"].getService(
|
||||
Ci.nsIX509CertDB
|
||||
);
|
||||
// The trusted CA that is typically used for "good" certificates.
|
||||
if (addDefaultRoot) {
|
||||
addCertFromFile(certdb, `${certsPath}/test-ca.pem`, "CTu,u,u");
|
||||
}
|
||||
|
||||
const CALLBACK_PORT = 8444;
|
||||
|
||||
let envSvc = Cc["@mozilla.org/process/environment;1"].getService(
|
||||
Ci.nsIEnvironment
|
||||
);
|
||||
let greBinDir = Services.dirsvc.get("GreBinD", Ci.nsIFile);
|
||||
envSvc.set("DYLD_LIBRARY_PATH", greBinDir.path);
|
||||
// TODO(bug 1107794): Android libraries are in /data/local/xpcb, but "GreBinD"
|
||||
// does not return this path on Android, so hard code it here.
|
||||
envSvc.set("LD_LIBRARY_PATH", greBinDir.path + ":/data/local/xpcb");
|
||||
envSvc.set("MOZ_TLS_SERVER_DEBUG_LEVEL", "3");
|
||||
envSvc.set("MOZ_TLS_SERVER_CALLBACK_PORT", CALLBACK_PORT);
|
||||
|
||||
let httpServer = new HttpServer();
|
||||
let serverReady = new Promise(resolve => {
|
||||
httpServer.registerPathHandler("/", function handleServerCallback(
|
||||
aRequest,
|
||||
aResponse
|
||||
) {
|
||||
aResponse.setStatusLine(aRequest.httpVersion, 200, "OK");
|
||||
aResponse.setHeader("Content-Type", "text/plain");
|
||||
let responseBody = "OK!";
|
||||
aResponse.bodyOutputStream.write(responseBody, responseBody.length);
|
||||
executeSoon(function() {
|
||||
httpServer.stop(resolve);
|
||||
});
|
||||
});
|
||||
httpServer.start(CALLBACK_PORT);
|
||||
});
|
||||
|
||||
let serverBin = _getBinaryUtil(serverBinName);
|
||||
let process = Cc["@mozilla.org/process/util;1"].createInstance(Ci.nsIProcess);
|
||||
process.init(serverBin);
|
||||
let certDir = do_get_file(certsPath, false);
|
||||
Assert.ok(certDir.exists(), `certificate folder (${certsPath}) should exist`);
|
||||
// Using "sql:" causes the SQL DB to be used so we can run tests on Android.
|
||||
process.run(false, ["sql:" + certDir.path, Services.appinfo.processID], 2);
|
||||
|
||||
registerCleanupFunction(function() {
|
||||
process.kill();
|
||||
});
|
||||
|
||||
await serverReady;
|
||||
}
|
||||
|
||||
function _getBinaryUtil(binaryUtilName) {
|
||||
let utilBin = Services.dirsvc.get("GreD", Ci.nsIFile);
|
||||
// On macOS, GreD is .../Contents/Resources, and most binary utilities
|
||||
// are located there, but certutil is in GreBinD (or .../Contents/MacOS),
|
||||
// so we have to change the path accordingly.
|
||||
if (binaryUtilName === "certutil") {
|
||||
utilBin = Services.dirsvc.get("GreBinD", Ci.nsIFile);
|
||||
}
|
||||
utilBin.append(binaryUtilName + mozinfo.bin_suffix);
|
||||
// If we're testing locally, the above works. If not, the server executable
|
||||
// is in another location.
|
||||
if (!utilBin.exists()) {
|
||||
utilBin = Services.dirsvc.get("CurWorkD", Ci.nsIFile);
|
||||
while (utilBin.path.includes("xpcshell")) {
|
||||
utilBin = utilBin.parent;
|
||||
}
|
||||
utilBin.append("bin");
|
||||
utilBin.append(binaryUtilName + mozinfo.bin_suffix);
|
||||
}
|
||||
// But maybe we're on Android, where binaries are in /data/local/xpcb.
|
||||
if (!utilBin.exists()) {
|
||||
utilBin.initWithPath("/data/local/xpcb/");
|
||||
utilBin.append(binaryUtilName);
|
||||
}
|
||||
Assert.ok(utilBin.exists(), `Binary util ${binaryUtilName} should exist`);
|
||||
return utilBin;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,286 @@
|
|||
/* 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";
|
||||
|
||||
ChromeUtils.import("resource://gre/modules/NetUtil.jsm");
|
||||
|
||||
let prefs;
|
||||
let h2Port;
|
||||
let listen;
|
||||
let trrServer;
|
||||
|
||||
const dns = Cc["@mozilla.org/network/dns-service;1"].getService(
|
||||
Ci.nsIDNSService
|
||||
);
|
||||
const certOverrideService = Cc[
|
||||
"@mozilla.org/security/certoverride;1"
|
||||
].getService(Ci.nsICertOverrideService);
|
||||
const threadManager = Cc["@mozilla.org/thread-manager;1"].getService(
|
||||
Ci.nsIThreadManager
|
||||
);
|
||||
const mainThread = threadManager.currentThread;
|
||||
|
||||
const defaultOriginAttributes = {};
|
||||
|
||||
function setup() {
|
||||
let env = Cc["@mozilla.org/process/environment;1"].getService(
|
||||
Ci.nsIEnvironment
|
||||
);
|
||||
h2Port = env.get("MOZHTTP2_PORT");
|
||||
Assert.notEqual(h2Port, null);
|
||||
Assert.notEqual(h2Port, "");
|
||||
|
||||
// Set to allow the cert presented by our H2 server
|
||||
do_get_profile();
|
||||
prefs = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch);
|
||||
|
||||
prefs.setBoolPref("network.security.esni.enabled", false);
|
||||
prefs.setBoolPref("network.http.spdy.enabled", true);
|
||||
prefs.setBoolPref("network.http.spdy.enabled.http2", true);
|
||||
// the TRR server is on 127.0.0.1
|
||||
prefs.setCharPref("network.trr.bootstrapAddress", "127.0.0.1");
|
||||
|
||||
// make all native resolve calls "secretly" resolve localhost instead
|
||||
prefs.setBoolPref("network.dns.native-is-localhost", true);
|
||||
|
||||
// 0 - off, 1 - race, 2 TRR first, 3 TRR only, 4 shadow
|
||||
prefs.setIntPref("network.trr.mode", 2); // TRR first
|
||||
prefs.setBoolPref("network.trr.wait-for-portal", false);
|
||||
// don't confirm that TRR is working, just go!
|
||||
prefs.setCharPref("network.trr.confirmationNS", "skip");
|
||||
|
||||
// So we can change the pref without clearing the cache to check a pushed
|
||||
// record with a TRR path that fails.
|
||||
Services.prefs.setBoolPref("network.trr.clear-cache-on-pref-change", false);
|
||||
|
||||
Services.prefs.setBoolPref("network.dns.upgrade_with_https_rr", true);
|
||||
Services.prefs.setBoolPref("network.dns.use_https_rr_as_altsvc", true);
|
||||
Services.prefs.setBoolPref("network.dns.echconfig.enabled", true);
|
||||
|
||||
// The moz-http2 cert is for foo.example.com and is signed by http2-ca.pem
|
||||
// so add that cert to the trust list as a signing cert. // the foo.example.com domain name.
|
||||
const certdb = Cc["@mozilla.org/security/x509certdb;1"].getService(
|
||||
Ci.nsIX509CertDB
|
||||
);
|
||||
addCertFromFile(certdb, "http2-ca.pem", "CTu,u,u");
|
||||
|
||||
add_tls_server_setup(
|
||||
"EncryptedClientHelloServer",
|
||||
"../../../security/manager/ssl/tests/unit/test_encrypted_client_hello"
|
||||
);
|
||||
}
|
||||
|
||||
setup();
|
||||
registerCleanupFunction(() => {
|
||||
prefs.clearUserPref("network.security.esni.enabled");
|
||||
prefs.clearUserPref("network.http.spdy.enabled");
|
||||
prefs.clearUserPref("network.http.spdy.enabled.http2");
|
||||
prefs.clearUserPref("network.dns.localDomains");
|
||||
prefs.clearUserPref("network.dns.native-is-localhost");
|
||||
prefs.clearUserPref("network.trr.mode");
|
||||
prefs.clearUserPref("network.trr.uri");
|
||||
prefs.clearUserPref("network.trr.credentials");
|
||||
prefs.clearUserPref("network.trr.wait-for-portal");
|
||||
prefs.clearUserPref("network.trr.allow-rfc1918");
|
||||
prefs.clearUserPref("network.trr.useGET");
|
||||
prefs.clearUserPref("network.trr.confirmationNS");
|
||||
prefs.clearUserPref("network.trr.bootstrapAddress");
|
||||
prefs.clearUserPref("network.trr.request-timeout");
|
||||
prefs.clearUserPref("network.trr.clear-cache-on-pref-change");
|
||||
prefs.clearUserPref("network.dns.upgrade_with_https_rr");
|
||||
prefs.clearUserPref("network.dns.use_https_rr_as_altsvc");
|
||||
prefs.clearUserPref("network.dns.echconfig.enabled");
|
||||
prefs.clearUserPref("network.dns.echconfig.fallback_to_origin");
|
||||
trrServer.stop();
|
||||
});
|
||||
|
||||
class DNSListener {
|
||||
constructor() {
|
||||
this.promise = new Promise(resolve => {
|
||||
this.resolve = resolve;
|
||||
});
|
||||
}
|
||||
onLookupComplete(inRequest, inRecord, inStatus) {
|
||||
this.resolve([inRequest, inRecord, inStatus]);
|
||||
}
|
||||
// So we can await this as a promise.
|
||||
then() {
|
||||
return this.promise.then.apply(this.promise, arguments);
|
||||
}
|
||||
}
|
||||
|
||||
DNSListener.prototype.QueryInterface = ChromeUtils.generateQI([
|
||||
"nsIDNSListener",
|
||||
]);
|
||||
|
||||
function makeChan(url) {
|
||||
let chan = NetUtil.newChannel({
|
||||
uri: url,
|
||||
loadUsingSystemPrincipal: true,
|
||||
}).QueryInterface(Ci.nsIHttpChannel);
|
||||
return chan;
|
||||
}
|
||||
|
||||
function channelOpenPromise(chan, flags) {
|
||||
return new Promise(resolve => {
|
||||
function finish(req, buffer) {
|
||||
certOverrideService.setDisableAllSecurityChecksAndLetAttackersInterceptMyData(
|
||||
false
|
||||
);
|
||||
resolve([req, buffer]);
|
||||
}
|
||||
certOverrideService.setDisableAllSecurityChecksAndLetAttackersInterceptMyData(
|
||||
true
|
||||
);
|
||||
let internal = chan.QueryInterface(Ci.nsIHttpChannelInternal);
|
||||
internal.setWaitForHTTPSSVCRecord();
|
||||
chan.asyncOpen(new ChannelListener(finish, null, flags));
|
||||
});
|
||||
}
|
||||
|
||||
add_task(async function testConnectWithECH() {
|
||||
const ECH_CONFIG_FIXED =
|
||||
"AEr+CABGABZlY2gtcHVibGljLmV4YW1wbGUuY29tACCKB1Y5SfrGIyk27W82xPpzWTDs3q72c04xSurDWlb9CgAgAAQAAQADADIAAA==";
|
||||
trrServer = new TRRServer();
|
||||
await trrServer.start();
|
||||
|
||||
Services.prefs.setIntPref("network.trr.mode", 3);
|
||||
Services.prefs.setCharPref(
|
||||
"network.trr.uri",
|
||||
`https://foo.example.com:${trrServer.port}/dns-query`
|
||||
);
|
||||
|
||||
// Only the last record is valid to use.
|
||||
await trrServer.registerDoHAnswers("ech-private.example.com", "HTTPS", [
|
||||
{
|
||||
name: "ech-private.example.com",
|
||||
ttl: 55,
|
||||
type: "HTTPS",
|
||||
flush: false,
|
||||
data: {
|
||||
priority: 1,
|
||||
name: "ech-private.example.com",
|
||||
values: [
|
||||
{ key: "alpn", value: "http/1.1" },
|
||||
{ key: "port", value: 8443 },
|
||||
{
|
||||
key: "echconfig",
|
||||
value: ECH_CONFIG_FIXED,
|
||||
needBase64Decode: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
await trrServer.registerDoHAnswers("ech-private.example.com", "A", [
|
||||
{
|
||||
name: "ech-private.example.com",
|
||||
ttl: 55,
|
||||
type: "A",
|
||||
flush: false,
|
||||
data: "127.0.0.1",
|
||||
},
|
||||
]);
|
||||
|
||||
let listener = new DNSListener();
|
||||
|
||||
let request = dns.asyncResolve(
|
||||
"ech-private.example.com",
|
||||
dns.RESOLVE_TYPE_HTTPSSVC,
|
||||
0,
|
||||
null, // resolverInfo
|
||||
listener,
|
||||
mainThread,
|
||||
defaultOriginAttributes
|
||||
);
|
||||
|
||||
let [inRequest, inRecord, inStatus] = await listener;
|
||||
Assert.equal(inRequest, request, "correct request was used");
|
||||
Assert.equal(inStatus, Cr.NS_OK, "status OK");
|
||||
|
||||
let chan = makeChan(`https://ech-private.example.com`);
|
||||
await channelOpenPromise(chan, CL_ALLOW_UNKNOWN_CL);
|
||||
let securityInfo = chan.securityInfo.QueryInterface(
|
||||
Ci.nsITransportSecurityInfo
|
||||
);
|
||||
Assert.ok(securityInfo.isAcceptedEch, "This host should have accepted ECH");
|
||||
|
||||
await trrServer.stop();
|
||||
});
|
||||
|
||||
add_task(async function testEchRetry() {
|
||||
dns.clearCache(true);
|
||||
|
||||
const ECH_CONFIG_TRUSTED_RETRY =
|
||||
"AEr+CABGABZlY2gtcHVibGljLmV4YW1wbGUuY29tACCKB1Y5SfrGIyk27W82xPpzWTDs3q72c04xSurDWlb9CgAgAAQAAQABADIAAA==";
|
||||
trrServer = new TRRServer();
|
||||
await trrServer.start();
|
||||
|
||||
Services.prefs.setIntPref("network.trr.mode", 3);
|
||||
Services.prefs.setCharPref(
|
||||
"network.trr.uri",
|
||||
`https://foo.example.com:${trrServer.port}/dns-query`
|
||||
);
|
||||
|
||||
// Only the last record is valid to use.
|
||||
await trrServer.registerDoHAnswers("ech-private.example.com", "HTTPS", [
|
||||
{
|
||||
name: "ech-private.example.com",
|
||||
ttl: 55,
|
||||
type: "HTTPS",
|
||||
flush: false,
|
||||
data: {
|
||||
priority: 1,
|
||||
name: "ech-private.example.com",
|
||||
values: [
|
||||
{ key: "alpn", value: "http/1.1" },
|
||||
{ key: "port", value: 8443 },
|
||||
{
|
||||
key: "echconfig",
|
||||
value: ECH_CONFIG_TRUSTED_RETRY,
|
||||
needBase64Decode: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
await trrServer.registerDoHAnswers("ech-private.example.com", "A", [
|
||||
{
|
||||
name: "ech-private.example.com",
|
||||
ttl: 55,
|
||||
type: "A",
|
||||
flush: false,
|
||||
data: "127.0.0.1",
|
||||
},
|
||||
]);
|
||||
|
||||
let listener = new DNSListener();
|
||||
|
||||
let request = dns.asyncResolve(
|
||||
"ech-private.example.com",
|
||||
dns.RESOLVE_TYPE_HTTPSSVC,
|
||||
0,
|
||||
null, // resolverInfo
|
||||
listener,
|
||||
mainThread,
|
||||
defaultOriginAttributes
|
||||
);
|
||||
|
||||
let [inRequest, inRecord, inStatus] = await listener;
|
||||
Assert.equal(inRequest, request, "correct request was used");
|
||||
Assert.equal(inStatus, Cr.NS_OK, "status OK");
|
||||
|
||||
Services.prefs.setBoolPref("network.dns.echconfig.enabled", true);
|
||||
let chan = makeChan(`https://ech-private.example.com`);
|
||||
await channelOpenPromise(chan, CL_ALLOW_UNKNOWN_CL);
|
||||
let securityInfo = chan.securityInfo.QueryInterface(
|
||||
Ci.nsITransportSecurityInfo
|
||||
);
|
||||
Assert.ok(securityInfo.isAcceptedEch, "This host should have accepted ECH");
|
||||
|
||||
await trrServer.stop();
|
||||
});
|
|
@ -0,0 +1,221 @@
|
|||
/* 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";
|
||||
|
||||
ChromeUtils.import("resource://gre/modules/NetUtil.jsm");
|
||||
|
||||
let prefs;
|
||||
let h2Port;
|
||||
let listen;
|
||||
let trrServer;
|
||||
|
||||
const dns = Cc["@mozilla.org/network/dns-service;1"].getService(
|
||||
Ci.nsIDNSService
|
||||
);
|
||||
const certOverrideService = Cc[
|
||||
"@mozilla.org/security/certoverride;1"
|
||||
].getService(Ci.nsICertOverrideService);
|
||||
const threadManager = Cc["@mozilla.org/thread-manager;1"].getService(
|
||||
Ci.nsIThreadManager
|
||||
);
|
||||
const mainThread = threadManager.currentThread;
|
||||
|
||||
const defaultOriginAttributes = {};
|
||||
|
||||
function setup() {
|
||||
let env = Cc["@mozilla.org/process/environment;1"].getService(
|
||||
Ci.nsIEnvironment
|
||||
);
|
||||
h2Port = env.get("MOZHTTP2_PORT");
|
||||
Assert.notEqual(h2Port, null);
|
||||
Assert.notEqual(h2Port, "");
|
||||
|
||||
// Set to allow the cert presented by our H2 server
|
||||
do_get_profile();
|
||||
prefs = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch);
|
||||
|
||||
prefs.setBoolPref("network.security.esni.enabled", false);
|
||||
prefs.setBoolPref("network.http.spdy.enabled", true);
|
||||
prefs.setBoolPref("network.http.spdy.enabled.http2", true);
|
||||
// the TRR server is on 127.0.0.1
|
||||
prefs.setCharPref("network.trr.bootstrapAddress", "127.0.0.1");
|
||||
|
||||
// make all native resolve calls "secretly" resolve localhost instead
|
||||
prefs.setBoolPref("network.dns.native-is-localhost", true);
|
||||
|
||||
// 0 - off, 1 - race, 2 TRR first, 3 TRR only, 4 shadow
|
||||
prefs.setIntPref("network.trr.mode", 2); // TRR first
|
||||
prefs.setBoolPref("network.trr.wait-for-portal", false);
|
||||
// don't confirm that TRR is working, just go!
|
||||
prefs.setCharPref("network.trr.confirmationNS", "skip");
|
||||
|
||||
// So we can change the pref without clearing the cache to check a pushed
|
||||
// record with a TRR path that fails.
|
||||
Services.prefs.setBoolPref("network.trr.clear-cache-on-pref-change", false);
|
||||
|
||||
Services.prefs.setBoolPref("network.dns.upgrade_with_https_rr", true);
|
||||
Services.prefs.setBoolPref("network.dns.use_https_rr_as_altsvc", true);
|
||||
Services.prefs.setBoolPref("network.dns.echconfig.enabled", true);
|
||||
|
||||
// The moz-http2 cert is for foo.example.com and is signed by http2-ca.pem
|
||||
// so add that cert to the trust list as a signing cert. // the foo.example.com domain name.
|
||||
const certdb = Cc["@mozilla.org/security/x509certdb;1"].getService(
|
||||
Ci.nsIX509CertDB
|
||||
);
|
||||
addCertFromFile(certdb, "http2-ca.pem", "CTu,u,u");
|
||||
|
||||
// An arbitrary, non-ECH server.
|
||||
add_tls_server_setup(
|
||||
"DelegatedCredentialsServer",
|
||||
"../../../security/manager/ssl/tests/unit/test_delegated_credentials"
|
||||
);
|
||||
|
||||
let nssComponent = Cc["@mozilla.org/psm;1"].getService(Ci.nsINSSComponent);
|
||||
nssComponent.clearSSLExternalAndInternalSessionCache();
|
||||
}
|
||||
|
||||
setup();
|
||||
registerCleanupFunction(() => {
|
||||
prefs.clearUserPref("network.security.esni.enabled");
|
||||
prefs.clearUserPref("network.http.spdy.enabled");
|
||||
prefs.clearUserPref("network.http.spdy.enabled.http2");
|
||||
prefs.clearUserPref("network.dns.localDomains");
|
||||
prefs.clearUserPref("network.dns.native-is-localhost");
|
||||
prefs.clearUserPref("network.trr.mode");
|
||||
prefs.clearUserPref("network.trr.uri");
|
||||
prefs.clearUserPref("network.trr.credentials");
|
||||
prefs.clearUserPref("network.trr.wait-for-portal");
|
||||
prefs.clearUserPref("network.trr.allow-rfc1918");
|
||||
prefs.clearUserPref("network.trr.useGET");
|
||||
prefs.clearUserPref("network.trr.confirmationNS");
|
||||
prefs.clearUserPref("network.trr.bootstrapAddress");
|
||||
prefs.clearUserPref("network.trr.request-timeout");
|
||||
prefs.clearUserPref("network.trr.clear-cache-on-pref-change");
|
||||
prefs.clearUserPref("network.dns.upgrade_with_https_rr");
|
||||
prefs.clearUserPref("network.dns.use_https_rr_as_altsvc");
|
||||
prefs.clearUserPref("network.dns.echconfig.enabled");
|
||||
prefs.clearUserPref("network.dns.echconfig.fallback_to_origin");
|
||||
trrServer.stop();
|
||||
});
|
||||
|
||||
class DNSListener {
|
||||
constructor() {
|
||||
this.promise = new Promise(resolve => {
|
||||
this.resolve = resolve;
|
||||
});
|
||||
}
|
||||
onLookupComplete(inRequest, inRecord, inStatus) {
|
||||
this.resolve([inRequest, inRecord, inStatus]);
|
||||
}
|
||||
// So we can await this as a promise.
|
||||
then() {
|
||||
return this.promise.then.apply(this.promise, arguments);
|
||||
}
|
||||
}
|
||||
|
||||
DNSListener.prototype.QueryInterface = ChromeUtils.generateQI([
|
||||
"nsIDNSListener",
|
||||
]);
|
||||
|
||||
function makeChan(url) {
|
||||
let chan = NetUtil.newChannel({
|
||||
uri: url,
|
||||
loadUsingSystemPrincipal: true,
|
||||
}).QueryInterface(Ci.nsIHttpChannel);
|
||||
return chan;
|
||||
}
|
||||
|
||||
function channelOpenPromise(chan, flags) {
|
||||
return new Promise(resolve => {
|
||||
function finish(req, buffer) {
|
||||
certOverrideService.setDisableAllSecurityChecksAndLetAttackersInterceptMyData(
|
||||
false
|
||||
);
|
||||
resolve([req, buffer]);
|
||||
}
|
||||
certOverrideService.setDisableAllSecurityChecksAndLetAttackersInterceptMyData(
|
||||
false
|
||||
);
|
||||
let internal = chan.QueryInterface(Ci.nsIHttpChannelInternal);
|
||||
internal.setWaitForHTTPSSVCRecord();
|
||||
chan.asyncOpen(new ChannelListener(finish, null, flags));
|
||||
});
|
||||
}
|
||||
|
||||
add_task(async function testRetryWithoutECH() {
|
||||
const ECH_CONFIG_FIXED =
|
||||
"AFH+CABNAB1kZWxlZ2F0ZWQtZW5hYmxlZC5leGFtcGxlLmNvbQAgigdWOUn6xiMpNu1vNsT6c1kw7N6u9nNOMUrqw1pW/QoAIAAEAAEAAwAyAAA=";
|
||||
trrServer = new TRRServer();
|
||||
await trrServer.start();
|
||||
|
||||
Services.prefs.setIntPref("network.trr.mode", 3);
|
||||
Services.prefs.setCharPref(
|
||||
"network.trr.uri",
|
||||
`https://foo.example.com:${trrServer.port}/dns-query`
|
||||
);
|
||||
|
||||
// Only the last record is valid to use.
|
||||
await trrServer.registerDoHAnswers(
|
||||
"delegated-disabled.example.com",
|
||||
"HTTPS",
|
||||
[
|
||||
{
|
||||
name: "delegated-disabled.example.com",
|
||||
ttl: 55,
|
||||
type: "HTTPS",
|
||||
flush: false,
|
||||
data: {
|
||||
priority: 1,
|
||||
name: "delegated-disabled.example.com",
|
||||
values: [
|
||||
{
|
||||
key: "echconfig",
|
||||
value: ECH_CONFIG_FIXED,
|
||||
needBase64Decode: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
]
|
||||
);
|
||||
|
||||
await trrServer.registerDoHAnswers("delegated-disabled.example.com", "A", [
|
||||
{
|
||||
name: "delegated-disabled.example.com",
|
||||
ttl: 55,
|
||||
type: "A",
|
||||
flush: false,
|
||||
data: "127.0.0.1",
|
||||
},
|
||||
]);
|
||||
|
||||
let listener = new DNSListener();
|
||||
|
||||
let request = dns.asyncResolve(
|
||||
"delegated-disabled.example.com",
|
||||
dns.RESOLVE_TYPE_HTTPSSVC,
|
||||
0,
|
||||
null, // resolverInfo
|
||||
listener,
|
||||
mainThread,
|
||||
defaultOriginAttributes
|
||||
);
|
||||
|
||||
let [inRequest, inRecord, inStatus] = await listener;
|
||||
Assert.equal(inRequest, request, "correct request was used");
|
||||
Assert.equal(inStatus, Cr.NS_OK, "status OK");
|
||||
|
||||
let chan = makeChan(`https://delegated-disabled.example.com:8443`);
|
||||
await channelOpenPromise(chan, CL_ALLOW_UNKNOWN_CL);
|
||||
let securityInfo = chan.securityInfo.QueryInterface(
|
||||
Ci.nsITransportSecurityInfo
|
||||
);
|
||||
|
||||
Assert.ok(
|
||||
!securityInfo.isAcceptedEch,
|
||||
"This host should not have accepted ECH"
|
||||
);
|
||||
await trrServer.stop();
|
||||
});
|
|
@ -495,4 +495,7 @@ run-sequentially = node server exceptions dont replay well
|
|||
skip-if = asan || tsan || os == 'win' || os =='android' || socketprocess_networking
|
||||
run-sequentially = node server exceptions dont replay well
|
||||
[test_cookie_ipv6.js]
|
||||
|
||||
[test_httpssvc_retry_with_ech.js]
|
||||
skip-if = asan || tsan || os == 'win' || os =='android'
|
||||
[test_httpssvc_retry_without_ech.js]
|
||||
skip-if = asan || tsan || os == 'win' || os =='android'
|
||||
|
|
|
@ -1392,13 +1392,23 @@ svcparam.encode = function(param, buf, offset) {
|
|||
svcparam.encode.bytes += 4;
|
||||
}
|
||||
} else if (key == 5) { //echconfig
|
||||
// TODO: base64 presentation format
|
||||
buf.writeUInt16BE(param.value.length, offset);
|
||||
offset += 2;
|
||||
svcparam.encode.bytes += 2;
|
||||
buf.write(param.value, offset);
|
||||
offset += param.value.length;
|
||||
svcparam.encode.bytes += param.value.length;
|
||||
if (svcparam.ech) {
|
||||
buf.writeUInt16BE(svcparam.ech.length, offset);
|
||||
offset += 2;
|
||||
svcparam.encode.bytes += 2;
|
||||
for (let i = 0; i < svcparam.ech.length; i++) {
|
||||
buf.writeUInt8(svcparam.ech[i], offset);
|
||||
offset++;
|
||||
}
|
||||
svcparam.encode.bytes += svcparam.ech.length;
|
||||
} else {
|
||||
buf.writeUInt16BE(param.value.length, offset);
|
||||
offset += 2;
|
||||
svcparam.encode.bytes += 2;
|
||||
buf.write(param.value, offset);
|
||||
offset += param.value.length;
|
||||
svcparam.encode.bytes += param.value.length;
|
||||
}
|
||||
} else if (key == 6) { //ipv6hint
|
||||
let val = param.value;
|
||||
if (!Array.isArray(val)) val = [val];
|
||||
|
@ -1458,7 +1468,13 @@ svcparam.encodingLength = function (param) {
|
|||
case 'no-default-alpn' : return 4
|
||||
case 'port' : return 4 + 2
|
||||
case 'ipv4hint' : return 4 + 4 * (Array.isArray(param.value) ? param.value.length : 1)
|
||||
case 'echconfig' : return 4 + param.value.length
|
||||
case 'echconfig' : {
|
||||
if (param.needBase64Decode) {
|
||||
svcparam.ech = Buffer.from(param.value, "base64");
|
||||
return 4 + svcparam.ech.length;
|
||||
}
|
||||
return 4 + param.value.length
|
||||
}
|
||||
case 'ipv6hint' : return 4 + 16 * (Array.isArray(param.value) ? param.value.length : 1)
|
||||
case 'key65535' : return 4
|
||||
default: return 4 // unknown option
|
||||
|
|
Загрузка…
Ссылка в новой задаче