Bug 902346 - Support socks proxy in TCPSocket. r=mixedpuppy,necko-reviewers

- Use nsIProtocolProxyService to look up proxy
- Pass the found proxy to CreateTransport

Differential Revision: https://phabricator.services.mozilla.com/D104357
This commit is contained in:
Ping Chen 2021-02-18 23:47:44 +00:00
Родитель ff3000edd1
Коммит b2fa673570
4 изменённых файлов: 193 добавлений и 16 удалений

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

@ -36,7 +36,14 @@
#include "nsIOutputStream.h"
#include "nsINSSErrorsService.h"
#include "nsISSLSocketControl.h"
#include "nsIProtocolProxyService.h"
#include "nsICancelable.h"
#include "nsIChannel.h"
#include "nsIURIMutator.h"
#include "nsNetCID.h"
#include "nsNetUtil.h"
#include "nsServiceManagerUtils.h"
#include "nsString.h"
#include "nsStringStream.h"
#include "secerr.h"
#include "sslerr.h"
@ -131,6 +138,7 @@ NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(TCPSocket)
NS_INTERFACE_MAP_ENTRY(nsIObserver)
NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
NS_INTERFACE_MAP_ENTRY(nsITCPSocketCallback)
NS_INTERFACE_MAP_ENTRY(nsIProtocolProxyCallback)
NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
TCPSocket::TCPSocket(nsIGlobalObject* aGlobal, const nsAString& aHost,
@ -221,7 +229,7 @@ nsresult TCPSocket::InitWithUnconnectedTransport(
return NS_OK;
}
nsresult TCPSocket::Init() {
nsresult TCPSocket::Init(nsIProxyInfo* aProxyInfo) {
nsCOMPtr<nsIObserverService> obs =
do_GetService("@mozilla.org/observer-service;1");
if (obs) {
@ -254,7 +262,7 @@ nsresult TCPSocket::Init() {
nsCOMPtr<nsISocketTransport> transport;
nsresult rv =
sts->CreateTransport(socketTypes, NS_ConvertUTF16toUTF8(mHost), mPort,
nullptr, nullptr, getter_AddRefs(transport));
aProxyInfo, nullptr, getter_AddRefs(transport));
NS_ENSURE_SUCCESS(rv, rv);
return InitWithUnconnectedTransport(transport);
@ -708,6 +716,11 @@ void TCPSocket::CloseHelper(bool waitForUnsentData) {
mReadyState = TCPReadyState::Closing;
if (mProxyRequest) {
mProxyRequest->Cancel(NS_BINDING_ABORTED);
mProxyRequest = nullptr;
}
if (mSocketBridgeChild) {
mSocketBridgeChild->SendClose();
return;
@ -829,9 +842,8 @@ TCPReadyState TCPSocket::ReadyState() { return mReadyState; }
TCPSocketBinaryType TCPSocket::BinaryType() {
if (mUseArrayBuffers) {
return TCPSocketBinaryType::Arraybuffer;
} else {
return TCPSocketBinaryType::String;
}
return TCPSocketBinaryType::String;
}
already_AddRefed<TCPSocket> TCPSocket::CreateAcceptedSocket(
@ -859,15 +871,70 @@ already_AddRefed<TCPSocket> TCPSocket::Constructor(
RefPtr<TCPSocket> socket =
new TCPSocket(global, aHost, aPort, aOptions.mUseSecureTransport,
aOptions.mBinaryType == TCPSocketBinaryType::Arraybuffer);
nsresult rv = socket->Init();
if (NS_WARN_IF(NS_FAILED(rv))) {
aRv.Throw(rv);
return nullptr;
}
socket->ResolveProxy();
return socket.forget();
}
nsresult TCPSocket::ResolveProxy() {
nsresult rv;
nsCOMPtr<nsIProtocolProxyService> pps =
do_GetService(NS_PROTOCOLPROXYSERVICE_CONTRACTID, &rv);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
nsCOMPtr<nsIURI> uri;
nsCString spec = mSsl ? "https://"_ns : "http://"_ns;
if (!AppendUTF16toUTF8(mHost, spec, fallible)) {
return NS_ERROR_OUT_OF_MEMORY;
}
rv = NS_MutateURI(NS_STANDARDURLMUTATOR_CONTRACTID)
.SetSpec(spec)
.SetPort(mPort)
.Finalize(uri);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
nsCOMPtr<nsIChannel> channel;
rv = NS_NewChannel(getter_AddRefs(channel), uri,
nsContentUtils::GetSystemPrincipal(),
nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
nsIContentPolicy::TYPE_OTHER);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
rv = pps->AsyncResolve(channel,
nsIProtocolProxyService::RESOLVE_PREFER_SOCKS_PROXY,
this, nullptr, getter_AddRefs(mProxyRequest));
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
return NS_OK;
}
NS_IMETHODIMP
TCPSocket::OnProxyAvailable(nsICancelable* aRequest, nsIChannel* aChannel,
nsIProxyInfo* aProxyInfo, nsresult aResult) {
mProxyRequest = nullptr;
if (NS_SUCCEEDED(aResult) && aProxyInfo) {
nsCString proxyType;
nsresult rv = aProxyInfo->GetType(proxyType);
if (NS_WARN_IF(NS_FAILED(rv))) {
Close();
return rv;
}
// Only supports SOCKS proxy for now.
if (proxyType == "socks" || proxyType == "socks4") {
return Init(aProxyInfo);
}
}
return Init(nullptr);
}
nsresult TCPSocket::CreateInputStreamPump() {
if (!mSocketInputStream) {
return NS_ERROR_NOT_AVAILABLE;

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

@ -10,6 +10,7 @@
#include "mozilla/dom/TCPSocketBinding.h"
#include "mozilla/dom/TypedArray.h"
#include "mozilla/DOMEventTargetHelper.h"
#include "nsIProxyInfo.h"
#include "nsITransport.h"
#include "nsIStreamListener.h"
#include "nsIAsyncInputStream.h"
@ -17,6 +18,7 @@
#include "nsIObserver.h"
#include "nsWeakReference.h"
#include "nsITCPSocketCallback.h"
#include "nsIProtocolProxyCallback.h"
#include "js/RootingAPI.h"
class nsISocketTransport;
@ -69,7 +71,8 @@ class TCPSocket final : public DOMEventTargetHelper,
public nsIInputStreamCallback,
public nsIObserver,
public nsSupportsWeakReference,
public nsITCPSocketCallback {
public nsITCPSocketCallback,
public nsIProtocolProxyCallback {
public:
TCPSocket(nsIGlobalObject* aGlobal, const nsAString& aHost, uint16_t aPort,
bool aSsl, bool aUseArrayBuffers);
@ -83,6 +86,7 @@ class TCPSocket final : public DOMEventTargetHelper,
NS_DECL_NSIINPUTSTREAMCALLBACK
NS_DECL_NSIOBSERVER
NS_DECL_NSITCPSOCKETCALLBACK
NS_DECL_NSIPROTOCOLPROXYCALLBACK
virtual JSObject* WrapObject(JSContext* aCx,
JS::Handle<JSObject*> aGivenProto) override;
@ -120,8 +124,7 @@ class TCPSocket final : public DOMEventTargetHelper,
// Used by the TCPServerSocketChild implementation when a new connection is
// accepted.
static already_AddRefed<TCPSocket> CreateAcceptedSocket(
nsIGlobalObject* aGlobal, TCPSocketChild* aSocketBridge,
bool aUseArrayBuffers);
nsIGlobalObject* aGlobal, TCPSocketChild* aBridge, bool aUseArrayBuffers);
// Initialize this socket's associated IPC actor in the parent process.
void SetSocketBridgeParent(TCPSocketParent* aBridgeParent);
@ -134,7 +137,7 @@ class TCPSocket final : public DOMEventTargetHelper,
IMPL_EVENT_HANDLER(error);
IMPL_EVENT_HANDLER(close);
nsresult Init();
nsresult Init(nsIProxyInfo* aProxyInfo);
// Inform this socket that a buffered send() has completed sending.
void NotifyCopyComplete(nsresult aStatus);
@ -147,7 +150,7 @@ class TCPSocket final : public DOMEventTargetHelper,
~TCPSocket();
// Initialize this socket with an existing IPC actor.
void InitWithSocketChild(TCPSocketChild* aBridge);
void InitWithSocketChild(TCPSocketChild* aSocketBridge);
// Initialize this socket from an existing low-level connection.
nsresult InitWithTransport(nsISocketTransport* aTransport);
// Initialize the input/output streams for this socket object.
@ -170,6 +173,8 @@ class TCPSocket final : public DOMEventTargetHelper,
// Helper for Close/CloseImmediately
void CloseHelper(bool waitForUnsentData);
nsresult ResolveProxy();
TCPReadyState mReadyState;
// Whether to use strings or array buffers for the "data" event.
bool mUseArrayBuffers;
@ -188,6 +193,8 @@ class TCPSocket final : public DOMEventTargetHelper,
nsCOMPtr<nsIInputStream> mSocketInputStream;
nsCOMPtr<nsIOutputStream> mSocketOutputStream;
nsCOMPtr<nsICancelable> mProxyRequest;
// Input stream machinery
nsCOMPtr<nsIInputStreamPump> mInputStreamPump;
nsCOMPtr<nsIScriptableInputStream> mInputStreamScriptable;

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

@ -80,7 +80,7 @@ mozilla::ipc::IPCResult TCPSocketParent::RecvOpen(
const bool& aUseArrayBuffers) {
mSocket = new TCPSocket(nullptr, aHost, aPort, aUseSSL, aUseArrayBuffers);
mSocket->SetSocketBridgeParent(this);
NS_ENSURE_SUCCESS(mSocket->Init(), IPC_OK());
NS_ENSURE_SUCCESS(mSocket->Init(nullptr), IPC_OK());
return IPC_OK();
}

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

@ -360,7 +360,7 @@ class SocksTestServer {
/**
* Tests the basic socks logic using a simple socket connection and the
* protocol proxy service. It seems TCPSocket has no way to tie proxy
* protocol proxy service. Before 902346, TCPSocket has no way to tie proxy
* data to it, so we go old school here.
*/
class SocksTestClient {
@ -481,6 +481,67 @@ add_task(async function test_socks_server() {
});
});
// Register a proxy to be used by TCPSocket connections later.
function registerProxy(socks) {
let pps = Cc["@mozilla.org/network/protocol-proxy-service;1"].getService(
Ci.nsIProtocolProxyService
);
let filter = {
QueryInterface: ChromeUtils.generateQI(["nsIProtocolProxyFilter"]),
applyFilter(uri, proxyInfo, callback) {
callback.onProxyFilterResult(
pps.newProxyInfoWithAuth(
socks.version,
socks.host,
socks.port,
socks.username,
socks.password,
"",
"",
socks.dns == "remote"
? Ci.nsIProxyInfo.TRANSPARENT_PROXY_RESOLVES_HOST
: 0,
-1,
null
)
);
},
};
pps.registerFilter(filter, 0);
registerCleanupFunction(() => {
pps.unregisterFilter(filter);
});
}
// A simple ping/pong to test the socks server with TCPSocket.
add_task(async function test_tcpsocket_proxy() {
let socks = {
version: "socks",
host: "127.0.0.1",
port: socksServer.listener.localPort,
username: "foo",
password: "bar",
dns: false,
};
let dest = {
host: "localhost",
port: 8888,
};
registerProxy(socks);
await new Promise((resolve, reject) => {
let client = new TCPSocket(dest.host, dest.port);
client.onopen = () => {
client.send("PING!");
};
client.ondata = e => {
equal("PONG!", e.data, "socks test ok");
resolve();
};
client.onerror = () => reject();
});
});
add_task(async function test_webRequest_socks_proxy() {
async function background(port) {
function checkProxyData(details) {
@ -555,3 +616,45 @@ add_task(async function test_webRequest_socks_proxy() {
await contentPage.close();
await handlingExt.unload();
});
add_task(async function test_onRequest_tcpsocket_proxy() {
async function background(port) {
browser.proxy.onRequest.addListener(
() => {
return [
{
type: "socks",
host: "127.0.0.1",
port,
username: "foo",
password: "bar",
},
];
},
{ urls: ["<all_urls>"] }
);
}
let handlingExt = ExtensionTestUtils.loadExtension({
manifest: {
permissions: ["proxy", "webRequest", "webRequestBlocking", "<all_urls>"],
},
background: `(${background})(${socksServer.listener.localPort})`,
});
await handlingExt.startup();
await new Promise((resolve, reject) => {
let client = new TCPSocket("localhost", 8888);
client.onopen = () => {
client.send("PING!");
};
client.ondata = e => {
equal("PONG!", e.data, "socks test ok");
resolve();
};
client.onerror = () => reject();
});
await handlingExt.unload();
});