commit 757bcfcd621253ae7767ec6eaf235cb38da7004d
parent c7325cad6fa282a437cac15a8c4f1f1e7e1ab8fc
Author: Cristian Tuns <ctuns@mozilla.com>
Date: Thu, 2 Oct 2025 07:41:28 -0400
Revert "Bug 1991426 - Run tests with speculative connections enabled and disabled, r=necko-reviewers,valentin" for causing lint failures
This reverts commit 66a7876f2bf075e585cef82987762d5fe53dfc8e.
Revert "Bug 1991426 - Adjust debug assertions for outer-connection fallback, r=necko-reviewers,valentin"
This reverts commit 2c7d76a615728d88038353b7df492a9ad5ffe2d1.
Revert "Bug 1991426 - Ensure we fallback the outer connection when mBeforeConnectedError is true, r=necko-reviewers,valentin"
This reverts commit 6e47dbc7c4fc6cd0fa7ac9c200212c56b781c370.
Diffstat:
6 files changed, 475 insertions(+), 556 deletions(-)
diff --git a/netwerk/protocol/http/HttpConnectionUDP.cpp b/netwerk/protocol/http/HttpConnectionUDP.cpp
@@ -900,12 +900,7 @@ void HttpConnectionUDP::CloseTransaction(nsAHttpTransaction* trans,
"]\n",
this, trans, static_cast<uint32_t>(reason)));
- // CloseTransaction may be called by nsHttpTransaction when a fallback
- // is needed. In this case, the transaction is still in mQueuedTransaction
- // and the proxy connect is still in progress.
- bool transInQueue = mQueuedTransaction.Contains(trans);
- MOZ_ASSERT(trans == mHttp3Session ||
- (transInQueue && IsProxyConnectInProgress()));
+ MOZ_ASSERT(trans == mHttp3Session);
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
if (NS_SUCCEEDED(reason) || (reason == NS_BASE_STREAM_CLOSED)) {
@@ -924,7 +919,7 @@ void HttpConnectionUDP::CloseTransaction(nsAHttpTransaction* trans,
if (mHttp3Session) {
// When proxy connnect failed, we call Http3Session::SetCleanShutdown to
// force Http3Session to release this UDP connection.
- mHttp3Session->SetCleanShutdown(aIsShutdown || transInQueue ||
+ mHttp3Session->SetCleanShutdown(aIsShutdown ||
(mIsInTunnel && !mProxyConnectSucceeded));
mHttp3Session->Close(reason);
if (!mHttp3Session->IsClosed()) {
diff --git a/netwerk/protocol/http/nsHttpTransaction.cpp b/netwerk/protocol/http/nsHttpTransaction.cpp
@@ -1881,18 +1881,13 @@ nsresult nsHttpTransaction::Restart() {
// to the next
mReuseOnRestart = false;
- if (!mDoNotRemoveAltSvc && !mDontRetryWithDirectRoute) {
- if (mConnInfo->IsHttp3ProxyConnection()) {
- RefPtr<nsHttpConnectionInfo> ci =
- mConnInfo->CreateConnectUDPFallbackConnInfo();
- mConnInfo = ci;
- RemoveAlternateServiceUsedHeader(mRequestHead);
- } else if (!mConnInfo->GetRoutedHost().IsEmpty() || mConnInfo->IsHttp3()) {
- RefPtr<nsHttpConnectionInfo> ci;
- mConnInfo->CloneAsDirectRoute(getter_AddRefs(ci));
- mConnInfo = ci;
- RemoveAlternateServiceUsedHeader(mRequestHead);
- }
+ if (!mDoNotRemoveAltSvc &&
+ (!mConnInfo->GetRoutedHost().IsEmpty() || mConnInfo->IsHttp3()) &&
+ !mDontRetryWithDirectRoute) {
+ RefPtr<nsHttpConnectionInfo> ci;
+ mConnInfo->CloneAsDirectRoute(getter_AddRefs(ci));
+ mConnInfo = ci;
+ RemoveAlternateServiceUsedHeader(mRequestHead);
}
// Reset mDoNotRemoveAltSvc for the next try.
@@ -3539,8 +3534,7 @@ void nsHttpTransaction::OnHttp3TunnelFallbackTimer() {
}
DisableHttp3(false);
- // Setting this flag since DisableHttp3 is already called.
- mDontRetryWithDirectRoute = true;
+
if (mConnection) {
mConnection->CloseTransaction(this, NS_ERROR_NET_RESET);
}
@@ -3589,6 +3583,7 @@ void nsHttpTransaction::OnFastFallbackTimer() {
void nsHttpTransaction::HandleFallback(
nsHttpConnectionInfo* aFallbackConnInfo) {
if (mConnection) {
+ MOZ_ASSERT(mActivated);
// Close the transaction with NS_ERROR_NET_RESET, since we know doing this
// will make transaction to be restarted.
mConnection->CloseTransaction(this, NS_ERROR_NET_RESET);
diff --git a/netwerk/test/unit/http3_proxy_common.js b/netwerk/test/unit/http3_proxy_common.js
@@ -1,485 +0,0 @@
-/* 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_channel.js */
-/* import-globals-from head_http3.js */
-
-const {
- Http3ProxyFilter,
- with_node_servers,
- NodeHTTPServer,
- NodeHTTPSServer,
- NodeHTTP2Server,
- NodeHTTP2ProxyServer,
-} = ChromeUtils.importESModule("resource://testing-common/NodeServer.sys.mjs");
-
-function makeChan(uri) {
- let chan = NetUtil.newChannel({
- uri,
- loadUsingSystemPrincipal: true,
- }).QueryInterface(Ci.nsIHttpChannel);
- chan.loadFlags = Ci.nsIChannel.LOAD_INITIAL_DOCUMENT_URI;
- return chan;
-}
-
-function channelOpenPromise(chan, flags) {
- return new Promise(resolve => {
- function finish(req, buffer) {
- resolve([req, buffer]);
- }
- chan.asyncOpen(new ChannelListener(finish, null, flags));
- });
-}
-
-let pps = Cc["@mozilla.org/network/protocol-proxy-service;1"].getService();
-let proxyHost;
-let proxyPort;
-let noResponsePort;
-let proxyAuth;
-let proxyFilter;
-
-/**
- * Sets up proxy filter to MASQUE H3 proxy
- */
-async function setup_http3_proxy() {
- Services.prefs.setBoolPref("network.proxy.allow_hijacking_localhost", true);
- Services.prefs.setBoolPref("network.dns.disableIPv6", true);
- Services.prefs.setIntPref("network.webtransport.datagram_size", 1500);
- Services.prefs.setCharPref("network.dns.localDomains", "foo.example.com");
- Services.prefs.setIntPref("network.http.http3.max_gso_segments", 1); // TODO: fix underflow
- 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");
-
- proxyHost = "foo.example.com";
- ({ masqueProxyPort: proxyPort, noResponsePort } =
- await create_masque_proxy_server());
- proxyAuth = "";
-
- Assert.notEqual(proxyPort, null);
- Assert.notEqual(proxyPort, "");
-
- // A dummy request to make sure AltSvcCache::mStorage is ready.
- let chan = makeChan(`https://localhost`);
- await channelOpenPromise(chan, CL_EXPECT_FAILURE);
-
- proxyFilter = new Http3ProxyFilter(
- proxyHost,
- proxyPort,
- 0,
- "/.well-known/masque/udp/{target_host}/{target_port}/",
- proxyAuth
- );
- pps.registerFilter(proxyFilter, 10);
-
- registerCleanupFunction(() => {
- Services.prefs.clearUserPref("network.proxy.allow_hijacking_localhost");
- });
-}
-
-/**
- * Tests HTTP connect through H3 proxy to HTTP, HTTPS and H2 servers
- * Makes multiple requests. Expects success.
- */
-async function test_http_connect() {
- info("Running test_http_connect");
- await with_node_servers(
- [NodeHTTPServer, NodeHTTPSServer, NodeHTTP2Server],
- async server => {
- info(`Proxying to ${server.constructor.name} server`);
- await server.registerPathHandler("/first", (req, resp) => {
- resp.writeHead(200);
- resp.end("first");
- });
- await server.registerPathHandler("/second", (req, resp) => {
- resp.writeHead(200);
- resp.end("second");
- });
- await server.registerPathHandler("/third", (req, resp) => {
- resp.writeHead(200);
- resp.end("third");
- });
- let chan = makeChan(
- `${server.protocol()}://alt1.example.com:${server.port()}/first`
- );
- let [req, buf] = await channelOpenPromise(
- chan,
- CL_IGNORE_CL | CL_ALLOW_UNKNOWN_CL
- );
- Assert.equal(req.status, Cr.NS_OK);
- Assert.equal(buf, "first");
- chan = makeChan(
- `${server.protocol()}://alt1.example.com:${server.port()}/second`
- );
- [req, buf] = await channelOpenPromise(
- chan,
- CL_IGNORE_CL | CL_ALLOW_UNKNOWN_CL
- );
- Assert.equal(req.status, Cr.NS_OK);
- Assert.equal(buf, "second");
-
- chan = makeChan(
- `${server.protocol()}://alt1.example.com:${server.port()}/third`
- );
- [req, buf] = await channelOpenPromise(
- chan,
- CL_IGNORE_CL | CL_ALLOW_UNKNOWN_CL
- );
- Assert.equal(req.status, Cr.NS_OK);
- Assert.equal(buf, "third");
- }
- );
-}
-
-/**
- * Test HTTP CONNECT authentication failure - tests behavior when proxy
- * authentication is required but not provided or incorrect
- */
-async function test_http_connect_auth_failure() {
- info("Running test_http_connect_auth_failure");
- await with_node_servers(
- [NodeHTTPServer, NodeHTTPSServer, NodeHTTP2Server],
- async server => {
- info(`Testing auth failure with ${server.constructor.name} server`);
- // Register a handler that requires authentication
- await server.registerPathHandler("/auth-required", (req, resp) => {
- const auth = req.headers.authorization;
- if (!auth || auth !== "Basic dGVzdDp0ZXN0") {
- resp.writeHead(401, {
- "WWW-Authenticate": 'Basic realm="Test Realm"',
- "Content-Type": "text/plain",
- });
- resp.end("");
- } else {
- resp.writeHead(200);
- resp.end("Authenticated");
- }
- });
-
- let chan = makeChan(
- `${server.protocol()}://alt1.example.com:${server.port()}/auth-required`
- );
- let [req] = await channelOpenPromise(
- chan,
- CL_IGNORE_CL | CL_ALLOW_UNKNOWN_CL
- );
-
- // Should receive 401 Unauthorized through the tunnel
- Assert.equal(req.status, Cr.NS_OK);
- Assert.equal(req.QueryInterface(Ci.nsIHttpChannel).responseStatus, 401);
- }
- );
-}
-
-/**
- * Test HTTP CONNECT with large request/response data - ensures the tunnel
- * can handle substantial data transfer without corruption or truncation
- */
-async function test_http_connect_large_data() {
- info("Running test_http_connect_large_data");
- await with_node_servers(
- [NodeHTTPServer, NodeHTTPSServer, NodeHTTP2Server],
- async server => {
- info(
- `Testing large data transfer with ${server.constructor.name} server`
- );
- // Create a large response payload (1MB of data)
- const largeData = "x".repeat(1024 * 1024);
- await server.registerPathHandler("/large", (req, resp) => {
- const largeData = "x".repeat(1024 * 1024);
- resp.writeHead(200, { "Content-Type": "text/plain" });
- resp.end(largeData);
- });
-
- let chan = makeChan(
- `${server.protocol()}://alt1.example.com:${server.port()}/large`
- );
- let [req, buf] = await channelOpenPromise(
- chan,
- CL_IGNORE_CL | CL_ALLOW_UNKNOWN_CL
- );
-
- Assert.equal(req.status, Cr.NS_OK);
- Assert.equal(buf.length, largeData.length);
- Assert.equal(buf, largeData);
- }
- );
-}
-
-/**
- * Test HTTP CONNECT tunnel connection refused - simulates target server
- * being unreachable or refusing connections
- */
-async function test_http_connect_connection_refused() {
- info("Running test_http_connect_connection_refused");
- // Test connecting to a port that's definitely not in use
- let chan = makeChan(`http://alt1.example.com:667/refused`);
- let [req] = await channelOpenPromise(chan, CL_EXPECT_FAILURE);
-
- // Should fail to establish tunnel connection
- Assert.notEqual(req.status, Cr.NS_OK);
- info(`Connection refused status: ${req.status}`);
-}
-
-/**
- * Test HTTP CONNECT with invalid target host - verifies proper error handling
- * when trying to tunnel to a non-existent hostname
- */
-async function test_http_connect_invalid_host() {
- info("Running test_http_connect_invalid_host");
- let chan = makeChan(`http://nonexistent.invalid.example/test`);
- let [req] = await channelOpenPromise(chan, CL_EXPECT_FAILURE);
-
- // Should fail DNS resolution for invalid hostname
- Assert.notEqual(req.status, Cr.NS_OK);
- info(`Invalid host status: ${req.status}`);
-}
-
-/**
- * Test concurrent HTTP CONNECT tunnels - ensures multiple simultaneous
- * requests can be established and used independently through the same H3 proxy
- */
-async function test_concurrent_http_connect_tunnels() {
- info("Running test_concurrent_http_connect_tunnels");
- await with_node_servers(
- [NodeHTTPServer, NodeHTTPSServer, NodeHTTP2Server],
- async server => {
- info(`Testing concurrent tunnels with ${server.constructor.name} server`);
-
- // Register multiple endpoints
- await server.registerPathHandler("/concurrent1", (req, resp) => {
- resp.writeHead(200);
- resp.end("response1");
- });
- await server.registerPathHandler("/concurrent2", (req, resp) => {
- resp.writeHead(200);
- resp.end("response2");
- });
- await server.registerPathHandler("/concurrent3", (req, resp) => {
- resp.writeHead(200);
- resp.end("response3");
- });
-
- // Create multiple concurrent requests through the tunnel
- const promises = [];
- for (let i = 1; i <= 3; i++) {
- let chan = makeChan(
- `${server.protocol()}://alt1.example.com:${server.port()}/concurrent${i}`
- );
- promises.push(
- channelOpenPromise(chan, CL_IGNORE_CL | CL_ALLOW_UNKNOWN_CL)
- );
- }
-
- const results = await Promise.all(promises);
-
- // Verify all requests succeeded with correct responses
- for (let i = 0; i < 3; i++) {
- const [req, buf] = results[i];
- Assert.equal(req.status, Cr.NS_OK);
- Assert.equal(buf, `response${i + 1}`);
- }
- info("All concurrent tunnels completed successfully");
- }
- );
-}
-
-/**
- * Test HTTP CONNECT tunnel stream closure handling - verifies proper cleanup
- * when the tunnel connection is closed unexpectedly
- */
-// eslint-disable-next-line no-unused-vars
-async function test_http_connect_stream_closure() {
- info("Running test_http_connect_stream_closure");
- await with_node_servers([NodeHTTPServer], async server => {
- info(`Testing stream closure with ${server.constructor.name} server`);
-
- await server.registerPathHandler("/close", (req, resp) => {
- // Send partial response then close connection abruptly
- resp.writeHead(200, { "Content-Type": "text/plain" });
- resp.write("partial");
- // Simulate connection closure
- resp.destroy();
- });
-
- let chan = makeChan(
- `${server.protocol()}://alt1.example.com:${server.port()}/close`
- );
- let [req] = await channelOpenPromise(chan, CL_EXPECT_FAILURE);
-
- // Should handle connection closure gracefully
- Assert.notEqual(req.status, Cr.NS_OK);
- info(`Stream closure status: ${req.status}`);
- });
-}
-
-/**
- * Test connect-udp - SUCCESS case.
- * Will use h3 proxy to connect to h3 server.
- */
-async function test_connect_udp() {
- info("Running test_connect_udp");
- let h3Port = Services.env.get("MOZHTTP3_PORT");
- info(`h3Port = ${h3Port}`);
-
- Services.prefs.setCharPref(
- "network.http.http3.alt-svc-mapping-for-testing",
- `alt1.example.com;h3=:${h3Port}`
- );
-
- {
- let chan = makeChan(`https://alt1.example.com:${h3Port}/no_body`);
- let [req] = await channelOpenPromise(
- chan,
- CL_IGNORE_CL | CL_ALLOW_UNKNOWN_CL
- );
- Assert.equal(req.protocolVersion, "h3");
- Assert.equal(req.status, Cr.NS_OK);
- Assert.equal(req.responseStatus, 200);
- }
-}
-
-async function test_http_connect_fallback() {
- info("Running test_http_connect_fallback");
- pps.unregisterFilter(proxyFilter);
-
- Services.prefs.setCharPref(
- "network.http.http3.alt-svc-mapping-for-testing",
- ""
- );
-
- let proxyPort = noResponsePort;
- let proxy = new NodeHTTP2ProxyServer();
- await proxy.startWithoutProxyFilter(proxyPort);
- Assert.equal(proxyPort, proxy.port());
- dump(`proxy port=${proxy.port()}\n`);
-
- let server = new NodeHTTP2Server();
- await server.start();
-
- // Register multiple endpoints
- await server.registerPathHandler("/concurrent1", (req, resp) => {
- resp.writeHead(200);
- resp.end("response1");
- });
- await server.registerPathHandler("/concurrent2", (req, resp) => {
- resp.writeHead(200);
- resp.end("response2");
- });
- await server.registerPathHandler("/concurrent3", (req, resp) => {
- resp.writeHead(200);
- resp.end("response3");
- });
-
- let filter = new Http3ProxyFilter(
- proxyHost,
- proxy.port(),
- 0,
- "/.well-known/masque/udp/{target_host}/{target_port}/",
- proxyAuth
- );
- pps.registerFilter(filter, 10);
-
- registerCleanupFunction(async () => {
- await proxy.stop();
- await server.stop();
- });
-
- // Create multiple concurrent requests through the tunnel
- const promises = [];
- for (let i = 1; i <= 3; i++) {
- let chan = makeChan(
- `${server.protocol()}://alt1.example.com:${server.port()}/concurrent${i}`
- );
- promises.push(channelOpenPromise(chan, CL_IGNORE_CL | CL_ALLOW_UNKNOWN_CL));
- }
-
- const results = await Promise.all(promises);
-
- // Verify all requests succeeded with correct responses
- for (let i = 0; i < 3; i++) {
- const [req, buf] = results[i];
- Assert.equal(req.status, Cr.NS_OK);
- Assert.equal(buf, `response${i + 1}`);
- }
-
- let h3Port = server.port();
- console.log(`h3Port = ${h3Port}`);
-
- Services.prefs.setCharPref(
- "network.http.http3.alt-svc-mapping-for-testing",
- `alt1.example.com;h3=:${h3Port}`
- );
-
- let chan = makeChan(`https://alt1.example.com:${h3Port}/concurrent1`);
- let [req] = await channelOpenPromise(
- chan,
- CL_IGNORE_CL | CL_ALLOW_UNKNOWN_CL
- );
- Assert.equal(req.status, Cr.NS_OK);
- Assert.equal(req.responseStatus, 200);
-
- await proxy.stop();
- pps.unregisterFilter(filter);
- await server.stop();
-}
-
-async function test_inner_connection_fallback() {
- info("Running test_inner_connection_fallback");
- let h3Port = Services.env.get("MOZHTTP3_PORT_NO_RESPONSE");
- info(`h3Port = ${h3Port}`);
-
- // Register the connect-udp proxy.
- pps.registerFilter(proxyFilter, 10);
-
- let server = new NodeHTTPSServer();
- await server.start(h3Port);
-
- // Register multiple endpoints
- await server.registerPathHandler("/concurrent1", (req, resp) => {
- resp.writeHead(200);
- resp.end("fallback1");
- });
- await server.registerPathHandler("/concurrent2", (req, resp) => {
- resp.writeHead(200);
- resp.end("fallback2");
- });
- await server.registerPathHandler("/concurrent3", (req, resp) => {
- resp.writeHead(200);
- resp.end("fallback3");
- });
- registerCleanupFunction(async () => {
- await server.stop();
- });
-
- Services.prefs.setCharPref(
- "network.http.http3.alt-svc-mapping-for-testing",
- `alt1.example.com;h3=:${h3Port}`
- );
-
- // Create multiple concurrent requests through the tunnel
- const promises = [];
- for (let i = 1; i <= 3; i++) {
- let chan = makeChan(
- `${server.protocol()}://alt1.example.com:${h3Port}/concurrent${i}`
- );
- promises.push(channelOpenPromise(chan, CL_IGNORE_CL | CL_ALLOW_UNKNOWN_CL));
- }
-
- const results = await Promise.all(promises);
-
- // Verify all requests succeeded with correct responses
- for (let i = 0; i < 3; i++) {
- const [req, buf] = results[i];
- Assert.equal(req.status, Cr.NS_OK);
- Assert.equal(buf, `fallback${i + 1}`);
- }
- await server.stop();
-}
diff --git a/netwerk/test/unit/test_http3_proxy.js b/netwerk/test/unit/test_http3_proxy.js
@@ -4,22 +4,471 @@
"use strict";
-/* import-globals-from http3_proxy_common.js */
+/* import-globals-from head_cache.js */
+/* import-globals-from head_cookies.js */
+/* import-globals-from head_channels.js */
-add_setup(async function () {
- Services.prefs.setIntPref("network.http.speculative-parallel-limit", 20);
+const {
+ Http3ProxyFilter,
+ with_node_servers,
+ NodeHTTPServer,
+ NodeHTTPSServer,
+ NodeHTTP2Server,
+ NodeHTTP2ProxyServer,
+} = ChromeUtils.importESModule("resource://testing-common/NodeServer.sys.mjs");
- await setup_http3_proxy();
+function makeChan(uri) {
+ let chan = NetUtil.newChannel({
+ uri,
+ loadUsingSystemPrincipal: true,
+ }).QueryInterface(Ci.nsIHttpChannel);
+ chan.loadFlags = Ci.nsIChannel.LOAD_INITIAL_DOCUMENT_URI;
+ return chan;
+}
+
+function channelOpenPromise(chan, flags) {
+ return new Promise(resolve => {
+ function finish(req, buffer) {
+ resolve([req, buffer]);
+ }
+ chan.asyncOpen(new ChannelListener(finish, null, flags));
+ });
+}
+
+let pps = Cc["@mozilla.org/network/protocol-proxy-service;1"].getService();
+let proxyHost;
+let proxyPort;
+let noResponsePort;
+let proxyAuth;
+let proxyFilter;
+
+/**
+ * Sets up proxy filter to MASQUE H3 proxy
+ */
+add_setup(async function setup() {
+ Services.prefs.setBoolPref("network.proxy.allow_hijacking_localhost", true);
+ Services.prefs.setBoolPref("network.dns.disableIPv6", true);
+ Services.prefs.setIntPref("network.webtransport.datagram_size", 1500);
+ Services.prefs.setCharPref("network.dns.localDomains", "foo.example.com");
+ Services.prefs.setIntPref("network.http.http3.max_gso_segments", 1); // TODO: fix underflow
+ 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");
+
+ proxyHost = "foo.example.com";
+ ({ masqueProxyPort: proxyPort, noResponsePort } =
+ await create_masque_proxy_server());
+ proxyAuth = "";
+
+ Assert.notEqual(proxyPort, null);
+ Assert.notEqual(proxyPort, "");
+
+ // A dummy request to make sure AltSvcCache::mStorage is ready.
+ let chan = makeChan(`https://localhost`);
+ await channelOpenPromise(chan, CL_EXPECT_FAILURE);
+
+ proxyFilter = new Http3ProxyFilter(
+ proxyHost,
+ proxyPort,
+ 0,
+ "/.well-known/masque/udp/{target_host}/{target_port}/",
+ proxyAuth
+ );
+ pps.registerFilter(proxyFilter, 10);
+
+ registerCleanupFunction(() => {
+ Services.prefs.clearUserPref("network.proxy.allow_hijacking_localhost");
+ });
+});
+
+/**
+ * Tests HTTP connect through H3 proxy to HTTP, HTTPS and H2 servers
+ * Makes multiple requests. Expects success.
+ */
+add_task(async function test_http_connect() {
+ await with_node_servers(
+ [NodeHTTPServer, NodeHTTPSServer, NodeHTTP2Server],
+ async server => {
+ info(`Proxying to ${server.constructor.name} server`);
+ await server.registerPathHandler("/first", (req, resp) => {
+ resp.writeHead(200);
+ resp.end("first");
+ });
+ await server.registerPathHandler("/second", (req, resp) => {
+ resp.writeHead(200);
+ resp.end("second");
+ });
+ await server.registerPathHandler("/third", (req, resp) => {
+ resp.writeHead(200);
+ resp.end("third");
+ });
+ let chan = makeChan(
+ `${server.protocol()}://alt1.example.com:${server.port()}/first`
+ );
+ let [req, buf] = await channelOpenPromise(
+ chan,
+ CL_IGNORE_CL | CL_ALLOW_UNKNOWN_CL
+ );
+ Assert.equal(req.status, Cr.NS_OK);
+ Assert.equal(buf, "first");
+ chan = makeChan(
+ `${server.protocol()}://alt1.example.com:${server.port()}/second`
+ );
+ [req, buf] = await channelOpenPromise(
+ chan,
+ CL_IGNORE_CL | CL_ALLOW_UNKNOWN_CL
+ );
+ Assert.equal(req.status, Cr.NS_OK);
+ Assert.equal(buf, "second");
+
+ chan = makeChan(
+ `${server.protocol()}://alt1.example.com:${server.port()}/third`
+ );
+ [req, buf] = await channelOpenPromise(
+ chan,
+ CL_IGNORE_CL | CL_ALLOW_UNKNOWN_CL
+ );
+ Assert.equal(req.status, Cr.NS_OK);
+ Assert.equal(buf, "third");
+ }
+ );
+});
+
+/**
+ * Test HTTP CONNECT authentication failure - tests behavior when proxy
+ * authentication is required but not provided or incorrect
+ */
+add_task(async function test_http_connect_auth_failure() {
+ await with_node_servers(
+ [NodeHTTPServer, NodeHTTPSServer, NodeHTTP2Server],
+ async server => {
+ info(`Testing auth failure with ${server.constructor.name} server`);
+ // Register a handler that requires authentication
+ await server.registerPathHandler("/auth-required", (req, resp) => {
+ const auth = req.headers.authorization;
+ if (!auth || auth !== "Basic dGVzdDp0ZXN0") {
+ resp.writeHead(401, {
+ "WWW-Authenticate": 'Basic realm="Test Realm"',
+ "Content-Type": "text/plain",
+ });
+ resp.end("");
+ } else {
+ resp.writeHead(200);
+ resp.end("Authenticated");
+ }
+ });
+
+ let chan = makeChan(
+ `${server.protocol()}://alt1.example.com:${server.port()}/auth-required`
+ );
+ let [req] = await channelOpenPromise(
+ chan,
+ CL_IGNORE_CL | CL_ALLOW_UNKNOWN_CL
+ );
+
+ // Should receive 401 Unauthorized through the tunnel
+ Assert.equal(req.status, Cr.NS_OK);
+ Assert.equal(req.QueryInterface(Ci.nsIHttpChannel).responseStatus, 401);
+ }
+ );
+});
+
+/**
+ * Test HTTP CONNECT with large request/response data - ensures the tunnel
+ * can handle substantial data transfer without corruption or truncation
+ */
+add_task(async function test_http_connect_large_data() {
+ await with_node_servers(
+ [NodeHTTPServer, NodeHTTPSServer, NodeHTTP2Server],
+ async server => {
+ info(
+ `Testing large data transfer with ${server.constructor.name} server`
+ );
+ // Create a large response payload (1MB of data)
+ const largeData = "x".repeat(1024 * 1024);
+ await server.registerPathHandler("/large", (req, resp) => {
+ const largeData = "x".repeat(1024 * 1024);
+ resp.writeHead(200, { "Content-Type": "text/plain" });
+ resp.end(largeData);
+ });
+
+ let chan = makeChan(
+ `${server.protocol()}://alt1.example.com:${server.port()}/large`
+ );
+ let [req, buf] = await channelOpenPromise(
+ chan,
+ CL_IGNORE_CL | CL_ALLOW_UNKNOWN_CL
+ );
+
+ Assert.equal(req.status, Cr.NS_OK);
+ Assert.equal(buf.length, largeData.length);
+ Assert.equal(buf, largeData);
+ }
+ );
+});
+
+/**
+ * Test HTTP CONNECT tunnel connection refused - simulates target server
+ * being unreachable or refusing connections
+ */
+add_task(async function test_http_connect_connection_refused() {
+ // Test connecting to a port that's definitely not in use
+ let chan = makeChan(`http://alt1.example.com:667/refused`);
+ let [req] = await channelOpenPromise(chan, CL_EXPECT_FAILURE);
+
+ // Should fail to establish tunnel connection
+ Assert.notEqual(req.status, Cr.NS_OK);
+ info(`Connection refused status: ${req.status}`);
+});
+
+/**
+ * Test HTTP CONNECT with invalid target host - verifies proper error handling
+ * when trying to tunnel to a non-existent hostname
+ */
+add_task(async function test_http_connect_invalid_host() {
+ let chan = makeChan(`http://nonexistent.invalid.example/test`);
+ let [req] = await channelOpenPromise(chan, CL_EXPECT_FAILURE);
+
+ // Should fail DNS resolution for invalid hostname
+ Assert.notEqual(req.status, Cr.NS_OK);
+ info(`Invalid host status: ${req.status}`);
+});
+
+/**
+ * Test concurrent HTTP CONNECT tunnels - ensures multiple simultaneous
+ * requests can be established and used independently through the same H3 proxy
+ */
+add_task(async function test_concurrent_http_connect_tunnels() {
+ await with_node_servers(
+ [NodeHTTPServer, NodeHTTPSServer, NodeHTTP2Server],
+ async server => {
+ info(`Testing concurrent tunnels with ${server.constructor.name} server`);
+
+ // Register multiple endpoints
+ await server.registerPathHandler("/concurrent1", (req, resp) => {
+ resp.writeHead(200);
+ resp.end("response1");
+ });
+ await server.registerPathHandler("/concurrent2", (req, resp) => {
+ resp.writeHead(200);
+ resp.end("response2");
+ });
+ await server.registerPathHandler("/concurrent3", (req, resp) => {
+ resp.writeHead(200);
+ resp.end("response3");
+ });
+
+ // Create multiple concurrent requests through the tunnel
+ const promises = [];
+ for (let i = 1; i <= 3; i++) {
+ let chan = makeChan(
+ `${server.protocol()}://alt1.example.com:${server.port()}/concurrent${i}`
+ );
+ promises.push(
+ channelOpenPromise(chan, CL_IGNORE_CL | CL_ALLOW_UNKNOWN_CL)
+ );
+ }
+
+ const results = await Promise.all(promises);
+
+ // Verify all requests succeeded with correct responses
+ for (let i = 0; i < 3; i++) {
+ const [req, buf] = results[i];
+ Assert.equal(req.status, Cr.NS_OK);
+ Assert.equal(buf, `response${i + 1}`);
+ }
+ info("All concurrent tunnels completed successfully");
+ }
+ );
});
-add_task(test_http_connect);
-add_task(test_http_connect_auth_failure);
-add_task(test_http_connect_large_data);
-add_task(test_http_connect_connection_refused);
-add_task(test_http_connect_invalid_host);
-add_task(test_concurrent_http_connect_tunnels);
-// TODO: Proxy needs to close the stream properly when socket failures occur
-// add_task(test_http_connect_stream_closure);
-add_task(test_connect_udp);
-add_task(test_http_connect_fallback);
-add_task(test_inner_connection_fallback);
+/**
+ * Test HTTP CONNECT tunnel stream closure handling - verifies proper cleanup
+ * when the tunnel connection is closed unexpectedly
+ */
+add_task(async function test_http_connect_stream_closure() {
+ await with_node_servers([NodeHTTPServer], async server => {
+ info(`Testing stream closure with ${server.constructor.name} server`);
+
+ await server.registerPathHandler("/close", (req, resp) => {
+ // Send partial response then close connection abruptly
+ resp.writeHead(200, { "Content-Type": "text/plain" });
+ resp.write("partial");
+ // Simulate connection closure
+ resp.destroy();
+ });
+
+ let chan = makeChan(
+ `${server.protocol()}://alt1.example.com:${server.port()}/close`
+ );
+ let [req] = await channelOpenPromise(chan, CL_EXPECT_FAILURE);
+
+ // Should handle connection closure gracefully
+ Assert.notEqual(req.status, Cr.NS_OK);
+ info(`Stream closure status: ${req.status}`);
+ });
+}).skip(
+ "TODO: Proxy needs to close the stream properly when socket failures occur"
+);
+
+/**
+ * Test connect-udp - SUCCESS case.
+ * Will use h3 proxy to connect to h3 server.
+ */
+add_task(async function test_connect_udp() {
+ let h3Port = Services.env.get("MOZHTTP3_PORT");
+ info(`h3Port = ${h3Port}`);
+
+ Services.prefs.setCharPref(
+ "network.http.http3.alt-svc-mapping-for-testing",
+ `alt1.example.com;h3=:${h3Port}`
+ );
+
+ {
+ let chan = makeChan(`https://alt1.example.com:${h3Port}/no_body`);
+ let [req] = await channelOpenPromise(
+ chan,
+ CL_IGNORE_CL | CL_ALLOW_UNKNOWN_CL
+ );
+ Assert.equal(req.protocolVersion, "h3");
+ Assert.equal(req.status, Cr.NS_OK);
+ Assert.equal(req.responseStatus, 200);
+ }
+});
+
+add_task(async function test_http_connect_fallback() {
+ pps.unregisterFilter(proxyFilter);
+
+ Services.prefs.setCharPref(
+ "network.http.http3.alt-svc-mapping-for-testing",
+ ""
+ );
+
+ let proxyPort = noResponsePort;
+ let proxy = new NodeHTTP2ProxyServer();
+ await proxy.startWithoutProxyFilter(proxyPort);
+ Assert.equal(proxyPort, proxy.port());
+ dump(`proxy port=${proxy.port()}\n`);
+
+ let server = new NodeHTTP2Server();
+ await server.start();
+
+ // Register multiple endpoints
+ await server.registerPathHandler("/concurrent1", (req, resp) => {
+ resp.writeHead(200);
+ resp.end("response1");
+ });
+ await server.registerPathHandler("/concurrent2", (req, resp) => {
+ resp.writeHead(200);
+ resp.end("response2");
+ });
+ await server.registerPathHandler("/concurrent3", (req, resp) => {
+ resp.writeHead(200);
+ resp.end("response3");
+ });
+
+ let filter = new Http3ProxyFilter(
+ proxyHost,
+ proxy.port(),
+ 0,
+ "/.well-known/masque/udp/{target_host}/{target_port}/",
+ proxyAuth
+ );
+ pps.registerFilter(filter, 10);
+
+ registerCleanupFunction(async () => {
+ await proxy.stop();
+ await server.stop();
+ });
+
+ // Create multiple concurrent requests through the tunnel
+ const promises = [];
+ for (let i = 1; i <= 3; i++) {
+ let chan = makeChan(
+ `${server.protocol()}://alt1.example.com:${server.port()}/concurrent${i}`
+ );
+ promises.push(channelOpenPromise(chan, CL_IGNORE_CL | CL_ALLOW_UNKNOWN_CL));
+ }
+
+ const results = await Promise.all(promises);
+
+ // Verify all requests succeeded with correct responses
+ for (let i = 0; i < 3; i++) {
+ const [req, buf] = results[i];
+ Assert.equal(req.status, Cr.NS_OK);
+ Assert.equal(buf, `response${i + 1}`);
+ }
+
+ let h3Port = server.port();
+ console.log(`h3Port = ${h3Port}`);
+
+ Services.prefs.setCharPref(
+ "network.http.http3.alt-svc-mapping-for-testing",
+ `alt1.example.com;h3=:${h3Port}`
+ );
+
+ let chan = makeChan(`https://alt1.example.com:${h3Port}/concurrent1`);
+ let [req] = await channelOpenPromise(
+ chan,
+ CL_IGNORE_CL | CL_ALLOW_UNKNOWN_CL
+ );
+ Assert.equal(req.status, Cr.NS_OK);
+ Assert.equal(req.responseStatus, 200);
+
+ await proxy.stop();
+ pps.unregisterFilter(filter);
+ await server.stop();
+});
+
+add_task(async function test_inner_connection_fallback() {
+ let h3Port = Services.env.get("MOZHTTP3_PORT_NO_RESPONSE");
+ info(`h3Port = ${h3Port}`);
+
+ // Register the connect-udp proxy.
+ pps.registerFilter(proxyFilter, 10);
+
+ let server = new NodeHTTPSServer();
+ await server.start(h3Port);
+
+ // Register multiple endpoints
+ await server.registerPathHandler("/concurrent1", (req, resp) => {
+ resp.writeHead(200);
+ resp.end("fallback1");
+ });
+ await server.registerPathHandler("/concurrent2", (req, resp) => {
+ resp.writeHead(200);
+ resp.end("fallback2");
+ });
+ await server.registerPathHandler("/concurrent3", (req, resp) => {
+ resp.writeHead(200);
+ resp.end("fallback3");
+ });
+ registerCleanupFunction(async () => {
+ await server.stop();
+ });
+
+ Services.prefs.setCharPref(
+ "network.http.http3.alt-svc-mapping-for-testing",
+ `alt1.example.com;h3=:${h3Port}`
+ );
+
+ // Create multiple concurrent requests through the tunnel
+ const promises = [];
+ for (let i = 1; i <= 3; i++) {
+ let chan = makeChan(
+ `${server.protocol()}://alt1.example.com:${h3Port}/concurrent${i}`
+ );
+ promises.push(channelOpenPromise(chan, CL_IGNORE_CL | CL_ALLOW_UNKNOWN_CL));
+ }
+
+ const results = await Promise.all(promises);
+
+ // Verify all requests succeeded with correct responses
+ for (let i = 0; i < 3; i++) {
+ const [req, buf] = results[i];
+ Assert.equal(req.status, Cr.NS_OK);
+ Assert.equal(buf, `fallback${i + 1}`);
+ }
+});
diff --git a/netwerk/test/unit/test_http3_proxy_no_speculative.js b/netwerk/test/unit/test_http3_proxy_no_speculative.js
@@ -1,25 +0,0 @@
-/* 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 http3_proxy_common.js */
-
-add_setup(async function () {
- Services.prefs.setIntPref("network.http.speculative-parallel-limit", 0);
-
- await setup_http3_proxy();
-});
-
-add_task(test_http_connect);
-add_task(test_http_connect_auth_failure);
-add_task(test_http_connect_large_data);
-add_task(test_http_connect_connection_refused);
-add_task(test_http_connect_invalid_host);
-add_task(test_concurrent_http_connect_tunnels);
-// TODO: Proxy needs to close the stream properly when socket failures occur
-// add_task(test_http_connect_stream_closure);
-add_task(test_connect_udp);
-add_task(test_http_connect_fallback);
-add_task(test_inner_connection_fallback);
diff --git a/netwerk/test/unit/xpcshell.toml b/netwerk/test/unit/xpcshell.toml
@@ -26,7 +26,6 @@ support-files = [
"trr_common.js",
"test_http3_prio_helpers.js",
"http2_test_common.js",
- "http3_proxy_common.js",
]
# dom.serviceWorkers.enabled is currently set to false in StaticPrefList.yaml
@@ -857,15 +856,6 @@ run-sequentially = ["true"] # http3server
skip-if = ["true"] # Will be reenabled in bug 1865394
["test_http3_proxy.js"]
-head = "head_cookies.js head_channels.js head_cache.js head_http3.js http3_proxy_common.js"
-run-sequentially = ["true"] # node server exceptions dont replay well
-skip-if = [
- "os == 'android'",
- "os == 'win' && os_version == '11.26100' && processor == 'x86_64' && msix'", # Bug 1808049
-]
-
-["test_http3_proxy_no_speculative.js"]
-head = "head_cookies.js head_channels.js head_cache.js head_http3.js http3_proxy_common.js"
run-sequentially = ["true"] # node server exceptions dont replay well
skip-if = [
"os == 'android'",