Bug 1713748 - Partition WebSocket nsWSAdmissionManager queue. r=timhuang,nhnt11

Differential Revision: https://phabricator.services.mozilla.com/D119534
This commit is contained in:
Paul Zuehlcke 2021-07-21 12:44:40 +00:00
parent a5e65da4cb
commit f97e49d85c
18 changed files with 136 additions and 42 deletions

View File

@ -537,7 +537,7 @@ var PushServiceWebSocket = {
try {
// Grab a wakelock before we open the socket to ensure we don't go to
// sleep before connection the is opened.
this._ws.asyncOpen(uri, uri.spec, 0, this._wsListener, null);
this._ws.asyncOpen(uri, uri.spec, {}, 0, this._wsListener, null);
this._currentState = STATE_WAITING_FOR_WS_START;
} catch (e) {
console.error(

View File

@ -42,7 +42,7 @@ MockWebSocketParent.prototype = {
return this._originalURI;
},
asyncOpen(uri, origin, windowId, listener, context) {
asyncOpen(uri, origin, originAttributes, windowId, listener, context) {
this._listener = listener;
this._context = context;
waterfall(() => this._listener.onStart(this._context));

View File

@ -250,7 +250,7 @@ MockWebSocket.prototype = {
return this._originalURI;
},
asyncOpen(uri, origin, windowId, listener, context) {
asyncOpen(uri, origin, originAttributes, windowId, listener, context) {
this._listener = listener;
this._context = context;
waterfall(() => this._listener.onStart(this._context));

View File

@ -1213,7 +1213,7 @@ class AsyncOpenRunnable final : public WebSocketMainThreadRunnable {
return true;
}
nsCOMPtr<nsIPrincipal> principal = doc->NodePrincipal();
nsCOMPtr<nsIPrincipal> principal = doc->PartitionedPrincipal();
if (!principal) {
mErrorCode = NS_ERROR_FAILURE;
return true;
@ -1233,8 +1233,9 @@ class AsyncOpenRunnable final : public WebSocketMainThreadRunnable {
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(aTopLevelWorkerPrivate && !aTopLevelWorkerPrivate->GetWindow());
mErrorCode = mImpl->AsyncOpen(aTopLevelWorkerPrivate->GetPrincipal(), 0,
nullptr, ""_ns, nullptr);
mErrorCode =
mImpl->AsyncOpen(aTopLevelWorkerPrivate->GetPartitionedPrincipal(), 0,
nullptr, ""_ns, nullptr);
return true;
}
@ -1256,6 +1257,7 @@ already_AddRefed<WebSocket> WebSocket::ConstructorCommon(
const nsACString& aNegotiatedExtensions, ErrorResult& aRv) {
MOZ_ASSERT_IF(!aTransportProvider, aNegotiatedExtensions.IsEmpty());
nsCOMPtr<nsIPrincipal> principal;
nsCOMPtr<nsIPrincipal> partitionedPrincipal;
nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
if (NS_WARN_IF(!global)) {
@ -1272,7 +1274,8 @@ already_AddRefed<WebSocket> WebSocket::ConstructorCommon(
}
principal = scriptPrincipal->GetPrincipal();
if (!principal) {
partitionedPrincipal = scriptPrincipal->PartitionedPrincipal();
if (!principal || !partitionedPrincipal) {
aRv.Throw(NS_ERROR_FAILURE);
return nullptr;
}
@ -1420,6 +1423,7 @@ already_AddRefed<WebSocket> WebSocket::ConstructorCommon(
if (NS_IsMainThread()) {
MOZ_ASSERT(principal);
MOZ_ASSERT(partitionedPrincipal);
nsCOMPtr<nsPIDOMWindowInner> ownerWindow = do_QueryInterface(global);
@ -1437,8 +1441,9 @@ already_AddRefed<WebSocket> WebSocket::ConstructorCommon(
}
}
aRv = webSocket->mImpl->AsyncOpen(principal, windowID, aTransportProvider,
aNegotiatedExtensions, std::move(stack));
aRv = webSocket->mImpl->AsyncOpen(partitionedPrincipal, windowID,
aTransportProvider, aNegotiatedExtensions,
std::move(stack));
} else {
MOZ_ASSERT(!aTransportProvider && aNegotiatedExtensions.IsEmpty(),
"not yet implemented");
@ -1748,7 +1753,9 @@ nsresult WebSocketImpl::AsyncOpen(
MOZ_ASSERT(NS_SUCCEEDED(rv));
}
rv = mChannel->AsyncOpen(uri, asciiOrigin, aInnerWindowID, this, nullptr);
rv = mChannel->AsyncOpenNative(uri, asciiOrigin,
aPrincipal->OriginAttributesRef(),
aInnerWindowID, this, nullptr);
if (NS_WARN_IF(NS_FAILED(rv))) {
return NS_ERROR_CONTENT_BLOCKED;
}

View File

@ -716,6 +716,11 @@ class WorkerPrivate final : public RelativeTimeline {
return mLoadInfo.mLoadingPrincipal;
}
nsIPrincipal* GetPartitionedPrincipal() const {
AssertIsOnMainThread();
return mLoadInfo.mPartitionedPrincipal;
}
const nsAString& OriginNoSuffix() const { return mLoadInfo.mOriginNoSuffix; }
const nsACString& Origin() const { return mLoadInfo.mOrigin; }

View File

@ -10445,6 +10445,12 @@
value: false
mirror: always
# Partition the websocket pending connection queue by OriginAttributes.
- name: privacy.partition.network_state.ws_connection_queue
type: RelaxedAtomicBool
value: true
mirror: always
# The global switch to control the URL query sting stripping which strips query
# parameters from loading URIs to prevent bounce (redirect) tracking.
- name: privacy.query_stripping.enabled

View File

@ -29,6 +29,7 @@ parent:
// Forwarded methods corresponding to methods on nsIWebSocketChannel
async AsyncOpen(nsIURI aURI,
nsCString aOrigin,
OriginAttributes aOriginAttributes,
uint64_t aInnerWindowID,
nsCString aProtocol,
bool aSecure,

View File

@ -12,6 +12,7 @@
#include "mozilla/Attributes.h"
#include "mozilla/EndianUtils.h"
#include "mozilla/MathAlgorithms.h"
#include "mozilla/StaticPrefs_privacy.h"
#include "mozilla/Utf8.h"
#include "mozilla/net/WebSocketEventService.h"
@ -317,10 +318,11 @@ class nsWSAdmissionManager {
// If there is already another WS channel connecting to this IP address,
// defer BeginOpen and mark as waiting in queue.
bool found = (sManager->IndexOf(ws->mAddress) >= 0);
bool found = (sManager->IndexOf(ws->mAddress, ws->mOriginSuffix) >= 0);
// Always add ourselves to queue, even if we'll connect immediately
UniquePtr<nsOpenConn> newdata(new nsOpenConn(ws->mAddress, ws));
UniquePtr<nsOpenConn> newdata(
new nsOpenConn(ws->mAddress, ws->mOriginSuffix, ws));
sManager->mQueue.AppendElement(std::move(newdata));
if (found) {
@ -357,7 +359,7 @@ class nsWSAdmissionManager {
// Check for queued connections to same host.
// Note: still need to check for failures, since next websocket with same
// host may have different port
sManager->ConnectNext(aChannel->mAddress);
sManager->ConnectNext(aChannel->mAddress, aChannel->mOriginSuffix);
}
// Called every time a websocket channel ends its session (including going
@ -409,7 +411,7 @@ class nsWSAdmissionManager {
LOG(("Websocket: changing state to NOT_CONNECTING"));
aChannel->mConnecting = NOT_CONNECTING;
if (wasNotQueued) {
sManager->ConnectNext(aChannel->mAddress);
sManager->ConnectNext(aChannel->mAddress, aChannel->mOriginSuffix);
}
}
}
@ -447,20 +449,22 @@ class nsWSAdmissionManager {
class nsOpenConn {
public:
nsOpenConn(nsCString& addr, WebSocketChannel* channel)
: mAddress(addr), mChannel(channel) {
nsOpenConn(nsCString& addr, nsCString& originSuffix,
WebSocketChannel* channel)
: mAddress(addr), mOriginSuffix(originSuffix), mChannel(channel) {
MOZ_COUNT_CTOR(nsOpenConn);
}
MOZ_COUNTED_DTOR(nsOpenConn)
nsCString mAddress;
nsCString mOriginSuffix;
WebSocketChannel* mChannel;
};
void ConnectNext(nsCString& hostName) {
void ConnectNext(nsCString& hostName, nsCString& originSuffix) {
MOZ_ASSERT(NS_IsMainThread(), "not main thread");
int32_t index = IndexOf(hostName);
int32_t index = IndexOf(hostName, originSuffix);
if (index >= 0) {
WebSocketChannel* chan = mQueue[index]->mChannel;
@ -481,9 +485,15 @@ class nsWSAdmissionManager {
}
}
int32_t IndexOf(nsCString& aStr) {
int32_t IndexOf(nsCString& aAddress, nsCString& aOriginSuffix) {
for (uint32_t i = 0; i < mQueue.Length(); i++) {
if (aStr == (mQueue[i])->mAddress) return i;
bool isPartitioned =
StaticPrefs::privacy_partition_network_state() &&
StaticPrefs::privacy_partition_network_state_ws_connection_queue();
if (aAddress == (mQueue[i])->mAddress &&
(!isPartitioned || aOriginSuffix == (mQueue[i])->mOriginSuffix)) {
return i;
}
}
return -1;
}
@ -3241,11 +3251,28 @@ WebSocketChannel::GetSecurityInfo(nsISupports** aSecurityInfo) {
NS_IMETHODIMP
WebSocketChannel::AsyncOpen(nsIURI* aURI, const nsACString& aOrigin,
JS::HandleValue aOriginAttributes,
uint64_t aInnerWindowID,
nsIWebSocketListener* aListener,
nsISupports* aContext) {
nsISupports* aContext, JSContext* aCx) {
OriginAttributes attrs;
if (!aOriginAttributes.isObject() || !attrs.Init(aCx, aOriginAttributes)) {
return NS_ERROR_INVALID_ARG;
}
return AsyncOpenNative(aURI, aOrigin, attrs, aInnerWindowID, aListener,
aContext);
}
NS_IMETHODIMP
WebSocketChannel::AsyncOpenNative(nsIURI* aURI, const nsACString& aOrigin,
const OriginAttributes& aOriginAttributes,
uint64_t aInnerWindowID,
nsIWebSocketListener* aListener,
nsISupports* aContext) {
LOG(("WebSocketChannel::AsyncOpen() %p\n", this));
aOriginAttributes.CreateSuffix(mOriginSuffix);
if (!NS_IsMainThread()) {
MOZ_ASSERT(false, "not main thread");
LOG(("WebSocketChannel::AsyncOpen() called off the main thread"));

View File

@ -92,8 +92,14 @@ class WebSocketChannel : public BaseWebSocketChannel,
// nsIWebSocketChannel methods BaseWebSocketChannel didn't implement for us
//
NS_IMETHOD AsyncOpen(nsIURI* aURI, const nsACString& aOrigin,
uint64_t aWindowID, nsIWebSocketListener* aListener,
nsISupports* aContext) override;
JS::HandleValue aOriginAttributes, uint64_t aWindowID,
nsIWebSocketListener* aListener, nsISupports* aContext,
JSContext* aCx) override;
NS_IMETHOD AsyncOpenNative(nsIURI* aURI, const nsACString& aOrigin,
const OriginAttributes& aOriginAttributes,
uint64_t aWindowID,
nsIWebSocketListener* aListener,
nsISupports* aContext) override;
NS_IMETHOD Close(uint16_t aCode, const nsACString& aReason) override;
NS_IMETHOD SendMsg(const nsACString& aMsg) override;
NS_IMETHOD SendBinaryMsg(const nsACString& aMsg) override;
@ -207,6 +213,8 @@ class WebSocketChannel : public BaseWebSocketChannel,
// then to IP address (unless we're leaving DNS resolution to a proxy server)
nsCString mAddress;
int32_t mPort; // WS server port
// Secondary key for the connection queue. Used by nsWSAdmissionManager.
nsCString mOriginSuffix;
// Used for off main thread access to the URI string.
nsCString mHost;

View File

@ -458,9 +458,23 @@ void WebSocketChannelChild::SetupNeckoTarget() {
NS_IMETHODIMP
WebSocketChannelChild::AsyncOpen(nsIURI* aURI, const nsACString& aOrigin,
JS::HandleValue aOriginAttributes,
uint64_t aInnerWindowID,
nsIWebSocketListener* aListener,
nsISupports* aContext) {
nsISupports* aContext, JSContext* aCx) {
OriginAttributes attrs;
if (!aOriginAttributes.isObject() || !attrs.Init(aCx, aOriginAttributes)) {
return NS_ERROR_INVALID_ARG;
}
return AsyncOpenNative(aURI, aOrigin, attrs, aInnerWindowID, aListener,
aContext);
}
NS_IMETHODIMP
WebSocketChannelChild::AsyncOpenNative(
nsIURI* aURI, const nsACString& aOrigin,
const OriginAttributes& aOriginAttributes, uint64_t aInnerWindowID,
nsIWebSocketListener* aListener, nsISupports* aContext) {
LOG(("WebSocketChannelChild::AsyncOpen() %p\n", this));
MOZ_ASSERT(NS_IsMainThread(), "not main thread");
@ -511,10 +525,11 @@ WebSocketChannelChild::AsyncOpen(nsIURI* aURI, const nsACString& aOrigin,
gNeckoChild->SendPWebSocketConstructor(
this, browserChild, IPC::SerializedLoadContext(this), mSerial);
if (!SendAsyncOpen(uri, nsCString(aOrigin), aInnerWindowID, mProtocol,
mEncrypted, mPingInterval, mClientSetPingInterval,
mPingResponseTimeout, mClientSetPingTimeout, loadInfoArgs,
transportProvider, mNegotiatedExtensions)) {
if (!SendAsyncOpen(uri, nsCString(aOrigin), aOriginAttributes, aInnerWindowID,
mProtocol, mEncrypted, mPingInterval,
mClientSetPingInterval, mPingResponseTimeout,
mClientSetPingTimeout, loadInfoArgs, transportProvider,
mNegotiatedExtensions)) {
return NS_ERROR_UNEXPECTED;
}

View File

@ -32,8 +32,14 @@ class WebSocketChannelChild final : public BaseWebSocketChannel,
// nsIWebSocketChannel methods BaseWebSocketChannel didn't implement for us
//
NS_IMETHOD AsyncOpen(nsIURI* aURI, const nsACString& aOrigin,
JS::HandleValue aOriginAttributes,
uint64_t aInnerWindowID, nsIWebSocketListener* aListener,
nsISupports* aContext) override;
nsISupports* aContext, JSContext* aCx) override;
NS_IMETHOD AsyncOpenNative(nsIURI* aURI, const nsACString& aOrigin,
const OriginAttributes& aOriginAttributes,
uint64_t aInnerWindowID,
nsIWebSocketListener* aListener,
nsISupports* aContext) override;
NS_IMETHOD Close(uint16_t code, const nsACString& reason) override;
NS_IMETHOD SendMsg(const nsACString& aMsg) override;
NS_IMETHOD SendBinaryMsg(const nsACString& aMsg) override;

View File

@ -47,7 +47,8 @@ mozilla::ipc::IPCResult WebSocketChannelParent::RecvDeleteSelf() {
}
mozilla::ipc::IPCResult WebSocketChannelParent::RecvAsyncOpen(
nsIURI* aURI, const nsCString& aOrigin, const uint64_t& aInnerWindowID,
nsIURI* aURI, const nsCString& aOrigin,
const OriginAttributes& aOriginAttributes, const uint64_t& aInnerWindowID,
const nsCString& aProtocol, const bool& aSecure,
const uint32_t& aPingInterval, const bool& aClientSetPingInterval,
const uint32_t& aPingTimeout, const bool& aClientSetPingTimeout,
@ -118,7 +119,8 @@ mozilla::ipc::IPCResult WebSocketChannelParent::RecvAsyncOpen(
MOZ_ASSERT(NS_SUCCEEDED(rv));
}
rv = mChannel->AsyncOpen(uri, aOrigin, aInnerWindowID, this, nullptr);
rv = mChannel->AsyncOpenNative(uri, aOrigin, aOriginAttributes,
aInnerWindowID, this, nullptr);
if (NS_FAILED(rv)) goto fail;
return IPC_OK();

View File

@ -39,7 +39,8 @@ class WebSocketChannelParent : public PWebSocketParent,
private:
mozilla::ipc::IPCResult RecvAsyncOpen(
nsIURI* aURI, const nsCString& aOrigin, const uint64_t& aInnerWindowID,
nsIURI* aURI, const nsCString& aOrigin,
const OriginAttributes& aOriginAttributes, const uint64_t& aInnerWindowID,
const nsCString& aProtocol, const bool& aSecure,
const uint32_t& aPingInterval, const bool& aClientSetPingInterval,
const uint32_t& aPingTimeout, const bool& aClientSetPingTimeout,

View File

@ -19,6 +19,10 @@ webidl Node;
#include "nsISupports.idl"
#include "nsIContentPolicy.idl"
[ref] native OriginAttributes(const mozilla::OriginAttributes);
/**
* Low-level websocket API: handles network protocol.
*
@ -128,15 +132,26 @@ interface nsIWebSocketChannel : nsISupports
*
* @param aURI the uri of the websocket protocol - may be redirected
* @param aOrigin the uri of the originating resource
* @param aOriginAttributes attributes of the originating resource.
* @param aInnerWindowID the inner window ID
* @param aListener the nsIWebSocketListener implementation
* @param aContext an opaque parameter forwarded to aListener's methods
*/
[must_use] void asyncOpen(in nsIURI aURI,
in ACString aOrigin,
in unsigned long long aInnerWindowID,
in nsIWebSocketListener aListener,
in nsISupports aContext);
[implicit_jscontext]
void asyncOpen(in nsIURI aURI,
in ACString aOrigin,
in jsval aOriginAttributes,
in unsigned long long aInnerWindowID,
in nsIWebSocketListener aListener,
in nsISupports aContext);
[must_use]
void asyncOpenNative(in nsIURI aURI,
in ACString aOrigin,
in OriginAttributes aOriginAttributes,
in unsigned long long aInnerWindowID,
in nsIWebSocketListener aListener,
in nsISupports aContext);
/*
* Close the websocket connection for writing - no more calls to sendMsg

View File

@ -164,8 +164,9 @@ static int FuzzingRunNetworkWebsocket(const uint8_t* data, size_t size) {
gWebSocketListener = new FuzzingWebSocketListener();
rv =
gWebSocketChannel->AsyncOpen(url, spec, 0, gWebSocketListener, nullptr);
OriginAttributes attrs;
rv = gWebSocketChannel->AsyncOpenNative(url, spec, attrs, 0,
gWebSocketListener, nullptr);
if (rv == NS_OK) {
FUZZING_LOG(("Successful call to AsyncOpen"));

View File

@ -72,6 +72,6 @@ function run_test() {
Ci.nsIContentPolicy.TYPE_WEBSOCKET
);
chan.asyncOpen(uri, url, 0, listener, null);
chan.asyncOpen(uri, url, {}, 0, listener, null);
do_test_pending();
}

View File

@ -42,7 +42,7 @@ function run_test() {
);
var uri = Services.io.newURI(url);
chan.asyncOpen(uri, url, 0, listener, null);
chan.asyncOpen(uri, url, {}, 0, listener, null);
do_test_pending();
} catch (x) {
dump("throwing " + x);

View File

@ -83,7 +83,7 @@ add_task(async function open_wss_when_h3_is_active() {
var wsListener = new WebSocketListener();
await new Promise(resolve => {
wsListener.finish = resolve;
chan.asyncOpen(uri, wssUri, 0, wsListener, null);
chan.asyncOpen(uri, wssUri, {}, 0, wsListener, null);
});
// Try to use https protocol, it should sttill use HTTP/3