commit eba1592b1611912d988565848c1d43291bbcf2b8
parent 23d523ef3f5a4daa1062ff91a78030f239f529e2
Author: Valentin Gosu <valentin.gosu@gmail.com>
Date: Tue, 25 Nov 2025 10:26:23 +0000
Bug 2001617 - Fix WS with masque proxy r=necko-reviewers,kershaw
This commit fixes WebSocket connections through HTTP/3 (MASQUE) proxy. The changes address several issues that caused WebSocket tests to fail when
running sequentially.
1. Http3Session.cpp - Fix ZERORTT stream queueing
When a tunnel stream can't do 0RTT (which is always the case for tunnel streams), it now gets added to mCannotDo0RTTStreams instead of just asserting it
shouldn't be there. This ensures the stream will be retried via Finish0Rtt() when the H3 session reaches CONNECTED state.
2. Http3StreamTunnel.cpp - Fix SetSecurityCallbacks
Changed SetSecurityCallbacks() to return NS_OK instead of NS_ERROR_NOT_IMPLEMENTED. This was causing WebSocket channel setup to fail.
3. DnsAndConnectSocket.cpp - Special handling for WebSocket through H3 proxy
For WebSocket transactions going through an HTTP/3 proxy, the transaction is queued for later dispatch (when the H3 session is connected) rather than
immediately dispatched. A NullHttpTransaction is used to drive the H3 connection establishment.
4. nsHttpConnectionMgr.cpp - WebSocket tunnel creation through H3 proxy
Added logic to handle WebSocket/WebTransport through H3 proxy:
- First checks for an existing H2 tunnel connection to reuse
- If none exists, creates a new tunnel through the H3 proxy using CreateTunnelStream()
5. ConnectionEntry.cpp/.h - New helper function
Added GetH2TunnelActiveConn() to find an existing H2 tunnel connection (nsHttpConnection using SPDY/H2) in active connections. This is needed because
GetH2orH3ActiveConn() skips H3 proxy entries when looking for H2 connections.
Differential Revision: https://phabricator.services.mozilla.com/D273789
Diffstat:
7 files changed, 103 insertions(+), 4 deletions(-)
diff --git a/netwerk/protocol/http/ConnectionEntry.cpp b/netwerk/protocol/http/ConnectionEntry.cpp
@@ -717,6 +717,23 @@ HttpConnectionBase* ConnectionEntry::GetH2orH3ActiveConn() {
return nullptr;
}
+already_AddRefed<nsHttpConnection> ConnectionEntry::GetH2TunnelActiveConn() {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ for (const auto& conn : mActiveConns) {
+ RefPtr<nsHttpConnection> connTCP = do_QueryObject(conn);
+ if (connTCP && connTCP->UsingSpdy() && connTCP->CanDirectlyActivate()) {
+ LOG(
+ ("GetH2TunnelActiveConn() request for ent %p %s "
+ "found an H2 tunnel connection %p\n",
+ this, mConnInfo->HashKey().get(), connTCP.get()));
+ return connTCP.forget();
+ }
+ }
+
+ return nullptr;
+}
+
void ConnectionEntry::CloseActiveConnections() {
while (mActiveConns.Length()) {
RefPtr<HttpConnectionBase> conn(mActiveConns[0]);
diff --git a/netwerk/protocol/http/ConnectionEntry.h b/netwerk/protocol/http/ConnectionEntry.h
@@ -74,6 +74,9 @@ class ConnectionEntry : public SupportsWeakPtr {
void RemoveExtendedCONNECTConns(HttpConnectionBase* conn);
HttpConnectionBase* GetH2orH3ActiveConn();
+ // Find an H2 tunnel connection (nsHttpConnection with UsingSpdy()) in active
+ // connections. This is used for WebSocket/WebTransport through H3 proxy.
+ already_AddRefed<nsHttpConnection> GetH2TunnelActiveConn();
// Make an active spdy connection DontReuse.
// TODO: this is a helper function and should nbe improved.
bool MakeFirstActiveSpdyConnDontReuse();
diff --git a/netwerk/protocol/http/DnsAndConnectSocket.cpp b/netwerk/protocol/http/DnsAndConnectSocket.cpp
@@ -24,6 +24,7 @@
#include "nsHttpHandler.h"
#include "ConnectionEntry.h"
#include "HttpConnectionUDP.h"
+#include "NullHttpTransaction.h"
#include "nsServiceManagerUtils.h"
#include "mozilla/net/NeckoChannelParams.h" // For HttpActivityArgs.
@@ -622,6 +623,35 @@ nsresult DnsAndConnectSocket::SetupConn(bool isPrimary, nsresult status) {
ent->InsertIntoActiveConns(conn);
if (mIsHttp3) {
+ // For WebSocket through HTTP/3 proxy, queue the transaction to be
+ // dispatched when the H3 session is connected, and use a NullTransaction
+ // to drive the H3 connection establishment.
+ // We do NOT create a ConnectionHandle for the WebSocket transaction here
+ // because it will get a tunnel connection later, and setting a
+ // ConnectionHandle now would cause it to be reclaimed when cleared.
+ nsHttpTransaction* trans = pendingTransInfo->Transaction();
+ if (trans->IsWebsocketUpgrade()) {
+ LOG(
+ ("DnsAndConnectSocket::SetupConn WebSocket through HTTP/3 proxy, "
+ "queueing for tunnel creation after H3 connected"));
+ // Put the transaction back in the pending queue so it can be
+ // dispatched through TryDispatchTransaction when the H3 session
+ // reports it's connected
+ RefPtr<PendingTransactionInfo> newPendingInfo =
+ new PendingTransactionInfo(trans);
+ ent->InsertTransaction(newPendingInfo);
+
+ // Dispatch a NullHttpTransaction to drive the H3 proxy connection
+ // establishment
+ nsCOMPtr<nsIInterfaceRequestor> nullCallbacks;
+ trans->GetSecurityCallbacks(getter_AddRefs(nullCallbacks));
+ RefPtr<nsAHttpTransaction> nullTrans =
+ new NullHttpTransaction(mConnInfo, nullCallbacks, mCaps);
+ rv = gHttpHandler->ConnMgr()->DispatchAbstractTransaction(
+ ent, nullTrans, mCaps, conn, 0);
+ return rv;
+ }
+
// Each connection must have a ConnectionHandle wrapper.
// In case of Http < 2 the a ConnectionHandle is created for each
// transaction in DispatchAbstractTransaction.
diff --git a/netwerk/protocol/http/Http3Session.cpp b/netwerk/protocol/http/Http3Session.cpp
@@ -1461,7 +1461,13 @@ nsresult Http3Session::TryActivating(
if (mState == ZERORTT) {
if (!aStream->Do0RTT()) {
- MOZ_ASSERT(!mCannotDo0RTTStreams.Contains(aStream));
+ // Stream can't do 0RTT - queue it for activation when the session
+ // reaches CONNECTED state via Finish0Rtt.
+ if (!mCannotDo0RTTStreams.Contains(aStream)) {
+ LOG(("Http3Session %p queuing stream %p for post-0RTT activation", this,
+ aStream));
+ mCannotDo0RTTStreams.AppendElement(aStream);
+ }
return NS_BASE_STREAM_WOULD_BLOCK;
}
}
diff --git a/netwerk/protocol/http/Http3StreamTunnel.cpp b/netwerk/protocol/http/Http3StreamTunnel.cpp
@@ -388,7 +388,7 @@ Http3TransportLayer::GetSecurityCallbacks(
NS_IMETHODIMP
Http3TransportLayer::SetSecurityCallbacks(
nsIInterfaceRequestor* aSecurityCallbacks) {
- return NS_ERROR_NOT_IMPLEMENTED;
+ return NS_OK;
}
NS_IMETHODIMP
diff --git a/netwerk/protocol/http/nsHttpConnectionMgr.cpp b/netwerk/protocol/http/nsHttpConnectionMgr.cpp
@@ -1402,6 +1402,49 @@ nsresult nsHttpConnectionMgr::TryDispatchTransaction(
// look for existing spdy connection - that's always best because it is
// essentially pipelining without head of line blocking
+ // For WebSocket/WebTransport through H3 proxy, we need to create a TCP
+ // tunnel through the H3 proxy first. But if there's already an H2 session
+ // available (from a previously established tunnel), we should use that
+ // instead of creating a new tunnel.
+ // The WebSocket transaction doesn't have a connection set (it was queued
+ // without one in DnsAndConnectSocket::SetupConn to avoid triggering reclaim
+ // when we clear it here).
+ if ((trans->IsWebsocketUpgrade() || trans->IsForWebTransport()) &&
+ ent->IsHttp3ProxyConnection()) {
+ // First check if there's an H2 session available (from existing tunnel)
+ // This handles the case where the tunnel was already established and the
+ // WebSocket transaction was reset to wait for H2 negotiation.
+ // We can't use GetH2orH3ActiveConn because it skips H3 proxy entries when
+ // looking for H2 connections. We use GetH2TunnelActiveConn to directly
+ // look for an H2 tunnel connection in the active connections.
+ RefPtr<nsHttpConnection> h2Tunnel = ent->GetH2TunnelActiveConn();
+ if (h2Tunnel) {
+ LOG(
+ ("TryDispatchTransaction: WebSocket through H3 proxy - using "
+ "existing H2 tunnel"));
+ return TryDispatchExtendedCONNECTransaction(ent, trans, h2Tunnel);
+ }
+
+ // No H2 session available yet - create a tunnel through the H3 proxy
+ RefPtr<HttpConnectionBase> conn = GetH2orH3ActiveConn(ent, true, false);
+ RefPtr<HttpConnectionUDP> connUDP = do_QueryObject(conn);
+ if (connUDP) {
+ LOG(("TryDispatchTransaction: WebSocket through HTTP/3 proxy"));
+ RefPtr<HttpConnectionBase> tunnelConn;
+ nsresult rv =
+ connUDP->CreateTunnelStream(trans, getter_AddRefs(tunnelConn), true);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ ent->InsertIntoActiveConns(tunnelConn);
+ tunnelConn->SetInTunnel();
+ if (trans->IsWebsocketUpgrade()) {
+ trans->SetIsHttp2Websocket(true);
+ }
+ return DispatchTransaction(ent, trans, tunnelConn);
+ }
+ }
+
RefPtr<HttpConnectionBase> conn = GetH2orH3ActiveConn(
ent,
(!StaticPrefs::network_http_http2_enabled() ||
diff --git a/netwerk/protocol/websocket/WebSocketChannel.cpp b/netwerk/protocol/websocket/WebSocketChannel.cpp
@@ -3747,9 +3747,9 @@ WebSocketChannel::OnTransportAvailable(nsISocketTransport* aTransport,
nsresult rv;
rv = mTransport->SetEventSink(nullptr, nullptr);
- if (NS_FAILED(rv)) return rv;
+ if (NS_WARN_IF(NS_FAILED(rv))) return rv;
rv = mTransport->SetSecurityCallbacks(this);
- if (NS_FAILED(rv)) return rv;
+ if (NS_WARN_IF(NS_FAILED(rv))) return rv;
return OnTransportAvailableInternal();
}