Bug 1825538 - Retry TRR request on on main thread when proxy connection failed, r=necko-reviewers,valentin

Differential Revision: https://phabricator.services.mozilla.com/D176621
This commit is contained in:
Kershaw Chang 2023-05-02 13:00:27 +00:00
Родитель e6702068c2
Коммит 0ad6584639
9 изменённых файлов: 183 добавлений и 10 удалений

Просмотреть файл

@ -9,6 +9,7 @@
#include "nsCharSeparatedTokenizer.h"
#include "nsContentUtils.h"
#include "nsHttpHandler.h"
#include "nsHttpChannel.h"
#include "nsHostResolver.h"
#include "nsIHttpChannel.h"
#include "nsIHttpChannelInternal.h"
@ -1072,7 +1073,9 @@ void TRR::Cancel(nsresult aStatus) {
isTRRServiceChannel = false;
}
}
if (isTRRServiceChannel && !XRE_IsSocketProcess()) {
// nsHttpChannel can be only canceled on the main thread.
RefPtr<nsHttpChannel> httpChannel = do_QueryObject(mChannel);
if (isTRRServiceChannel && !XRE_IsSocketProcess() && !httpChannel) {
if (TRRService::Get()) {
nsCOMPtr<nsIThread> thread = TRRService::Get()->TRRThread();
if (thread && !thread->IsOnCurrentThread()) {

Просмотреть файл

@ -545,7 +545,7 @@ nsresult TRRService::DispatchTRRRequestInternal(TRR* aTrrRequest,
already_AddRefed<nsIThread> TRRService::MainThreadOrTRRThread(bool aWithLock) {
if (!StaticPrefs::network_trr_fetch_off_main_thread() ||
XRE_IsSocketProcess()) {
XRE_IsSocketProcess() || mDontUseTRRThread) {
return do_GetMainThread();
}

Просмотреть файл

@ -102,6 +102,8 @@ class TRRService : public TRRServiceBase,
void InitTRRConnectionInfo() override;
void DontUseTRRThread() { mDontUseTRRThread = true; }
private:
virtual ~TRRService();
@ -149,6 +151,7 @@ class TRRService : public TRRServiceBase,
false}; // set when captive portal check is passed
Atomic<bool, Relaxed> mDisableIPv6; // don't even try
Atomic<bool, Relaxed> mShutdown{false};
Atomic<bool, Relaxed> mDontUseTRRThread{false};
// TRR Blocklist storage
// mTRRBLStorage is only modified on the main thread, but we query whether it

Просмотреть файл

@ -1367,7 +1367,17 @@ void nsHostResolver::AddToEvictionQ(nsHostRecord* rec,
// Returns true if we retried with either TRR or Native.
bool nsHostResolver::MaybeRetryTRRLookup(
AddrHostRecord* aAddrRec, nsresult aFirstAttemptStatus,
TRRSkippedReason aFirstAttemptSkipReason, const MutexAutoLock& aLock) {
TRRSkippedReason aFirstAttemptSkipReason, nsresult aChannelStatus,
const MutexAutoLock& aLock) {
if (NS_FAILED(aFirstAttemptStatus) &&
(aChannelStatus == NS_ERROR_PROXY_UNAUTHORIZED ||
aChannelStatus == NS_ERROR_PROXY_AUTHENTICATION_FAILED) &&
aAddrRec->mEffectiveTRRMode == nsIRequest::TRR_ONLY_MODE) {
LOG(("MaybeRetryTRRLookup retry because of proxy connect failed"));
TRRService::Get()->DontUseTRRThread();
return DoRetryTRR(aAddrRec, aLock);
}
if (NS_SUCCEEDED(aFirstAttemptStatus) ||
aAddrRec->mEffectiveTRRMode != nsIRequest::TRR_FIRST_MODE ||
aFirstAttemptStatus == NS_ERROR_DEFINITIVE_UNKNOWN_HOST) {
@ -1415,6 +1425,11 @@ bool nsHostResolver::MaybeRetryTRRLookup(
static_cast<uint32_t>(aFirstAttemptSkipReason)));
TRRService::Get()->RetryTRRConfirm();
return DoRetryTRR(aAddrRec, aLock);
}
bool nsHostResolver::DoRetryTRR(AddrHostRecord* aAddrRec,
const mozilla::MutexAutoLock& aLock) {
{
// Clear out the old query
auto trrQuery = aAddrRec->mTRRQuery.Lock();
@ -1498,7 +1513,8 @@ nsHostResolver::LookupStatus nsHostResolver::CompleteLookupLocked(
addrRec->RecordReason(TRRSkippedReason::TRR_OK);
}
if (MaybeRetryTRRLookup(addrRec, status, aReason, aLock)) {
nsresult channelStatus = aTRRRequest->ChannelStatus();
if (MaybeRetryTRRLookup(addrRec, status, aReason, channelStatus, aLock)) {
MOZ_ASSERT(addrRec->mResolving);
return LOOKUP_OK;
}

Просмотреть файл

@ -227,10 +227,12 @@ class nsHostResolver : public nsISupports, public AHostResolver {
uint32_t defaultGracePeriod);
virtual ~nsHostResolver();
bool DoRetryTRR(AddrHostRecord* aAddrRec,
const mozilla::MutexAutoLock& aLock);
bool MaybeRetryTRRLookup(
AddrHostRecord* aAddrRec, nsresult aFirstAttemptStatus,
mozilla::net::TRRSkippedReason aFirstAttemptSkipReason,
const mozilla::MutexAutoLock& aLock);
nsresult aChannelStatus, const mozilla::MutexAutoLock& aLock);
LookupStatus CompleteLookupLocked(nsHostRecord*, nsresult,
mozilla::net::AddrInfo*, bool pb,

Просмотреть файл

@ -1028,6 +1028,17 @@ TRRServiceChannel::OnStartRequest(nsIRequest* request) {
mResponseHead = mTransaction->TakeResponseHead();
if (mResponseHead) {
uint32_t httpStatus = mResponseHead->Status();
if (mTransaction->ProxyConnectFailed()) {
LOG(("TRRServiceChannel proxy connect failed httpStatus: %d",
httpStatus));
MOZ_ASSERT(mConnectionInfo->UsingConnect(),
"proxy connect failed but not using CONNECT?");
nsresult rv = HttpProxyResponseToErrorCode(httpStatus);
mTransaction->DontReuseConnection();
Cancel(rv);
return CallOnStartRequest();
}
if ((httpStatus < 500) && (httpStatus != 421) && (httpStatus != 407)) {
ProcessAltService();
}

Просмотреть файл

@ -448,7 +448,7 @@ class NodeHTTPSProxyServer extends BaseHTTPProxy {
// HTTP2 proxy
class HTTP2ProxyCode {
static async startServer(port) {
static async startServer(port, auth) {
const fs = require("fs");
const options = {
key: fs.readFileSync(__dirname + "/proxy-cert.key"),
@ -457,7 +457,7 @@ class HTTP2ProxyCode {
const http2 = require("http2");
global.proxy = http2.createSecureServer(options);
global.socketCounts = {};
this.setupProxy();
this.setupProxy(auth);
await global.proxy.listen(port);
let proxyPort = global.proxy.address().port;
@ -465,7 +465,7 @@ class HTTP2ProxyCode {
return proxyPort;
}
static setupProxy() {
static setupProxy(auth) {
if (!global.proxy) {
throw new Error("proxy is null");
}
@ -539,6 +539,16 @@ class HTTP2ProxyCode {
return;
}
const authorization_token = headers["proxy-authorization"];
if (auth && !authorization_token) {
stream.respond({
":status": 407,
"proxy-authenticate": "Basic realm='foo'",
});
stream.end();
return;
}
const target = headers[":authority"];
const { port } = new URL(`https://${target}`);
const net = require("net");
@ -596,14 +606,16 @@ class NodeHTTP2ProxyServer extends BaseHTTPProxy {
/// Starts the server
/// @port - default 0
/// when provided, will attempt to listen on that port.
async start(port = 0) {
async start(port = 0, auth) {
this.processId = await NodeServer.fork();
await this.execute(BaseProxyCode);
await this.execute(HTTP2ProxyCode);
await this.execute(ADB);
await this.execute(`global.connect_handler = null;`);
this._port = await this.execute(`HTTP2ProxyCode.startServer(${port})`);
this._port = await this.execute(
`HTTP2ProxyCode.startServer(${port}, ${auth})`
);
this.registerFilter();
}

Просмотреть файл

@ -0,0 +1,122 @@
/* 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";
/* import-globals-from head_cache.js */
/* import-globals-from head_cookies.js */
/* import-globals-from head_channels.js */
/* import-globals-from head_servers.js */
function setup() {
trr_test_setup();
Services.prefs.setBoolPref("network.trr.fetch_off_main_thread", true);
}
setup();
registerCleanupFunction(async () => {
trr_clear_prefs();
});
function AuthPrompt() {}
AuthPrompt.prototype = {
user: "guest",
pass: "guest",
QueryInterface: ChromeUtils.generateQI(["nsIAuthPrompt2"]),
promptAuth: function ap_promptAuth(channel, level, authInfo) {
authInfo.username = this.user;
authInfo.password = this.pass;
return true;
},
asyncPromptAuth: function ap_async(chan, cb, ctx, lvl, info) {
throw Components.Exception("", Cr.NS_ERROR_NOT_IMPLEMENTED);
},
};
function Requestor() {}
Requestor.prototype = {
QueryInterface: ChromeUtils.generateQI(["nsIInterfaceRequestor"]),
getInterface: function requestor_gi(iid) {
if (iid.equals(Ci.nsIAuthPrompt2)) {
// Allow the prompt to store state by caching it here
if (!this.prompt) {
this.prompt = new AuthPrompt();
}
return this.prompt;
}
throw Components.Exception("", Cr.NS_ERROR_NO_INTERFACE);
},
prompt: null,
};
// Test if we successfully retry TRR request on main thread.
add_task(async function test_trr_proxy_auth() {
let certdb = Cc["@mozilla.org/security/x509certdb;1"].getService(
Ci.nsIX509CertDB
);
addCertFromFile(certdb, "http2-ca.pem", "CTu,u,u");
addCertFromFile(certdb, "proxy-ca.pem", "CTu,u,u");
let 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`
);
await trrServer.registerDoHAnswers("test.proxy.com", "A", {
answers: [
{
name: "test.proxy.com",
ttl: 55,
type: "A",
flush: false,
data: "3.3.3.3",
},
],
});
await new TRRDNSListener("test.proxy.com", "3.3.3.3");
let proxy = new NodeHTTP2ProxyServer();
await proxy.start(0, true);
registerCleanupFunction(async () => {
await proxy.stop();
await trrServer.stop();
});
let authTriggered = false;
let observer = {
QueryInterface: ChromeUtils.generateQI(["nsIObserver"]),
observe(aSubject, aTopic, aData) {
if (aTopic == "http-on-examine-response") {
Services.obs.removeObserver(observer, "http-on-examine-response");
let channel = aSubject.QueryInterface(Ci.nsIChannel);
channel.notificationCallbacks = new Requestor();
if (
channel.URI.spec.startsWith(
`https://foo.example.com:${trrServer.port}/dns-query`
)
) {
authTriggered = true;
}
}
},
};
Services.obs.addObserver(observer, "http-on-examine-response");
Services.dns.clearCache(true);
await new TRRDNSListener("test.proxy.com", "3.3.3.3");
Assert.ok(authTriggered);
});

Просмотреть файл

@ -772,3 +772,7 @@ head = head_channels.js head_cache.js head_cookies.js head_trr.js head_http3.js
skip-if =
os == 'android'
socketprocess_networking
[test_trr_proxy_auth.js]
skip-if =
os == 'android'
socketprocess_networking