diff options
author | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
---|---|---|
committer | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
commit | 5f8de423f190bbb79a62f804151bc24824fa32d8 (patch) | |
tree | 10027f336435511475e392454359edea8e25895d /netwerk/socket | |
parent | 49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff) | |
download | uxp-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.gz |
Add m-esr52 at 52.6.0
Diffstat (limited to 'netwerk/socket')
-rw-r--r-- | netwerk/socket/moz.build | 37 | ||||
-rw-r--r-- | netwerk/socket/nsINamedPipeService.idl | 77 | ||||
-rw-r--r-- | netwerk/socket/nsISOCKSSocketInfo.idl | 24 | ||||
-rw-r--r-- | netwerk/socket/nsISSLSocketControl.idl | 140 | ||||
-rw-r--r-- | netwerk/socket/nsISocketProvider.idl | 124 | ||||
-rw-r--r-- | netwerk/socket/nsISocketProviderService.idl | 20 | ||||
-rw-r--r-- | netwerk/socket/nsITransportSecurityInfo.idl | 24 | ||||
-rw-r--r-- | netwerk/socket/nsNamedPipeIOLayer.cpp | 952 | ||||
-rw-r--r-- | netwerk/socket/nsNamedPipeIOLayer.h | 23 | ||||
-rw-r--r-- | netwerk/socket/nsNamedPipeService.cpp | 324 | ||||
-rw-r--r-- | netwerk/socket/nsNamedPipeService.h | 59 | ||||
-rw-r--r-- | netwerk/socket/nsSOCKSIOLayer.cpp | 1612 | ||||
-rw-r--r-- | netwerk/socket/nsSOCKSIOLayer.h | 25 | ||||
-rw-r--r-- | netwerk/socket/nsSOCKSSocketProvider.cpp | 110 | ||||
-rw-r--r-- | netwerk/socket/nsSOCKSSocketProvider.h | 35 | ||||
-rw-r--r-- | netwerk/socket/nsSocketProviderService.cpp | 45 | ||||
-rw-r--r-- | netwerk/socket/nsSocketProviderService.h | 24 | ||||
-rw-r--r-- | netwerk/socket/nsUDPSocketProvider.cpp | 50 | ||||
-rw-r--r-- | netwerk/socket/nsUDPSocketProvider.h | 23 |
19 files changed, 3728 insertions, 0 deletions
diff --git a/netwerk/socket/moz.build b/netwerk/socket/moz.build new file mode 100644 index 0000000000..34361a10c7 --- /dev/null +++ b/netwerk/socket/moz.build @@ -0,0 +1,37 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +XPIDL_SOURCES += [ + 'nsISocketProvider.idl', + 'nsISocketProviderService.idl', + 'nsISOCKSSocketInfo.idl', + 'nsISSLSocketControl.idl', + 'nsITransportSecurityInfo.idl', +] + +XPIDL_MODULE = 'necko_socket' + +LOCAL_INCLUDES += [ + '/netwerk/base', +] + +UNIFIED_SOURCES += [ + 'nsSocketProviderService.cpp', + 'nsSOCKSIOLayer.cpp', + 'nsSOCKSSocketProvider.cpp', + 'nsUDPSocketProvider.cpp', +] + +if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows': + XPIDL_SOURCES += [ + 'nsINamedPipeService.idl', + ] + UNIFIED_SOURCES += [ + 'nsNamedPipeIOLayer.cpp', + 'nsNamedPipeService.cpp' + ] + +FINAL_LIBRARY = 'xul' diff --git a/netwerk/socket/nsINamedPipeService.idl b/netwerk/socket/nsINamedPipeService.idl new file mode 100644 index 0000000000..9db557577d --- /dev/null +++ b/netwerk/socket/nsINamedPipeService.idl @@ -0,0 +1,77 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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/. */ + +#include "nsISupports.idl" +#include "nsrootidl.idl" + +/** + * nsINamedPipeDataObserver + * + * This is the callback interface for nsINamedPipeService. + * The functions are called by the internal thread in the nsINamedPipeService. + */ +[scriptable, uuid(de4f460b-94fd-442c-9002-1637beb2185a)] +interface nsINamedPipeDataObserver : nsISupports +{ + /** + * onDataAvailable + * + * @param aBytesTransferred + * Transfered bytes during last transmission. + * @param aOverlapped + * Corresponding overlapped structure used by the async I/O + */ + void onDataAvailable(in unsigned long aBytesTransferred, + in voidPtr aOverlapped); + + /** + * onError + * + * @param aError + * Error code of the error. + * @param aOverlapped + * Corresponding overlapped structure used by the async I/O + */ + void onError(in unsigned long aError, + in voidPtr aOverlapped); +}; + +/** + * nsINamedPipeService + */ +[scriptable, uuid(1bf19133-5625-4ac8-836a-80b1c215f72b)] +interface nsINamedPipeService : nsISupports +{ + /** + * addDataObserver + * + * @param aHandle + * The handle that is going to be monitored for read/write operations. + * Only handles that are opened with overlapped IO are supported. + * @param aObserver + * The observer of the handle, the service strong-refs of the observer. + */ + void addDataObserver(in voidPtr aHandle, + in nsINamedPipeDataObserver aObserver); + + /** + * removeDataObserver + * + * @param aHandle + The handle associated to the observer, and will be closed by the + service. + * @param aObserver + * The observer to be removed. + */ + void removeDataObserver(in voidPtr aHandle, + in nsINamedPipeDataObserver aObserver); + + /** + * isOnCurrentThread + * + * @return the caller runs within the internal thread. + */ + boolean isOnCurrentThread(); +}; diff --git a/netwerk/socket/nsISOCKSSocketInfo.idl b/netwerk/socket/nsISOCKSSocketInfo.idl new file mode 100644 index 0000000000..b17176f53b --- /dev/null +++ b/netwerk/socket/nsISOCKSSocketInfo.idl @@ -0,0 +1,24 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * 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/. */ + +#include "nsISupports.idl" + +%{ C++ +namespace mozilla { +namespace net { +union NetAddr; +} +} +%} +[ptr] native NetAddrPtr(mozilla::net::NetAddr); + +[scriptable, uuid(D5C0D1F9-22D7-47DC-BF91-D9AC6E1251A6)] +interface nsISOCKSSocketInfo : nsISupports +{ + [noscript] attribute NetAddrPtr destinationAddr; + [noscript] attribute NetAddrPtr externalProxyAddr; + [noscript] attribute NetAddrPtr internalProxyAddr; +}; diff --git a/netwerk/socket/nsISSLSocketControl.idl b/netwerk/socket/nsISSLSocketControl.idl new file mode 100644 index 0000000000..b8c2b41848 --- /dev/null +++ b/netwerk/socket/nsISSLSocketControl.idl @@ -0,0 +1,140 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * 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/. */ + +#include "nsISupports.idl" + +interface nsIInterfaceRequestor; +interface nsIX509Cert; + +%{C++ +#include "nsTArrayForwardDeclare.h" +class nsCString; +%} +[ref] native nsCStringTArrayRef(nsTArray<nsCString>); + +[scriptable, builtinclass, uuid(418265c8-654e-4fbb-ba62-4eed27de1f03)] +interface nsISSLSocketControl : nsISupports { + attribute nsIInterfaceRequestor notificationCallbacks; + + void proxyStartSSL(); + void StartTLS(); + + /* NPN (Next Protocol Negotiation) is a mechanism for + negotiating the protocol to be spoken inside the SSL + tunnel during the SSL handshake. The NPNList is the list + of offered client side protocols. setNPNList() needs to + be called before any data is read or written (including the + handshake to be setup correctly. The server determines the + priority when multiple matches occur, but if there is no overlap + the first protocol in the list is used. */ + + [noscript] void setNPNList(in nsCStringTArrayRef aNPNList); + + /* negotiatedNPN is '' if no NPN list was provided by the client, + * or if the server did not select any protocol choice from that + * list. That also includes the case where the server does not + * implement NPN. + * + * If negotiatedNPN is read before NPN has progressed to the point + * where this information is available NS_ERROR_NOT_CONNECTED is + * raised. + */ + readonly attribute ACString negotiatedNPN; + + /* For 0RTT we need to know the alpn protocol selected for the last tls + * session. This function will return a value if applicable or an error + * NS_ERROR_NOT_AVAILABLE. + */ + ACString getAlpnEarlySelection(); + + /* If 0RTT handshake was applied and some data has been sent, as soon as + * the handshake finishes this attribute will be set to appropriate value. + */ + readonly attribute bool earlyDataAccepted; + + /* When 0RTT is performed, PR_Write will not drive the handshake forward. + * It must be forced by calling this function. + */ + void driveHandshake(); + + /* Determine if a potential SSL connection to hostname:port with + * a desired NPN negotiated protocol of npnProtocol can use the socket + * associated with this object instead of making a new one. + */ + boolean joinConnection( + in ACString npnProtocol, /* e.g. "spdy/2" */ + in ACString hostname, + in long port); + + /* Determine if existing connection should be trusted to convey information about + * a hostname. + */ + boolean isAcceptableForHost(in ACString hostname); + + /* The Key Exchange Algorithm is used when determining whether or + not HTTP/2 can be used. + + After a handshake is complete it can be read from KEAUsed. + The values correspond to the SSLKEAType enum in NSS or the + KEY_EXCHANGE_UNKNOWN constant defined below. + + KEAKeyBits is the size/security-level used for the KEA. + */ + + [infallible] readonly attribute short KEAUsed; + [infallible] readonly attribute unsigned long KEAKeyBits; + + const short KEY_EXCHANGE_UNKNOWN = -1; + + /* + * The original flags from the socket provider. + */ + readonly attribute uint32_t providerFlags; + + /* These values are defined by TLS. */ + const short SSL_VERSION_3 = 0x0300; + const short TLS_VERSION_1 = 0x0301; + const short TLS_VERSION_1_1 = 0x0302; + const short TLS_VERSION_1_2 = 0x0303; + const short TLS_VERSION_1_3 = 0x0304; + const short SSL_VERSION_UNKNOWN = -1; + + [infallible] readonly attribute short SSLVersionUsed; + [infallible] readonly attribute short SSLVersionOffered; + + /* These values match the NSS defined values in sslt.h */ + const short SSL_MAC_UNKNOWN = -1; + const short SSL_MAC_NULL = 0; + const short SSL_MAC_MD5 = 1; + const short SSL_MAC_SHA = 2; + const short SSL_HMAC_MD5 = 3; + const short SSL_HMAC_SHA = 4; + const short SSL_HMAC_SHA256 = 5; + const short SSL_MAC_AEAD = 6; + + [infallible] readonly attribute short MACAlgorithmUsed; + + /** + * If set before the server requests a client cert (assuming it does so at + * all), then this cert will be presented to the server, instead of asking + * the user or searching the set of rememebered user cert decisions. + */ + attribute nsIX509Cert clientCert; + + /** + * bypassAuthentication is true if the server certificate checks are + * not be enforced. This is to enable non-secure transport over TLS. + */ + [infallible] readonly attribute boolean bypassAuthentication; + + /* + * failedVerification is true if any enforced certificate checks have failed. + * Connections that have not yet tried to verify, have verifications bypassed, + * or are using acceptable exceptions will all return false. + */ + [infallible] readonly attribute boolean failedVerification; +}; + diff --git a/netwerk/socket/nsISocketProvider.idl b/netwerk/socket/nsISocketProvider.idl new file mode 100644 index 0000000000..19b010eda8 --- /dev/null +++ b/netwerk/socket/nsISocketProvider.idl @@ -0,0 +1,124 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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/. */ + +#include "nsISupports.idl" + +interface nsIProxyInfo; +[ptr] native PRFileDescStar(struct PRFileDesc); +native NeckoOriginAttributes(mozilla::NeckoOriginAttributes); +[ref] native const_OriginAttributesRef(const mozilla::NeckoOriginAttributes); + +%{ C++ +#include "mozilla/BasePrincipal.h" +%} + +/** + * nsISocketProvider + */ +[scriptable, uuid(508d5469-9e1e-4a08-b5b0-7cfebba1e51a)] +interface nsISocketProvider : nsISupports +{ + /** + * newSocket + * + * @param aFamily + * The address family for this socket (PR_AF_INET or PR_AF_INET6). + * @param aHost + * The origin hostname for this connection. + * @param aPort + * The origin port for this connection. + * @param aProxyHost + * If non-null, the proxy hostname for this connection. + * @param aProxyPort + * The proxy port for this connection. + * @param aFlags + * Control flags that govern this connection (see below.) + * @param aFileDesc + * The resulting PRFileDesc. + * @param aSecurityInfo + * Any security info that should be associated with aFileDesc. This + * object typically implements nsITransportSecurityInfo. + */ + [noscript] + void newSocket(in long aFamily, + in string aHost, + in long aPort, + in nsIProxyInfo aProxy, + in const_OriginAttributesRef aOriginAttributes, + in unsigned long aFlags, + out PRFileDescStar aFileDesc, + out nsISupports aSecurityInfo); + + /** + * addToSocket + * + * This function is called to allow the socket provider to layer a + * PRFileDesc on top of another PRFileDesc. For example, SSL via a SOCKS + * proxy. + * + * Parameters are the same as newSocket with the exception of aFileDesc, + * which is an in-param instead. + */ + [noscript] + void addToSocket(in long aFamily, + in string aHost, + in long aPort, + in nsIProxyInfo aProxy, + in const_OriginAttributesRef aOriginAttributes, + in unsigned long aFlags, + in PRFileDescStar aFileDesc, + out nsISupports aSecurityInfo); + + /** + * PROXY_RESOLVES_HOST + * + * This flag is set if the proxy is to perform hostname resolution instead + * of the client. When set, the hostname parameter passed when in this + * interface will be used instead of the address structure passed for a + * later connect et al. request. + */ + const long PROXY_RESOLVES_HOST = 1 << 0; + + /** + * When setting this flag, the socket will not apply any + * credentials when establishing a connection. For example, + * an SSL connection would not send any client-certificates + * if this flag is set. + */ + const long ANONYMOUS_CONNECT = 1 << 1; + + /** + * If set, indicates that the connection was initiated from a source + * defined as being private in the sense of Private Browsing. Generally, + * there should be no state shared between connections that are private + * and those that are not; it is OK for multiple private connections + * to share state with each other, and it is OK for multiple non-private + * connections to share state with each other. + */ + const unsigned long NO_PERMANENT_STORAGE = 1 << 2; + + /** + * This flag is an explicit opt-in that allows a normally secure socket + * provider to use, at its discretion, an insecure algorithm. e.g. + * a TLS socket without authentication. + */ + const unsigned long MITM_OK = 1 << 3; + + /** + * If set, do not use newer protocol features that might have interop problems + * on the Internet. Intended only for use with critical infra like the updater. + * default is false. + */ + const unsigned long BE_CONSERVATIVE = 1 << 4; +}; + +%{C++ +/** + * nsISocketProvider implementations should be registered with XPCOM under a + * contract ID of the form: "@mozilla.org/network/socket;2?type=foo" + */ +#define NS_NETWORK_SOCKET_CONTRACTID_PREFIX \ + "@mozilla.org/network/socket;2?type=" +%} diff --git a/netwerk/socket/nsISocketProviderService.idl b/netwerk/socket/nsISocketProviderService.idl new file mode 100644 index 0000000000..db38d299a5 --- /dev/null +++ b/netwerk/socket/nsISocketProviderService.idl @@ -0,0 +1,20 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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/. */ + +#include "nsISupports.idl" + +interface nsISocketProvider; + +/** + * nsISocketProviderService + * + * Provides a mapping between a socket type and its associated socket provider + * instance. One could also use the service manager directly. + */ +[scriptable, uuid(8f8a23d0-5472-11d3-bbc8-0000861d1237)] +interface nsISocketProviderService : nsISupports +{ + nsISocketProvider getSocketProvider(in string socketType); +}; diff --git a/netwerk/socket/nsITransportSecurityInfo.idl b/netwerk/socket/nsITransportSecurityInfo.idl new file mode 100644 index 0000000000..bec59c8e53 --- /dev/null +++ b/netwerk/socket/nsITransportSecurityInfo.idl @@ -0,0 +1,24 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * 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/. */ + +#include "nsISupports.idl" + +interface nsIX509CertList; + +[scriptable, uuid(216112d3-28bc-4671-b057-f98cc09ba1ea)] +interface nsITransportSecurityInfo : nsISupports { + readonly attribute unsigned long securityState; + readonly attribute wstring errorMessage; + readonly attribute long errorCode; // PRErrorCode + + /** + * If certificate verification failed, this will be the peer certificate + * chain provided in the handshake, so it can be used for error reporting. + * If verification succeeded, this will be null. + */ + readonly attribute nsIX509CertList failedCertChain; +}; + diff --git a/netwerk/socket/nsNamedPipeIOLayer.cpp b/netwerk/socket/nsNamedPipeIOLayer.cpp new file mode 100644 index 0000000000..6de51ea1c3 --- /dev/null +++ b/netwerk/socket/nsNamedPipeIOLayer.cpp @@ -0,0 +1,952 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#include <algorithm> +#include <utility> +#include "mozilla/Atomics.h" +#include "mozilla/DebugOnly.h" +#include "mozilla/Logging.h" +#include "mozilla/Move.h" +#include "mozilla/net/DNS.h" +#include "mozilla/RefPtr.h" +#include "mozilla/Unused.h" +#include "nsINamedPipeService.h" +#include "nsISupportsImpl.h" +#include "nsIThread.h" +#include "nsNamedPipeIOLayer.h" +#include "nsNetCID.h" +#include "nspr.h" +#include "nsServiceManagerUtils.h" +#include "nsSocketTransportService2.h" +#include "nsString.h" +#include "nsThreadUtils.h" +#include "private/pprio.h" + +namespace mozilla { +namespace net { + +static mozilla::LazyLogModule gNamedPipeLog("NamedPipeWin"); +#define LOG_NPIO_DEBUG(...) MOZ_LOG(gNamedPipeLog, mozilla::LogLevel::Debug, \ + (__VA_ARGS__)) +#define LOG_NPIO_ERROR(...) MOZ_LOG(gNamedPipeLog, mozilla::LogLevel::Error, \ + (__VA_ARGS__)) + +PRDescIdentity nsNamedPipeLayerIdentity; +static PRIOMethods nsNamedPipeLayerMethods; + +class NamedPipeInfo final : public nsINamedPipeDataObserver +{ +public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSINAMEDPIPEDATAOBSERVER + + explicit NamedPipeInfo(); + + nsresult Connect(const nsACString& aPath); + nsresult Disconnect(); + + /** + * Both blocking/non-blocking mode are supported in this class. + * The default mode is non-blocking mode, however, the client may change its + * mode to blocking mode during hand-shaking (e.g. nsSOCKSSocketInfo). + * + * In non-blocking mode, |Read| and |Write| should be called by clients only + * when |GetPollFlags| reports data availability. That is, the client calls + * |GetPollFlags| with |PR_POLL_READ| and/or |PR_POLL_WRITE| set, and + * according to the flags that set, |GetPollFlags| will check buffers status + * and decide corresponding actions: + * + * ------------------------------------------------------------------- + * | | data in buffer | empty buffer | + * |---------------+-------------------------+-----------------------| + * | PR_POLL_READ | out: PR_POLL_READ | DoRead/DoReadContinue | + * |---------------+-------------------------+-----------------------| + * | PR_POLL_WRITE | DoWrite/DoWriteContinue | out: PR_POLL_WRITE | + * ------------------------------------------+------------------------ + * + * |DoRead| and |DoWrite| initiate read/write operations asynchronously, and + * the |DoReadContinue| and |DoWriteContinue| are used to check the amount + * of the data are read/written to/from buffers. + * + * The output parameter and the return value of |GetPollFlags| are identical + * because we don't rely on the low-level select function to wait for data + * availability, we instead use nsNamedPipeService to poll I/O completeness. + * + * When client get |PR_POLL_READ| or |PR_POLL_WRITE| from |GetPollFlags|, + * they are able to use |Read| or |Write| to access the data in the buffer, + * and this is supposed to be very fast because no network traffic is involved. + * + * In blocking mode, the flow is quite similar to non-blocking mode, but + * |DoReadContinue| and |DoWriteContinue| are never been used since the + * operations are done synchronously, which could lead to slow responses. + */ + int32_t Read(void* aBuffer, int32_t aSize); + int32_t Write(const void* aBuffer, int32_t aSize); + + // Like Read, but doesn't remove data in internal buffer. + uint32_t Peek(void* aBuffer, int32_t aSize); + + // Number of bytes available to read in internal buffer. + int32_t Available() const; + + // Flush write buffer + // + // @return whether the buffer has been flushed + bool Sync(uint32_t aTimeout); + void SetNonblocking(bool nonblocking); + + bool IsConnected() const; + bool IsNonblocking() const; + HANDLE GetHandle() const; + + // Initiate and check current status for read/write operations. + int16_t GetPollFlags(int16_t aInFlags, int16_t* aOutFlags); + +private: + virtual ~NamedPipeInfo(); + + /** + * DoRead/DoWrite starts a read/write call synchronously or asynchronously + * depending on |mNonblocking|. In blocking mode, they return when the action + * has been done and in non-blocking mode it returns the number of bytes that + * were read/written if the operation is done immediately. If it takes some + * time to finish the operation, zero is returned and + * DoReadContinue/DoWriteContinue must be called to get async I/O result. + */ + int32_t DoRead(); + int32_t DoReadContinue(); + int32_t DoWrite(); + int32_t DoWriteContinue(); + + /** + * There was a write size limitation of named pipe, + * see https://support.microsoft.com/en-us/kb/119218 for more information. + * The limitation no longer exists, so feel free to change the value. + */ + static const uint32_t kBufferSize = 65536; + + nsCOMPtr<nsINamedPipeService> mNamedPipeService; + + HANDLE mPipe; // the handle to the named pipe. + OVERLAPPED mReadOverlapped; // used for asynchronous read operations. + OVERLAPPED mWriteOverlapped; // used for asynchronous write operations. + + uint8_t mReadBuffer[kBufferSize]; // octets read from pipe. + + /** + * These indicates the [begin, end) position of the data in the buffer. + */ + DWORD mReadBegin; + DWORD mReadEnd; + + bool mHasPendingRead; // previous asynchronous read is not finished yet. + + uint8_t mWriteBuffer[kBufferSize]; // octets to be written to pipe. + + /** + * These indicates the [begin, end) position of the data in the buffer. + */ + DWORD mWriteBegin; // how many bytes are already written. + DWORD mWriteEnd; // valid amount of data in the buffer. + + bool mHasPendingWrite; // previous asynchronous write is not finished yet. + + /** + * current blocking mode is non-blocking or not, accessed only in socket + * thread. + */ + bool mNonblocking; + + Atomic<DWORD> mErrorCode; // error code from Named Pipe Service. +}; + +NS_IMPL_ISUPPORTS(NamedPipeInfo, + nsINamedPipeDataObserver) + +NamedPipeInfo::NamedPipeInfo() + : mNamedPipeService(do_GetService(NS_NAMEDPIPESERVICE_CONTRACTID)) + , mPipe(INVALID_HANDLE_VALUE) + , mReadBegin(0) + , mReadEnd(0) + , mHasPendingRead(false) + , mWriteBegin(0) + , mWriteEnd(0) + , mHasPendingWrite(false) + , mNonblocking(true) + , mErrorCode(0) +{ + MOZ_ASSERT(mNamedPipeService); + + ZeroMemory(&mReadOverlapped, sizeof(OVERLAPPED)); + ZeroMemory(&mWriteOverlapped, sizeof(OVERLAPPED)); +} + +NamedPipeInfo::~NamedPipeInfo() +{ + MOZ_ASSERT(!mPipe); +} + +// nsINamedPipeDataObserver + +NS_IMETHODIMP +NamedPipeInfo::OnDataAvailable(uint32_t aBytesTransferred, + void* aOverlapped) +{ + DebugOnly<bool> isOnPipeServiceThread; + MOZ_ASSERT(NS_SUCCEEDED(mNamedPipeService->IsOnCurrentThread(&isOnPipeServiceThread)) && + isOnPipeServiceThread); + + if (aOverlapped == &mReadOverlapped) { + LOG_NPIO_DEBUG("[%s] %p read %d bytes", __func__, this, aBytesTransferred); + } else if (aOverlapped == &mWriteOverlapped) { + LOG_NPIO_DEBUG("[%s] %p write %d bytes", __func__, this, aBytesTransferred); + } else { + MOZ_ASSERT(false, "invalid callback"); + mErrorCode = ERROR_INVALID_DATA; + return NS_ERROR_FAILURE; + } + + mErrorCode = ERROR_SUCCESS; + + // dispatch an empty event to trigger STS thread + gSocketTransportService->Dispatch(NS_NewRunnableFunction([]{}), + NS_DISPATCH_NORMAL); + + return NS_OK; +} + +NS_IMETHODIMP +NamedPipeInfo::OnError(uint32_t aError, + void* aOverlapped) +{ + DebugOnly<bool> isOnPipeServiceThread; + MOZ_ASSERT(NS_SUCCEEDED(mNamedPipeService->IsOnCurrentThread(&isOnPipeServiceThread)) && + isOnPipeServiceThread); + + LOG_NPIO_ERROR("[%s] error code=%d", __func__, aError); + mErrorCode = aError; + + // dispatch an empty event to trigger STS thread + gSocketTransportService->Dispatch(NS_NewRunnableFunction([]{}), + NS_DISPATCH_NORMAL); + + return NS_OK; +} + +// Named pipe operations + +nsresult +NamedPipeInfo::Connect(const nsACString& aPath) +{ + MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); + + HANDLE pipe; + nsAutoCString path(aPath); + + pipe = CreateFileA(path.get(), + GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE, + nullptr, + OPEN_EXISTING, + FILE_FLAG_OVERLAPPED, + nullptr); + + if (pipe == INVALID_HANDLE_VALUE) { + LOG_NPIO_ERROR("[%p] CreateFile error (%d)", this, GetLastError()); + return NS_ERROR_FAILURE; + } + + DWORD pipeMode = PIPE_READMODE_MESSAGE; + if (!SetNamedPipeHandleState(pipe, &pipeMode, nullptr, nullptr)) { + LOG_NPIO_ERROR("[%p] SetNamedPipeHandleState error (%d)", + this, + GetLastError()); + CloseHandle(pipe); + return NS_ERROR_FAILURE; + } + + nsresult rv = mNamedPipeService->AddDataObserver(pipe, this); + if (NS_WARN_IF(NS_FAILED(rv))) { + CloseHandle(pipe); + return rv; + } + + HANDLE readEvent = CreateEventA(nullptr, TRUE, TRUE, "NamedPipeRead"); + if (NS_WARN_IF(!readEvent || readEvent == INVALID_HANDLE_VALUE)) { + CloseHandle(pipe); + return NS_ERROR_FAILURE; + } + + HANDLE writeEvent = CreateEventA(nullptr, TRUE, TRUE, "NamedPipeWrite"); + if (NS_WARN_IF(!writeEvent || writeEvent == INVALID_HANDLE_VALUE)) { + CloseHandle(pipe); + CloseHandle(readEvent); + return NS_ERROR_FAILURE; + } + + mPipe = pipe; + mReadOverlapped.hEvent = readEvent; + mWriteOverlapped.hEvent = writeEvent; + return NS_OK; +} + +nsresult +NamedPipeInfo::Disconnect() +{ + MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); + + nsresult rv = mNamedPipeService->RemoveDataObserver(mPipe, this); + NS_WARN_IF(NS_FAILED(rv)); + mPipe = nullptr; + + if (mReadOverlapped.hEvent && + mReadOverlapped.hEvent != INVALID_HANDLE_VALUE) { + CloseHandle(mReadOverlapped.hEvent); + mReadOverlapped.hEvent = nullptr; + } + + if (mWriteOverlapped.hEvent && + mWriteOverlapped.hEvent != INVALID_HANDLE_VALUE) { + CloseHandle(mWriteOverlapped.hEvent); + mWriteOverlapped.hEvent = nullptr; + } + + return NS_OK; +} + +int32_t +NamedPipeInfo::Read(void* aBuffer, int32_t aSize) +{ + MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); + + int32_t bytesRead = Peek(aBuffer, aSize); + + if (bytesRead > 0) { + mReadBegin += bytesRead; + } + + return bytesRead; +} + +int32_t +NamedPipeInfo::Write(const void* aBuffer, int32_t aSize) +{ + MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); + MOZ_ASSERT(mWriteBegin <= mWriteEnd); + + if (!IsConnected()) { + // pipe unconnected + PR_SetError(PR_NOT_CONNECTED_ERROR, 0); + return -1; + } + + if (mWriteBegin == mWriteEnd) { + mWriteBegin = mWriteEnd = 0; + } + + int32_t bytesToWrite = std::min<int32_t>(aSize, + sizeof(mWriteBuffer) - mWriteEnd); + MOZ_ASSERT(bytesToWrite >= 0); + + if (bytesToWrite == 0) { + PR_SetError(IsNonblocking() ? PR_WOULD_BLOCK_ERROR + : PR_IO_PENDING_ERROR, + 0); + return -1; + } + + memcpy(&mWriteBuffer[mWriteEnd], aBuffer, bytesToWrite); + mWriteEnd += bytesToWrite; + + /** + * Triggers internal write operation by calling |GetPollFlags|. + * This is required for callers that use blocking I/O because they don't call + * |GetPollFlags| to write data, but this also works for non-blocking I/O. + */ + int16_t outFlag; + GetPollFlags(PR_POLL_WRITE, &outFlag); + + return bytesToWrite; +} + +uint32_t +NamedPipeInfo::Peek(void* aBuffer, int32_t aSize) +{ + MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); + MOZ_ASSERT(mReadBegin <= mReadEnd); + + if (!IsConnected()) { + // pipe unconnected + PR_SetError(PR_NOT_CONNECTED_ERROR, 0); + return -1; + } + + /** + * If there's nothing in the read buffer, try to trigger internal read + * operation by calling |GetPollFlags|. This is required for callers that + * use blocking I/O because they don't call |GetPollFlags| to read data, + * but this also works for non-blocking I/O. + */ + if (!Available()) { + int16_t outFlag; + GetPollFlags(PR_POLL_READ, &outFlag); + + if (!(outFlag & PR_POLL_READ)) { + PR_SetError(IsNonblocking() ? PR_WOULD_BLOCK_ERROR + : PR_IO_PENDING_ERROR, + 0); + return -1; + } + } + + // Available() can't return more than what fits to the buffer at the read offset. + int32_t bytesRead = std::min<int32_t>(aSize, Available()); + MOZ_ASSERT(bytesRead >= 0); + MOZ_ASSERT(mReadBegin + bytesRead <= mReadEnd); + memcpy(aBuffer, &mReadBuffer[mReadBegin], bytesRead); + return bytesRead; +} + +int32_t +NamedPipeInfo::Available() const +{ + MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); + MOZ_ASSERT(mReadBegin <= mReadEnd); + MOZ_ASSERT(mReadEnd - mReadBegin <= 0x7FFFFFFF); // no more than int32_max + return mReadEnd - mReadBegin; +} + +bool +NamedPipeInfo::Sync(uint32_t aTimeout) +{ + MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); + if (!mHasPendingWrite) { + return true; + } + return WaitForSingleObject(mWriteOverlapped.hEvent, aTimeout) == WAIT_OBJECT_0; +} + +void +NamedPipeInfo::SetNonblocking(bool nonblocking) +{ + MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); + mNonblocking = nonblocking; +} + +bool +NamedPipeInfo::IsConnected() const +{ + MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); + return mPipe && mPipe != INVALID_HANDLE_VALUE; +} + +bool +NamedPipeInfo::IsNonblocking() const +{ + MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); + return mNonblocking; +} + +HANDLE +NamedPipeInfo::GetHandle() const +{ + MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); + return mPipe; +} + + +int16_t +NamedPipeInfo::GetPollFlags(int16_t aInFlags, int16_t* aOutFlags) +{ + MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); + + *aOutFlags = 0; + + if (aInFlags & PR_POLL_READ) { + int32_t bytesToRead = 0; + if (mReadBegin < mReadEnd) { // data in buffer and is ready to be read + bytesToRead = Available(); + } else if (mHasPendingRead) { // nonblocking I/O and has pending task + bytesToRead = DoReadContinue(); + } else { // read bufer is empty. + bytesToRead = DoRead(); + } + + if (bytesToRead > 0) { + *aOutFlags |= PR_POLL_READ; + } else if (bytesToRead < 0) { + *aOutFlags |= PR_POLL_ERR; + } + } + + if (aInFlags & PR_POLL_WRITE) { + int32_t bytesWritten = 0; + if (mHasPendingWrite) { // nonblocking I/O and has pending task. + bytesWritten = DoWriteContinue(); + } else if (mWriteBegin < mWriteEnd) { // data in buffer, ready to write + bytesWritten = DoWrite(); + } else { // write buffer is empty. + *aOutFlags |= PR_POLL_WRITE; + } + + if (bytesWritten < 0) { + *aOutFlags |= PR_POLL_ERR; + } else if (bytesWritten && + !mHasPendingWrite && + mWriteBegin == mWriteEnd) { + *aOutFlags |= PR_POLL_WRITE; + } + } + + return *aOutFlags; +} + +// @return: data has been read and is available +int32_t +NamedPipeInfo::DoRead() +{ + MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); + MOZ_ASSERT(!mHasPendingRead); + MOZ_ASSERT(mReadBegin == mReadEnd); // the buffer should be empty + + mReadBegin = 0; + mReadEnd = 0; + + BOOL success = ReadFile(mPipe, + mReadBuffer, + sizeof(mReadBuffer), + &mReadEnd, + IsNonblocking() ? &mReadOverlapped : nullptr); + + if (success) { + LOG_NPIO_DEBUG("[%s][%p] %d bytes read", __func__, this, mReadEnd); + return mReadEnd; + } + + switch (GetLastError()) { + case ERROR_MORE_DATA: // has more data to read + mHasPendingRead = true; + return DoReadContinue(); + + case ERROR_IO_PENDING: // read is pending + mHasPendingRead = true; + break; + + default: + LOG_NPIO_ERROR("[%s] ReadFile failed (%d)", __func__, GetLastError()); + Disconnect(); + PR_SetError(PR_IO_ERROR, 0); + return -1; + } + + return 0; +} + +int32_t +NamedPipeInfo::DoReadContinue() +{ + MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); + MOZ_ASSERT(mHasPendingRead); + MOZ_ASSERT(mReadBegin == 0 && mReadEnd == 0); + + BOOL success; + success = GetOverlappedResult(mPipe, + &mReadOverlapped, + &mReadEnd, + FALSE); + if (success) { + mHasPendingRead = false; + if (mReadEnd == 0) { + Disconnect(); + PR_SetError(PR_NOT_CONNECTED_ERROR, 0); + return -1; + } + + LOG_NPIO_DEBUG("[%s][%p] %d bytes read", __func__, this, mReadEnd); + return mReadEnd; + } + + switch (GetLastError()) { + case ERROR_MORE_DATA: + mHasPendingRead = false; + LOG_NPIO_DEBUG("[%s][%p] %d bytes read", __func__, this, mReadEnd); + return mReadEnd; + case ERROR_IO_INCOMPLETE: // still in progress + break; + default: + LOG_NPIO_ERROR("[%s]: GetOverlappedResult failed (%d)", + __func__, + GetLastError()); + Disconnect(); + PR_SetError(PR_IO_ERROR, 0); + return -1; + } + + return 0; +} + +int32_t +NamedPipeInfo::DoWrite() +{ + MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); + MOZ_ASSERT(!mHasPendingWrite); + MOZ_ASSERT(mWriteBegin < mWriteEnd); + + DWORD bytesWritten = 0; + BOOL success = WriteFile(mPipe, + &mWriteBuffer[mWriteBegin], + mWriteEnd - mWriteBegin, + &bytesWritten, + IsNonblocking() ? &mWriteOverlapped : nullptr); + + if (success) { + mWriteBegin += bytesWritten; + LOG_NPIO_DEBUG("[%s][%p] %d bytes written", __func__, this, bytesWritten); + return bytesWritten; + } + + if (GetLastError() != ERROR_IO_PENDING) { + LOG_NPIO_ERROR("[%s] WriteFile failed (%d)", __func__, GetLastError()); + Disconnect(); + PR_SetError(PR_IO_ERROR, 0); + return -1; + } + + mHasPendingWrite = true; + + return 0; +} + +int32_t +NamedPipeInfo::DoWriteContinue() +{ + MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); + MOZ_ASSERT(mHasPendingWrite); + + DWORD bytesWritten = 0; + BOOL success = GetOverlappedResult(mPipe, + &mWriteOverlapped, + &bytesWritten, + FALSE); + + if (!success) { + if (GetLastError() == ERROR_IO_INCOMPLETE) { + // still in progress + return 0; + } + + LOG_NPIO_ERROR("[%s] GetOverlappedResult failed (%d)", + __func__, + GetLastError()); + Disconnect(); + PR_SetError(PR_IO_ERROR, 0); + return -1; + } + + mHasPendingWrite = false; + mWriteBegin += bytesWritten; + LOG_NPIO_DEBUG("[%s][%p] %d bytes written", __func__, this, bytesWritten); + return bytesWritten; +} + +static inline NamedPipeInfo* +GetNamedPipeInfo(PRFileDesc* aFd) +{ + MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); + MOZ_DIAGNOSTIC_ASSERT(aFd); + MOZ_DIAGNOSTIC_ASSERT(aFd->secret); + MOZ_DIAGNOSTIC_ASSERT(PR_GetLayersIdentity(aFd) == nsNamedPipeLayerIdentity); + + if (!aFd || + !aFd->secret || + PR_GetLayersIdentity(aFd) != nsNamedPipeLayerIdentity) { + LOG_NPIO_ERROR("cannot get named pipe info"); + return nullptr; + } + + return reinterpret_cast<NamedPipeInfo*>(aFd->secret); +} + +static PRStatus +nsNamedPipeConnect(PRFileDesc* aFd, + const PRNetAddr* aAddr, + PRIntervalTime aTimeout) +{ + MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); + + NamedPipeInfo* info = GetNamedPipeInfo(aFd); + if (!info) { + PR_SetError(PR_BAD_DESCRIPTOR_ERROR, 0); + return PR_FAILURE; + } + + if (NS_WARN_IF(NS_FAILED(info->Connect( + nsDependentCString(aAddr->local.path))))) { + return PR_FAILURE; + } + + return PR_SUCCESS; +} + +static PRStatus +nsNamedPipeConnectContinue(PRFileDesc* aFd, PRInt16 aOutFlags) +{ + MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); + + return PR_SUCCESS; +} + +static PRStatus +nsNamedPipeClose(PRFileDesc* aFd) +{ + MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); + + if (aFd->secret && PR_GetLayersIdentity(aFd) == nsNamedPipeLayerIdentity) { + RefPtr<NamedPipeInfo> info = dont_AddRef(GetNamedPipeInfo(aFd)); + info->Disconnect(); + aFd->secret = nullptr; + aFd->identity = PR_INVALID_IO_LAYER; + } + + MOZ_ASSERT(!aFd->lower); + PR_DELETE(aFd); + + return PR_SUCCESS; +} + +static PRInt32 +nsNamedPipeSend(PRFileDesc* aFd, + const void* aBuffer, + PRInt32 aAmount, + PRIntn aFlags, + PRIntervalTime aTimeout) +{ + MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); + + Unused << aFlags; + Unused << aTimeout; + + NamedPipeInfo* info = GetNamedPipeInfo(aFd); + if (!info) { + PR_SetError(PR_BAD_DESCRIPTOR_ERROR, 0); + return -1; + } + return info->Write(aBuffer, aAmount); +} + +static PRInt32 +nsNamedPipeRecv(PRFileDesc* aFd, + void* aBuffer, + PRInt32 aAmount, + PRIntn aFlags, + PRIntervalTime aTimeout) +{ + MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); + + Unused << aTimeout; + + NamedPipeInfo* info = GetNamedPipeInfo(aFd); + if (!info) { + PR_SetError(PR_BAD_DESCRIPTOR_ERROR, 0); + return -1; + } + + if (aFlags) { + if (aFlags != PR_MSG_PEEK) { + PR_SetError(PR_UNKNOWN_ERROR, 0); + return -1; + } + return info->Peek(aBuffer, aAmount); + } + + return info->Read(aBuffer, aAmount); +} + +static inline PRInt32 +nsNamedPipeRead(PRFileDesc* aFd, void* aBuffer, PRInt32 aAmount) +{ + MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); + + NamedPipeInfo* info = GetNamedPipeInfo(aFd); + if (!info) { + PR_SetError(PR_BAD_DESCRIPTOR_ERROR, 0); + return -1; + } + return info->Read(aBuffer, aAmount); +} + +static inline PRInt32 +nsNamedPipeWrite(PRFileDesc* aFd, const void* aBuffer, PRInt32 aAmount) +{ + MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); + + NamedPipeInfo* info = GetNamedPipeInfo(aFd); + if (!info) { + PR_SetError(PR_BAD_DESCRIPTOR_ERROR, 0); + return -1; + } + return info->Write(aBuffer, aAmount); +} + +static PRInt32 +nsNamedPipeAvailable(PRFileDesc* aFd) +{ + MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); + + NamedPipeInfo* info = GetNamedPipeInfo(aFd); + if (!info) { + PR_SetError(PR_BAD_DESCRIPTOR_ERROR, 0); + return -1; + } + return static_cast<PRInt32>(info->Available()); +} + +static PRInt64 +nsNamedPipeAvailable64(PRFileDesc* aFd) +{ + MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); + + NamedPipeInfo* info = GetNamedPipeInfo(aFd); + if (!info) { + PR_SetError(PR_BAD_DESCRIPTOR_ERROR, 0); + return -1; + } + return static_cast<PRInt64>(info->Available()); +} + +static PRStatus +nsNamedPipeSync(PRFileDesc* aFd) +{ + MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); + + NamedPipeInfo* info = GetNamedPipeInfo(aFd); + if (!info) { + PR_SetError(PR_BAD_DESCRIPTOR_ERROR, 0); + return PR_FAILURE; + } + return info->Sync(0) ? PR_SUCCESS : PR_FAILURE; +} + +static PRInt16 +nsNamedPipePoll(PRFileDesc* aFd, PRInt16 aInFlags, PRInt16* aOutFlags) +{ + MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); + + NamedPipeInfo* info = GetNamedPipeInfo(aFd); + if (!info) { + PR_SetError(PR_BAD_DESCRIPTOR_ERROR, 0); + return 0; + } + return info->GetPollFlags(aInFlags, aOutFlags); +} + +// FIXME: remove socket option functions? +static PRStatus +nsNamedPipeGetSocketOption(PRFileDesc* aFd, PRSocketOptionData* aData) +{ + MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); + + MOZ_ASSERT(aFd); + MOZ_ASSERT(aData); + + switch (aData->option) { + case PR_SockOpt_Nonblocking: + aData->value.non_blocking = GetNamedPipeInfo(aFd)->IsNonblocking() + ? PR_TRUE + : PR_FALSE; + break; + case PR_SockOpt_Keepalive: + aData->value.keep_alive = PR_TRUE; + break; + case PR_SockOpt_NoDelay: + aData->value.no_delay = PR_TRUE; + break; + default: + PR_SetError(PR_INVALID_METHOD_ERROR, 0); + return PR_FAILURE; + } + + return PR_SUCCESS; +} + +static PRStatus +nsNamedPipeSetSocketOption(PRFileDesc* aFd, const PRSocketOptionData* aData) +{ + MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); + + MOZ_ASSERT(aFd); + MOZ_ASSERT(aData); + + switch (aData->option) { + case PR_SockOpt_Nonblocking: + GetNamedPipeInfo(aFd)->SetNonblocking(aData->value.non_blocking); + break; + case PR_SockOpt_Keepalive: + case PR_SockOpt_NoDelay: + break; + default: + PR_SetError(PR_INVALID_METHOD_ERROR, 0); + return PR_FAILURE; + } + + return PR_SUCCESS; +} + +static void +Initialize() +{ + MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); + + static bool initialized = false; + if (initialized) { + return; + } + + nsNamedPipeLayerIdentity = PR_GetUniqueIdentity("Named Pipe layer"); + nsNamedPipeLayerMethods = *PR_GetDefaultIOMethods(); + nsNamedPipeLayerMethods.close = nsNamedPipeClose; + nsNamedPipeLayerMethods.read = nsNamedPipeRead; + nsNamedPipeLayerMethods.write = nsNamedPipeWrite; + nsNamedPipeLayerMethods.available = nsNamedPipeAvailable; + nsNamedPipeLayerMethods.available64 = nsNamedPipeAvailable64; + nsNamedPipeLayerMethods.fsync = nsNamedPipeSync; + nsNamedPipeLayerMethods.connect = nsNamedPipeConnect; + nsNamedPipeLayerMethods.recv = nsNamedPipeRecv; + nsNamedPipeLayerMethods.send = nsNamedPipeSend; + nsNamedPipeLayerMethods.poll = nsNamedPipePoll; + nsNamedPipeLayerMethods.getsocketoption = nsNamedPipeGetSocketOption; + nsNamedPipeLayerMethods.setsocketoption = nsNamedPipeSetSocketOption; + nsNamedPipeLayerMethods.connectcontinue = nsNamedPipeConnectContinue; + + initialized = true; +} + +bool +IsNamedPipePath(const nsACString& aPath) +{ + return StringBeginsWith(aPath, NS_LITERAL_CSTRING("\\\\.\\pipe\\")); +} + +PRFileDesc* +CreateNamedPipeLayer() +{ + MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); + Initialize(); + + PRFileDesc* layer = PR_CreateIOLayerStub(nsNamedPipeLayerIdentity, + &nsNamedPipeLayerMethods); + if (NS_WARN_IF(!layer)) { + LOG_NPIO_ERROR("CreateNamedPipeLayer() failed."); + return nullptr; + } + + RefPtr<NamedPipeInfo> info = new NamedPipeInfo(); + layer->secret = reinterpret_cast<PRFilePrivate*>(info.forget().take()); + + return layer; +} + +} // namespace net +} // namespace mozilla diff --git a/netwerk/socket/nsNamedPipeIOLayer.h b/netwerk/socket/nsNamedPipeIOLayer.h new file mode 100644 index 0000000000..3c5a893d9d --- /dev/null +++ b/netwerk/socket/nsNamedPipeIOLayer.h @@ -0,0 +1,23 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#ifndef mozilla_netwerk_socket_nsNamedPipeIOLayer_h +#define mozilla_netwerk_socket_nsNamedPipeIOLayer_h + +#include "nscore.h" +#include "prio.h" + +namespace mozilla { +namespace net { + +bool IsNamedPipePath(const nsACString& aPath); +PRFileDesc* CreateNamedPipeLayer(); + +extern PRDescIdentity nsNamedPipeLayerIdentity; + +} // namespace net +} // namespace mozilla + +#endif // mozilla_netwerk_socket_nsNamedPipeIOLayer_h diff --git a/netwerk/socket/nsNamedPipeService.cpp b/netwerk/socket/nsNamedPipeService.cpp new file mode 100644 index 0000000000..8e72cc8ef0 --- /dev/null +++ b/netwerk/socket/nsNamedPipeService.cpp @@ -0,0 +1,324 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#include "mozilla/Services.h" +#include "nsCOMPtr.h" +#include "nsIObserverService.h" +#include "nsIThread.h" +#include "nsNamedPipeService.h" +#include "nsNetCID.h" +#include "nsThreadUtils.h" + +namespace mozilla { +namespace net { + +static mozilla::LazyLogModule gNamedPipeServiceLog("NamedPipeWin"); +#define LOG_NPS_DEBUG(...) \ + MOZ_LOG(gNamedPipeServiceLog, mozilla::LogLevel::Debug, (__VA_ARGS__)) +#define LOG_NPS_ERROR(...) \ + MOZ_LOG(gNamedPipeServiceLog, mozilla::LogLevel::Error, (__VA_ARGS__)) + +NS_IMPL_ISUPPORTS(NamedPipeService, + nsINamedPipeService, + nsIObserver, + nsIRunnable) + +NamedPipeService::NamedPipeService() + : mIocp(nullptr) + , mIsShutdown(false) + , mLock("NamedPipeServiceLock") +{ +} + +nsresult +NamedPipeService::Init() +{ + MOZ_ASSERT(!mIsShutdown); + + nsresult rv; + + // nsIObserverService must be accessed in main thread. + // register shutdown event to stop NamedPipeSrv thread. + nsCOMPtr<nsIObserver> self(this); + nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction([self = Move(self)] () -> void { + MOZ_ASSERT(NS_IsMainThread()); + + nsCOMPtr<nsIObserverService> svc = mozilla::services::GetObserverService(); + + if (NS_WARN_IF(!svc)) { + return; + } + + if (NS_WARN_IF(NS_FAILED(svc->AddObserver(self, + NS_XPCOM_SHUTDOWN_OBSERVER_ID, + false)))) { + return; + } + }); + + if (NS_IsMainThread()) { + rv = r->Run(); + } else { + rv = NS_DispatchToMainThread(r); + } + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + mIocp = CreateIoCompletionPort(INVALID_HANDLE_VALUE, nullptr, 0, 1); + if (NS_WARN_IF(!mIocp || mIocp == INVALID_HANDLE_VALUE)) { + Shutdown(); + return NS_ERROR_FAILURE; + } + + rv = NS_NewNamedThread("NamedPipeSrv", getter_AddRefs(mThread)); + if (NS_WARN_IF(NS_FAILED(rv))) { + Shutdown(); + return rv; + } + + return NS_OK; +} + +void +NamedPipeService::Shutdown() +{ + MOZ_ASSERT(NS_IsMainThread()); + + // remove observer + nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService(); + if (obs) { + obs->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID); + } + + // stop thread + if (mThread && !mIsShutdown) { + mIsShutdown = true; + + // invoke ERROR_ABANDONED_WAIT_0 to |GetQueuedCompletionStatus| + CloseHandle(mIocp); + mIocp = nullptr; + + mThread->Shutdown(); + } + + // close I/O Completion Port + if (mIocp && mIocp != INVALID_HANDLE_VALUE) { + CloseHandle(mIocp); + mIocp = nullptr; + } +} + +void +NamedPipeService::RemoveRetiredObjects() +{ + MOZ_ASSERT(NS_GetCurrentThread() == mThread); + mLock.AssertCurrentThreadOwns(); + + if (!mRetiredHandles.IsEmpty()) { + for (auto& handle : mRetiredHandles) { + CloseHandle(handle); + } + mRetiredHandles.Clear(); + } + + mRetiredObservers.Clear(); +} + +/** + * Implement nsINamedPipeService + */ + +NS_IMETHODIMP +NamedPipeService::AddDataObserver(void* aHandle, + nsINamedPipeDataObserver* aObserver) +{ + if (!aHandle || aHandle == INVALID_HANDLE_VALUE || !aObserver) { + return NS_ERROR_ILLEGAL_VALUE; + } + + nsresult rv; + + HANDLE h = CreateIoCompletionPort(aHandle, + mIocp, + reinterpret_cast<ULONG_PTR>(aObserver), + 1); + if (NS_WARN_IF(!h)) { + LOG_NPS_ERROR("CreateIoCompletionPort error (%d)", GetLastError()); + return NS_ERROR_FAILURE; + } + if (NS_WARN_IF(h != mIocp)) { + LOG_NPS_ERROR("CreateIoCompletionPort got unexpected value %p (should be %p)", + h, + mIocp); + CloseHandle(h); + return NS_ERROR_FAILURE; + } + + { + MutexAutoLock lock(mLock); + MOZ_ASSERT(!mObservers.Contains(aObserver)); + + mObservers.AppendElement(aObserver); + + // start event loop + if (mObservers.Length() == 1) { + rv = mThread->Dispatch(this, NS_DISPATCH_NORMAL); + if (NS_WARN_IF(NS_FAILED(rv))) { + LOG_NPS_ERROR("Dispatch to thread failed (%08x)", rv); + mObservers.Clear(); + return rv; + } + } + } + + return NS_OK; +} + +NS_IMETHODIMP +NamedPipeService::RemoveDataObserver(void* aHandle, + nsINamedPipeDataObserver* aObserver) +{ + MutexAutoLock lock(mLock); + mObservers.RemoveElement(aObserver); + + mRetiredHandles.AppendElement(aHandle); + mRetiredObservers.AppendElement(aObserver); + + return NS_OK; +} + +NS_IMETHODIMP +NamedPipeService::IsOnCurrentThread(bool* aRetVal) +{ + MOZ_ASSERT(mThread); + MOZ_ASSERT(aRetVal); + + if (!mThread) { + *aRetVal = false; + return NS_OK; + } + + return mThread->IsOnCurrentThread(aRetVal); +} + +/** + * Implement nsIObserver + */ + +NS_IMETHODIMP +NamedPipeService::Observe(nsISupports* aSubject, + const char* aTopic, + const char16_t* aData) +{ + MOZ_ASSERT(NS_IsMainThread()); + + if (!strcmp(NS_XPCOM_SHUTDOWN_OBSERVER_ID, aTopic)) { + Shutdown(); + } + + return NS_OK; +} + +/** + * Implement nsIRunnable + */ + +NS_IMETHODIMP +NamedPipeService::Run() +{ + MOZ_ASSERT(NS_GetCurrentThread() == mThread); + MOZ_ASSERT(mIocp && mIocp != INVALID_HANDLE_VALUE); + + while (!mIsShutdown) { + { + MutexAutoLock lock(mLock); + if (mObservers.IsEmpty()) { + LOG_NPS_DEBUG("no observer, stop loop"); + break; + } + + RemoveRetiredObjects(); + } + + DWORD bytesTransferred = 0; + ULONG_PTR key = 0; + LPOVERLAPPED overlapped = nullptr; + BOOL success = GetQueuedCompletionStatus(mIocp, + &bytesTransferred, + &key, + &overlapped, + 1000); // timeout, 1s + auto err = GetLastError(); + if (!success) { + if (err == WAIT_TIMEOUT) { + continue; + } else if (err == ERROR_ABANDONED_WAIT_0) { // mIocp was closed + break; + } else if (!overlapped) { + /** + * Did not dequeue a completion packet from the completion port, and + * bytesTransferred/key are meaningless. + * See remarks of |GetQueuedCompletionStatus| API. + */ + + LOG_NPS_ERROR("invalid overlapped (%d)", err); + continue; + } + + MOZ_ASSERT(key); + } + + /** + * Windows doesn't provide a method to remove created I/O Completion Port, + * all we can do is just close the handle we monitored before. + * In some cases, there's race condition that the monitored handle has an + * I/O status after the observer is being removed and destroyed. + * To avoid changing the ref-count of a dangling pointer, don't use nsCOMPtr + * here. + */ + nsINamedPipeDataObserver* target = + reinterpret_cast<nsINamedPipeDataObserver*>(key); + + nsCOMPtr<nsINamedPipeDataObserver> obs; + { + MutexAutoLock lock(mLock); + + auto idx = mObservers.IndexOf(target); + if (idx == decltype(mObservers)::NoIndex) { + LOG_NPS_ERROR("observer %p not found", target); + continue; + } + obs = target; + } + + MOZ_ASSERT(obs.get()); + + if (success) { + LOG_NPS_DEBUG("OnDataAvailable: obs=%p, bytes=%d", + obs.get(), + bytesTransferred); + obs->OnDataAvailable(bytesTransferred, overlapped); + } else { + LOG_NPS_ERROR("GetQueuedCompletionStatus %p failed, error=%d", + obs.get(), + err); + obs->OnError(err, overlapped); + } + + } + + { + MutexAutoLock lock(mLock); + RemoveRetiredObjects(); + } + + return NS_OK; +} + +static NS_DEFINE_CID(kNamedPipeServiceCID, NS_NAMEDPIPESERVICE_CID); + +} // namespace net +} // namespace mozilla diff --git a/netwerk/socket/nsNamedPipeService.h b/netwerk/socket/nsNamedPipeService.h new file mode 100644 index 0000000000..1b4f97cacb --- /dev/null +++ b/netwerk/socket/nsNamedPipeService.h @@ -0,0 +1,59 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#ifndef mozilla_netwerk_socket_nsNamedPipeService_h +#define mozilla_netwerk_socket_nsNamedPipeService_h + +#include <windows.h> +#include "mozilla/Atomics.h" +#include "mozilla/Mutex.h" +#include "nsINamedPipeService.h" +#include "nsIObserver.h" +#include "nsIRunnable.h" +#include "nsTArray.h" + +namespace mozilla { +namespace net { + +class NamedPipeService final : public nsINamedPipeService + , public nsIObserver + , public nsIRunnable +{ +public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSINAMEDPIPESERVICE + NS_DECL_NSIOBSERVER + NS_DECL_NSIRUNNABLE + + explicit NamedPipeService(); + + nsresult Init(); + +private: + virtual ~NamedPipeService() = default; + void Shutdown(); + void RemoveRetiredObjects(); + + HANDLE mIocp; // native handle to the I/O completion port. + Atomic<bool> mIsShutdown; // set to true to stop the event loop running by mThread. + nsCOMPtr<nsIThread> mThread; // worker thread to get I/O events. + + /** + * The observers is maintained in |mObservers| to ensure valid life-cycle. + * We don't remove the handle and corresponding observer directly, instead + * the handle and observer into a "retired" list and close/remove them in + * the worker thread to avoid a race condition that might happen between + * |CloseHandle()| and |GetQueuedCompletionStatus()|. + */ + Mutex mLock; + nsTArray<nsCOMPtr<nsINamedPipeDataObserver>> mObservers; // protected by mLock + nsTArray<nsCOMPtr<nsINamedPipeDataObserver>> mRetiredObservers; // protected by mLock + nsTArray<HANDLE> mRetiredHandles; // protected by mLock +}; + +} // namespace net +} // namespace mozilla + +#endif // mozilla_netwerk_socket_nsNamedPipeService_h diff --git a/netwerk/socket/nsSOCKSIOLayer.cpp b/netwerk/socket/nsSOCKSIOLayer.cpp new file mode 100644 index 0000000000..22f5751fbe --- /dev/null +++ b/netwerk/socket/nsSOCKSIOLayer.cpp @@ -0,0 +1,1612 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* vim:set expandtab ts=4 sw=4 sts=4 cin: */ +/* 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/. */ + +#include "nspr.h" +#include "private/pprio.h" +#include "nsString.h" +#include "nsCRT.h" + +#include "nsIServiceManager.h" +#include "nsIDNSService.h" +#include "nsIDNSRecord.h" +#include "nsISOCKSSocketInfo.h" +#include "nsISocketProvider.h" +#include "nsNamedPipeIOLayer.h" +#include "nsSOCKSIOLayer.h" +#include "nsNetCID.h" +#include "nsIDNSListener.h" +#include "nsICancelable.h" +#include "nsThreadUtils.h" +#include "nsIFile.h" +#include "nsIFileProtocolHandler.h" +#include "mozilla/Logging.h" +#include "mozilla/net/DNS.h" +#include "mozilla/Unused.h" + +using mozilla::LogLevel; +using namespace mozilla::net; + +static PRDescIdentity nsSOCKSIOLayerIdentity; +static PRIOMethods nsSOCKSIOLayerMethods; +static bool firstTime = true; +static bool ipv6Supported = true; + + +static mozilla::LazyLogModule gSOCKSLog("SOCKS"); +#define LOGDEBUG(args) MOZ_LOG(gSOCKSLog, mozilla::LogLevel::Debug, args) +#define LOGERROR(args) MOZ_LOG(gSOCKSLog, mozilla::LogLevel::Error , args) + +class nsSOCKSSocketInfo : public nsISOCKSSocketInfo + , public nsIDNSListener +{ + enum State { + SOCKS_INITIAL, + SOCKS_DNS_IN_PROGRESS, + SOCKS_DNS_COMPLETE, + SOCKS_CONNECTING_TO_PROXY, + SOCKS4_WRITE_CONNECT_REQUEST, + SOCKS4_READ_CONNECT_RESPONSE, + SOCKS5_WRITE_AUTH_REQUEST, + SOCKS5_READ_AUTH_RESPONSE, + SOCKS5_WRITE_USERNAME_REQUEST, + SOCKS5_READ_USERNAME_RESPONSE, + SOCKS5_WRITE_CONNECT_REQUEST, + SOCKS5_READ_CONNECT_RESPONSE_TOP, + SOCKS5_READ_CONNECT_RESPONSE_BOTTOM, + SOCKS_CONNECTED, + SOCKS_FAILED + }; + + // A buffer of 520 bytes should be enough for any request and response + // in case of SOCKS4 as well as SOCKS5 + static const uint32_t BUFFER_SIZE = 520; + static const uint32_t MAX_HOSTNAME_LEN = 255; + static const uint32_t MAX_USERNAME_LEN = 255; + static const uint32_t MAX_PASSWORD_LEN = 255; + +public: + nsSOCKSSocketInfo(); + + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSISOCKSSOCKETINFO + NS_DECL_NSIDNSLISTENER + + void Init(int32_t version, + int32_t family, + nsIProxyInfo *proxy, + const char *destinationHost, + uint32_t flags); + + void SetConnectTimeout(PRIntervalTime to); + PRStatus DoHandshake(PRFileDesc *fd, int16_t oflags = -1); + int16_t GetPollFlags() const; + bool IsConnected() const { return mState == SOCKS_CONNECTED; } + void ForgetFD() { mFD = nullptr; } + void SetNamedPipeFD(PRFileDesc *fd) { mFD = fd; } + +private: + virtual ~nsSOCKSSocketInfo() + { + ForgetFD(); + HandshakeFinished(); + } + + void HandshakeFinished(PRErrorCode err = 0); + PRStatus StartDNS(PRFileDesc *fd); + PRStatus ConnectToProxy(PRFileDesc *fd); + void FixupAddressFamily(PRFileDesc *fd, NetAddr *proxy); + PRStatus ContinueConnectingToProxy(PRFileDesc *fd, int16_t oflags); + PRStatus WriteV4ConnectRequest(); + PRStatus ReadV4ConnectResponse(); + PRStatus WriteV5AuthRequest(); + PRStatus ReadV5AuthResponse(); + PRStatus WriteV5UsernameRequest(); + PRStatus ReadV5UsernameResponse(); + PRStatus WriteV5ConnectRequest(); + PRStatus ReadV5AddrTypeAndLength(uint8_t *type, uint32_t *len); + PRStatus ReadV5ConnectResponseTop(); + PRStatus ReadV5ConnectResponseBottom(); + + uint8_t ReadUint8(); + uint16_t ReadUint16(); + uint32_t ReadUint32(); + void ReadNetAddr(NetAddr *addr, uint16_t fam); + void ReadNetPort(NetAddr *addr); + + void WantRead(uint32_t sz); + PRStatus ReadFromSocket(PRFileDesc *fd); + PRStatus WriteToSocket(PRFileDesc *fd); + + bool IsLocalProxy() + { + nsAutoCString proxyHost; + mProxy->GetHost(proxyHost); + return IsHostLocalTarget(proxyHost); + } + + nsresult SetLocalProxyPath(const nsACString& aLocalProxyPath, + NetAddr* aProxyAddr) + { +#ifdef XP_UNIX + nsresult rv; + MOZ_ASSERT(aProxyAddr); + + nsCOMPtr<nsIProtocolHandler> protocolHandler( + do_GetService(NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX "file", &rv)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + nsCOMPtr<nsIFileProtocolHandler> fileHandler( + do_QueryInterface(protocolHandler, &rv)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + nsCOMPtr<nsIFile> socketFile; + rv = fileHandler->GetFileFromURLSpec(aLocalProxyPath, + getter_AddRefs(socketFile)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + nsAutoCString path; + if (NS_WARN_IF(NS_FAILED(rv = socketFile->GetNativePath(path)))) { + return rv; + } + + if (sizeof(aProxyAddr->local.path) <= path.Length()) { + NS_WARNING("domain socket path too long."); + return NS_ERROR_FAILURE; + } + + aProxyAddr->raw.family = AF_UNIX; + strcpy(aProxyAddr->local.path, path.get()); + + return NS_OK; +#elif defined(XP_WIN) + MOZ_ASSERT(aProxyAddr); + + if (sizeof(aProxyAddr->local.path) <= aLocalProxyPath.Length()) { + NS_WARNING("pipe path too long."); + return NS_ERROR_FAILURE; + } + + aProxyAddr->raw.family = AF_LOCAL; + strcpy(aProxyAddr->local.path, PromiseFlatCString(aLocalProxyPath).get()); + return NS_OK; +#else + mozilla::Unused << aLocalProxyPath; + mozilla::Unused << aProxyAddr; + return NS_ERROR_NOT_IMPLEMENTED; +#endif + } + + bool + SetupNamedPipeLayer(PRFileDesc *fd) + { +#if defined(XP_WIN) + if (IsLocalProxy()) { + // nsSOCKSIOLayer handshaking only works under blocking mode + // unfortunately. Remember named pipe's FD to switch between modes. + SetNamedPipeFD(fd->lower); + return true; + } +#endif + return false; + } + +private: + State mState; + uint8_t * mData; + uint8_t * mDataIoPtr; + uint32_t mDataLength; + uint32_t mReadOffset; + uint32_t mAmountToRead; + nsCOMPtr<nsIDNSRecord> mDnsRec; + nsCOMPtr<nsICancelable> mLookup; + nsresult mLookupStatus; + PRFileDesc *mFD; + + nsCString mDestinationHost; + nsCOMPtr<nsIProxyInfo> mProxy; + int32_t mVersion; // SOCKS version 4 or 5 + int32_t mDestinationFamily; + uint32_t mFlags; + NetAddr mInternalProxyAddr; + NetAddr mExternalProxyAddr; + NetAddr mDestinationAddr; + PRIntervalTime mTimeout; + nsCString mProxyUsername; // Cache, from mProxy +}; + +nsSOCKSSocketInfo::nsSOCKSSocketInfo() + : mState(SOCKS_INITIAL) + , mDataIoPtr(nullptr) + , mDataLength(0) + , mReadOffset(0) + , mAmountToRead(0) + , mVersion(-1) + , mDestinationFamily(AF_INET) + , mFlags(0) + , mTimeout(PR_INTERVAL_NO_TIMEOUT) +{ + mData = new uint8_t[BUFFER_SIZE]; + + mInternalProxyAddr.raw.family = AF_INET; + mInternalProxyAddr.inet.ip = htonl(INADDR_ANY); + mInternalProxyAddr.inet.port = htons(0); + + mExternalProxyAddr.raw.family = AF_INET; + mExternalProxyAddr.inet.ip = htonl(INADDR_ANY); + mExternalProxyAddr.inet.port = htons(0); + + mDestinationAddr.raw.family = AF_INET; + mDestinationAddr.inet.ip = htonl(INADDR_ANY); + mDestinationAddr.inet.port = htons(0); +} + +/* Helper template class to statically check that writes to a fixed-size + * buffer are not going to overflow. + * + * Example usage: + * uint8_t real_buf[TOTAL_SIZE]; + * Buffer<TOTAL_SIZE> buf(&real_buf); + * auto buf2 = buf.WriteUint16(1); + * auto buf3 = buf2.WriteUint8(2); + * + * It is possible to chain them, to limit the number of (error-prone) + * intermediate variables: + * auto buf = Buffer<TOTAL_SIZE>(&real_buf) + * .WriteUint16(1) + * .WriteUint8(2); + * + * Debug builds assert when intermediate variables are reused: + * Buffer<TOTAL_SIZE> buf(&real_buf); + * auto buf2 = buf.WriteUint16(1); + * auto buf3 = buf.WriteUint8(2); // Asserts + * + * Strings can be written, given an explicit maximum length. + * buf.WriteString<MAX_STRING_LENGTH>(str); + * + * The Written() method returns how many bytes have been written so far: + * Buffer<TOTAL_SIZE> buf(&real_buf); + * auto buf2 = buf.WriteUint16(1); + * auto buf3 = buf2.WriteUint8(2); + * buf3.Written(); // returns 3. + */ +template <size_t Size> +class Buffer +{ +public: + Buffer() : mBuf(nullptr), mLength(0) {} + + explicit Buffer(uint8_t* aBuf, size_t aLength=0) + : mBuf(aBuf), mLength(aLength) {} + + template <size_t Size2> + MOZ_IMPLICIT Buffer(const Buffer<Size2>& aBuf) : mBuf(aBuf.mBuf), mLength(aBuf.mLength) { + static_assert(Size2 > Size, "Cannot cast buffer"); + } + + Buffer<Size - sizeof(uint8_t)> WriteUint8(uint8_t aValue) { + return Write(aValue); + } + + Buffer<Size - sizeof(uint16_t)> WriteUint16(uint16_t aValue) { + return Write(aValue); + } + + Buffer<Size - sizeof(uint32_t)> WriteUint32(uint32_t aValue) { + return Write(aValue); + } + + Buffer<Size - sizeof(uint16_t)> WriteNetPort(const NetAddr* aAddr) { + return WriteUint16(aAddr->inet.port); + } + + Buffer<Size - sizeof(IPv6Addr)> WriteNetAddr(const NetAddr* aAddr) { + if (aAddr->raw.family == AF_INET) { + return Write(aAddr->inet.ip); + } else if (aAddr->raw.family == AF_INET6) { + return Write(aAddr->inet6.ip.u8); + } + NS_NOTREACHED("Unknown address family"); + return *this; + } + + template <size_t MaxLength> + Buffer<Size - MaxLength> WriteString(const nsACString& aStr) { + if (aStr.Length() > MaxLength) { + return Buffer<Size - MaxLength>(nullptr); + } + return WritePtr<char, MaxLength>(aStr.Data(), aStr.Length()); + } + + size_t Written() { + MOZ_ASSERT(mBuf); + return mLength; + } + + explicit operator bool() { return !!mBuf; } +private: + template <size_t Size2> + friend class Buffer; + + template <typename T> + Buffer<Size - sizeof(T)> Write(T& aValue) { + return WritePtr<T, sizeof(T)>(&aValue, sizeof(T)); + } + + template <typename T, size_t Length> + Buffer<Size - Length> WritePtr(const T* aValue, size_t aCopyLength) { + static_assert(Size >= Length, "Cannot write that much"); + MOZ_ASSERT(aCopyLength <= Length); + MOZ_ASSERT(mBuf); + memcpy(mBuf, aValue, aCopyLength); + Buffer<Size - Length> result(mBuf + aCopyLength, mLength + aCopyLength); + mBuf = nullptr; + mLength = 0; + return result; + } + + uint8_t* mBuf; + size_t mLength; +}; + + +void +nsSOCKSSocketInfo::Init(int32_t version, int32_t family, nsIProxyInfo *proxy, const char *host, uint32_t flags) +{ + mVersion = version; + mDestinationFamily = family; + mProxy = proxy; + mDestinationHost = host; + mFlags = flags; + mProxy->GetUsername(mProxyUsername); // cache +} + +NS_IMPL_ISUPPORTS(nsSOCKSSocketInfo, nsISOCKSSocketInfo, nsIDNSListener) + +NS_IMETHODIMP +nsSOCKSSocketInfo::GetExternalProxyAddr(NetAddr * *aExternalProxyAddr) +{ + memcpy(*aExternalProxyAddr, &mExternalProxyAddr, sizeof(NetAddr)); + return NS_OK; +} + +NS_IMETHODIMP +nsSOCKSSocketInfo::SetExternalProxyAddr(NetAddr *aExternalProxyAddr) +{ + memcpy(&mExternalProxyAddr, aExternalProxyAddr, sizeof(NetAddr)); + return NS_OK; +} + +NS_IMETHODIMP +nsSOCKSSocketInfo::GetDestinationAddr(NetAddr * *aDestinationAddr) +{ + memcpy(*aDestinationAddr, &mDestinationAddr, sizeof(NetAddr)); + return NS_OK; +} + +NS_IMETHODIMP +nsSOCKSSocketInfo::SetDestinationAddr(NetAddr *aDestinationAddr) +{ + memcpy(&mDestinationAddr, aDestinationAddr, sizeof(NetAddr)); + return NS_OK; +} + +NS_IMETHODIMP +nsSOCKSSocketInfo::GetInternalProxyAddr(NetAddr * *aInternalProxyAddr) +{ + memcpy(*aInternalProxyAddr, &mInternalProxyAddr, sizeof(NetAddr)); + return NS_OK; +} + +NS_IMETHODIMP +nsSOCKSSocketInfo::SetInternalProxyAddr(NetAddr *aInternalProxyAddr) +{ + memcpy(&mInternalProxyAddr, aInternalProxyAddr, sizeof(NetAddr)); + return NS_OK; +} + +// There needs to be a means of distinguishing between connection errors +// that the SOCKS server reports when it rejects a connection request, and +// connection errors that happen while attempting to connect to the SOCKS +// server. Otherwise, Firefox will report incorrectly that the proxy server +// is refusing connections when a SOCKS request is rejected by the proxy. +// When a SOCKS handshake failure occurs, the PR error is set to +// PR_UNKNOWN_ERROR, and the real error code is returned via the OS error. +void +nsSOCKSSocketInfo::HandshakeFinished(PRErrorCode err) +{ + if (err == 0) { + mState = SOCKS_CONNECTED; +#if defined(XP_WIN) + // Switch back to nonblocking mode after finishing handshaking. + if (IsLocalProxy() && mFD) { + PRSocketOptionData opt_nonblock; + opt_nonblock.option = PR_SockOpt_Nonblocking; + opt_nonblock.value.non_blocking = PR_TRUE; + PR_SetSocketOption(mFD, &opt_nonblock); + mFD = nullptr; + } +#endif + } else { + mState = SOCKS_FAILED; + PR_SetError(PR_UNKNOWN_ERROR, err); + } + + // We don't need the buffer any longer, so free it. + delete [] mData; + mData = nullptr; + mDataIoPtr = nullptr; + mDataLength = 0; + mReadOffset = 0; + mAmountToRead = 0; + if (mLookup) { + mLookup->Cancel(NS_ERROR_FAILURE); + mLookup = nullptr; + } +} + +PRStatus +nsSOCKSSocketInfo::StartDNS(PRFileDesc *fd) +{ + MOZ_ASSERT(!mDnsRec && mState == SOCKS_INITIAL, + "Must be in initial state to make DNS Lookup"); + + nsCOMPtr<nsIDNSService> dns = do_GetService(NS_DNSSERVICE_CONTRACTID); + if (!dns) + return PR_FAILURE; + + nsCString proxyHost; + mProxy->GetHost(proxyHost); + + mFD = fd; + nsresult rv = dns->AsyncResolve(proxyHost, 0, this, + NS_GetCurrentThread(), + getter_AddRefs(mLookup)); + + if (NS_FAILED(rv)) { + LOGERROR(("socks: DNS lookup for SOCKS proxy %s failed", + proxyHost.get())); + return PR_FAILURE; + } + mState = SOCKS_DNS_IN_PROGRESS; + PR_SetError(PR_IN_PROGRESS_ERROR, 0); + return PR_FAILURE; +} + +NS_IMETHODIMP +nsSOCKSSocketInfo::OnLookupComplete(nsICancelable *aRequest, + nsIDNSRecord *aRecord, + nsresult aStatus) +{ + MOZ_ASSERT(aRequest == mLookup, "wrong DNS query"); + mLookup = nullptr; + mLookupStatus = aStatus; + mDnsRec = aRecord; + mState = SOCKS_DNS_COMPLETE; + if (mFD) { + ConnectToProxy(mFD); + ForgetFD(); + } + return NS_OK; +} + +PRStatus +nsSOCKSSocketInfo::ConnectToProxy(PRFileDesc *fd) +{ + PRStatus status; + nsresult rv; + + MOZ_ASSERT(mState == SOCKS_DNS_COMPLETE, + "Must have DNS to make connection!"); + + if (NS_FAILED(mLookupStatus)) { + PR_SetError(PR_BAD_ADDRESS_ERROR, 0); + return PR_FAILURE; + } + + // Try socks5 if the destination addrress is IPv6 + if (mVersion == 4 && + mDestinationAddr.raw.family == AF_INET6) { + mVersion = 5; + } + + nsAutoCString proxyHost; + mProxy->GetHost(proxyHost); + + int32_t proxyPort; + mProxy->GetPort(&proxyPort); + + int32_t addresses = 0; + do { + if (IsLocalProxy()) { + rv = SetLocalProxyPath(proxyHost, &mInternalProxyAddr); + if (NS_FAILED(rv)) { + LOGERROR(("socks: unable to connect to SOCKS proxy, %s", + proxyHost.get())); + return PR_FAILURE; + } + } else { + if (addresses++) { + mDnsRec->ReportUnusable(proxyPort); + } + + rv = mDnsRec->GetNextAddr(proxyPort, &mInternalProxyAddr); + // No more addresses to try? If so, we'll need to bail + if (NS_FAILED(rv)) { + LOGERROR(("socks: unable to connect to SOCKS proxy, %s", + proxyHost.get())); + return PR_FAILURE; + } + + if (MOZ_LOG_TEST(gSOCKSLog, LogLevel::Debug)) { + char buf[kIPv6CStrBufSize]; + NetAddrToString(&mInternalProxyAddr, buf, sizeof(buf)); + LOGDEBUG(("socks: trying proxy server, %s:%hu", + buf, ntohs(mInternalProxyAddr.inet.port))); + } + } + + NetAddr proxy = mInternalProxyAddr; + FixupAddressFamily(fd, &proxy); + PRNetAddr prProxy; + NetAddrToPRNetAddr(&proxy, &prProxy); + status = fd->lower->methods->connect(fd->lower, &prProxy, mTimeout); + if (status != PR_SUCCESS) { + PRErrorCode c = PR_GetError(); + + // If EINPROGRESS, return now and check back later after polling + if (c == PR_WOULD_BLOCK_ERROR || c == PR_IN_PROGRESS_ERROR) { + mState = SOCKS_CONNECTING_TO_PROXY; + return status; + } else if (IsLocalProxy()) { + LOGERROR(("socks: connect to domain socket failed (%d)", c)); + PR_SetError(PR_CONNECT_REFUSED_ERROR, 0); + mState = SOCKS_FAILED; + return status; + } + } + } while (status != PR_SUCCESS); + +#if defined(XP_WIN) + // Switch to blocking mode during handshaking + if (IsLocalProxy() && mFD) { + PRSocketOptionData opt_nonblock; + opt_nonblock.option = PR_SockOpt_Nonblocking; + opt_nonblock.value.non_blocking = PR_FALSE; + PR_SetSocketOption(mFD, &opt_nonblock); + } +#endif + + // Connected now, start SOCKS + if (mVersion == 4) + return WriteV4ConnectRequest(); + return WriteV5AuthRequest(); +} + +void +nsSOCKSSocketInfo::FixupAddressFamily(PRFileDesc *fd, NetAddr *proxy) +{ + int32_t proxyFamily = mInternalProxyAddr.raw.family; + // Do nothing if the address family is already matched + if (proxyFamily == mDestinationFamily) { + return; + } + // If the system does not support IPv6 and the proxy address is IPv6, + // We can do nothing here. + if (proxyFamily == AF_INET6 && !ipv6Supported) { + return; + } + // If the system does not support IPv6 and the destination address is + // IPv6, convert IPv4 address to IPv4-mapped IPv6 address to satisfy + // the emulation layer + if (mDestinationFamily == AF_INET6 && !ipv6Supported) { + proxy->inet6.family = AF_INET6; + proxy->inet6.port = mInternalProxyAddr.inet.port; + uint8_t *proxyp = proxy->inet6.ip.u8; + memset(proxyp, 0, 10); + memset(proxyp + 10, 0xff, 2); + memcpy(proxyp + 12,(char *) &mInternalProxyAddr.inet.ip, 4); + // mDestinationFamily should not be updated + return; + } + // There's no PR_NSPR_IO_LAYER required when using named pipe, + // we simply ignore the TCP family here. + if (SetupNamedPipeLayer(fd)) { + return; + } + + // Get an OS native handle from a specified FileDesc + PROsfd osfd = PR_FileDesc2NativeHandle(fd); + if (osfd == -1) { + return; + } + + // Create a new FileDesc with a specified family + PRFileDesc *tmpfd = PR_OpenTCPSocket(proxyFamily); + if (!tmpfd) { + return; + } + PROsfd newsd = PR_FileDesc2NativeHandle(tmpfd); + if (newsd == -1) { + PR_Close(tmpfd); + return; + } + // Must succeed because PR_FileDesc2NativeHandle succeeded + fd = PR_GetIdentitiesLayer(fd, PR_NSPR_IO_LAYER); + MOZ_ASSERT(fd); + // Swap OS native handles + PR_ChangeFileDescNativeHandle(fd, newsd); + PR_ChangeFileDescNativeHandle(tmpfd, osfd); + // Close temporary FileDesc which is now associated with + // old OS native handle + PR_Close(tmpfd); + mDestinationFamily = proxyFamily; +} + +PRStatus +nsSOCKSSocketInfo::ContinueConnectingToProxy(PRFileDesc *fd, int16_t oflags) +{ + PRStatus status; + + MOZ_ASSERT(mState == SOCKS_CONNECTING_TO_PROXY, + "Continuing connection in wrong state!"); + + LOGDEBUG(("socks: continuing connection to proxy")); + + status = fd->lower->methods->connectcontinue(fd->lower, oflags); + if (status != PR_SUCCESS) { + PRErrorCode c = PR_GetError(); + if (c != PR_WOULD_BLOCK_ERROR && c != PR_IN_PROGRESS_ERROR) { + // A connection failure occured, try another address + mState = SOCKS_DNS_COMPLETE; + return ConnectToProxy(fd); + } + + // We're still connecting + return PR_FAILURE; + } + + // Connected now, start SOCKS + if (mVersion == 4) + return WriteV4ConnectRequest(); + return WriteV5AuthRequest(); +} + +PRStatus +nsSOCKSSocketInfo::WriteV4ConnectRequest() +{ + if (mProxyUsername.Length() > MAX_USERNAME_LEN) { + LOGERROR(("socks username is too long")); + HandshakeFinished(PR_UNKNOWN_ERROR); + return PR_FAILURE; + } + + NetAddr *addr = &mDestinationAddr; + int32_t proxy_resolve; + + MOZ_ASSERT(mState == SOCKS_CONNECTING_TO_PROXY, + "Invalid state!"); + + proxy_resolve = mFlags & nsISocketProvider::PROXY_RESOLVES_HOST; + + mDataLength = 0; + mState = SOCKS4_WRITE_CONNECT_REQUEST; + + LOGDEBUG(("socks4: sending connection request (socks4a resolve? %s)", + proxy_resolve? "yes" : "no")); + + // Send a SOCKS 4 connect request. + auto buf = Buffer<BUFFER_SIZE>(mData) + .WriteUint8(0x04) // version -- 4 + .WriteUint8(0x01) // command -- connect + .WriteNetPort(addr); + + // We don't have anything more to write after the if, so we can + // use a buffer with no further writes allowed. + Buffer<0> buf3; + if (proxy_resolve) { + // Add the full name, null-terminated, to the request + // according to SOCKS 4a. A fake IP address, with the first + // four bytes set to 0 and the last byte set to something other + // than 0, is used to notify the proxy that this is a SOCKS 4a + // request. This request type works for Tor and perhaps others. + // Passwords not supported by V4. + auto buf2 = buf.WriteUint32(htonl(0x00000001)) // Fake IP + .WriteString<MAX_USERNAME_LEN>(mProxyUsername) + .WriteUint8(0x00) // Null-terminate username + .WriteString<MAX_HOSTNAME_LEN>(mDestinationHost); // Hostname + if (!buf2) { + LOGERROR(("socks4: destination host name is too long!")); + HandshakeFinished(PR_BAD_ADDRESS_ERROR); + return PR_FAILURE; + } + buf3 = buf2.WriteUint8(0x00); + } else if (addr->raw.family == AF_INET) { + // Passwords not supported by V4. + buf3 = buf.WriteNetAddr(addr) // Add the IPv4 address + .WriteString<MAX_USERNAME_LEN>(mProxyUsername) + .WriteUint8(0x00); // Null-terminate username + } else { + LOGERROR(("socks: SOCKS 4 can only handle IPv4 addresses!")); + HandshakeFinished(PR_BAD_ADDRESS_ERROR); + return PR_FAILURE; + } + + mDataLength = buf3.Written(); + return PR_SUCCESS; +} + +PRStatus +nsSOCKSSocketInfo::ReadV4ConnectResponse() +{ + MOZ_ASSERT(mState == SOCKS4_READ_CONNECT_RESPONSE, + "Handling SOCKS 4 connection reply in wrong state!"); + MOZ_ASSERT(mDataLength == 8, + "SOCKS 4 connection reply must be 8 bytes!"); + + LOGDEBUG(("socks4: checking connection reply")); + + if (ReadUint8() != 0x00) { + LOGERROR(("socks4: wrong connection reply")); + HandshakeFinished(PR_CONNECT_REFUSED_ERROR); + return PR_FAILURE; + } + + // See if our connection request was granted + if (ReadUint8() == 90) { + LOGDEBUG(("socks4: connection successful!")); + HandshakeFinished(); + return PR_SUCCESS; + } + + LOGERROR(("socks4: unable to connect")); + HandshakeFinished(PR_CONNECT_REFUSED_ERROR); + return PR_FAILURE; +} + +PRStatus +nsSOCKSSocketInfo::WriteV5AuthRequest() +{ + MOZ_ASSERT(mVersion == 5, "SOCKS version must be 5!"); + + mDataLength = 0; + mState = SOCKS5_WRITE_AUTH_REQUEST; + + // Send an initial SOCKS 5 greeting + LOGDEBUG(("socks5: sending auth methods")); + mDataLength = Buffer<BUFFER_SIZE>(mData) + .WriteUint8(0x05) // version -- 5 + .WriteUint8(0x01) // # of auth methods -- 1 + // Use authenticate iff we have a proxy username. + .WriteUint8(mProxyUsername.IsEmpty() ? 0x00 : 0x02) + .Written(); + + return PR_SUCCESS; +} + +PRStatus +nsSOCKSSocketInfo::ReadV5AuthResponse() +{ + MOZ_ASSERT(mState == SOCKS5_READ_AUTH_RESPONSE, + "Handling SOCKS 5 auth method reply in wrong state!"); + MOZ_ASSERT(mDataLength == 2, + "SOCKS 5 auth method reply must be 2 bytes!"); + + LOGDEBUG(("socks5: checking auth method reply")); + + // Check version number + if (ReadUint8() != 0x05) { + LOGERROR(("socks5: unexpected version in the reply")); + HandshakeFinished(PR_CONNECT_REFUSED_ERROR); + return PR_FAILURE; + } + + // Make sure our authentication choice was accepted, + // and continue accordingly + uint8_t authMethod = ReadUint8(); + if (mProxyUsername.IsEmpty() && authMethod == 0x00) { // no auth + LOGDEBUG(("socks5: server allows connection without authentication")); + return WriteV5ConnectRequest(); + } else if (!mProxyUsername.IsEmpty() && authMethod == 0x02) { // username/pw + LOGDEBUG(("socks5: auth method accepted by server")); + return WriteV5UsernameRequest(); + } else { // 0xFF signals error + LOGERROR(("socks5: server did not accept our authentication method")); + HandshakeFinished(PR_CONNECT_REFUSED_ERROR); + return PR_FAILURE; + } +} + +PRStatus +nsSOCKSSocketInfo::WriteV5UsernameRequest() +{ + MOZ_ASSERT(mVersion == 5, "SOCKS version must be 5!"); + + if (mProxyUsername.Length() > MAX_USERNAME_LEN) { + LOGERROR(("socks username is too long")); + HandshakeFinished(PR_UNKNOWN_ERROR); + return PR_FAILURE; + } + + nsCString password; + mProxy->GetPassword(password); + if (password.Length() > MAX_PASSWORD_LEN) { + LOGERROR(("socks password is too long")); + HandshakeFinished(PR_UNKNOWN_ERROR); + return PR_FAILURE; + } + + mDataLength = 0; + mState = SOCKS5_WRITE_USERNAME_REQUEST; + + // RFC 1929 Username/password auth for SOCKS 5 + LOGDEBUG(("socks5: sending username and password")); + mDataLength = Buffer<BUFFER_SIZE>(mData) + .WriteUint8(0x01) // version 1 (not 5) + .WriteUint8(mProxyUsername.Length()) // username length + .WriteString<MAX_USERNAME_LEN>(mProxyUsername) // username + .WriteUint8(password.Length()) // password length + .WriteString<MAX_PASSWORD_LEN>(password) // password. WARNING: Sent unencrypted! + .Written(); + + return PR_SUCCESS; +} + +PRStatus +nsSOCKSSocketInfo::ReadV5UsernameResponse() +{ + MOZ_ASSERT(mState == SOCKS5_READ_USERNAME_RESPONSE, + "Handling SOCKS 5 username/password reply in wrong state!"); + + MOZ_ASSERT(mDataLength == 2, + "SOCKS 5 username reply must be 2 bytes"); + + // Check version number, must be 1 (not 5) + if (ReadUint8() != 0x01) { + LOGERROR(("socks5: unexpected version in the reply")); + HandshakeFinished(PR_CONNECT_REFUSED_ERROR); + return PR_FAILURE; + } + + // Check whether username/password were accepted + if (ReadUint8() != 0x00) { // 0 = success + LOGERROR(("socks5: username/password not accepted")); + HandshakeFinished(PR_CONNECT_REFUSED_ERROR); + return PR_FAILURE; + } + + LOGDEBUG(("socks5: username/password accepted by server")); + + return WriteV5ConnectRequest(); +} + +PRStatus +nsSOCKSSocketInfo::WriteV5ConnectRequest() +{ + // Send SOCKS 5 connect request + NetAddr *addr = &mDestinationAddr; + int32_t proxy_resolve; + proxy_resolve = mFlags & nsISocketProvider::PROXY_RESOLVES_HOST; + + LOGDEBUG(("socks5: sending connection request (socks5 resolve? %s)", + proxy_resolve? "yes" : "no")); + + mDataLength = 0; + mState = SOCKS5_WRITE_CONNECT_REQUEST; + + auto buf = Buffer<BUFFER_SIZE>(mData) + .WriteUint8(0x05) // version -- 5 + .WriteUint8(0x01) // command -- connect + .WriteUint8(0x00); // reserved + + // We're writing a net port after the if, so we need a buffer allowing + // to write that much. + Buffer<sizeof(uint16_t)> buf2; + // Add the address to the SOCKS 5 request. SOCKS 5 supports several + // address types, so we pick the one that works best for us. + if (proxy_resolve) { + // Add the host name. Only a single byte is used to store the length, + // so we must prevent long names from being used. + buf2 = buf.WriteUint8(0x03) // addr type -- domainname + .WriteUint8(mDestinationHost.Length()) // name length + .WriteString<MAX_HOSTNAME_LEN>(mDestinationHost); // Hostname + if (!buf2) { + LOGERROR(("socks5: destination host name is too long!")); + HandshakeFinished(PR_BAD_ADDRESS_ERROR); + return PR_FAILURE; + } + } else if (addr->raw.family == AF_INET) { + buf2 = buf.WriteUint8(0x01) // addr type -- IPv4 + .WriteNetAddr(addr); + } else if (addr->raw.family == AF_INET6) { + buf2 = buf.WriteUint8(0x04) // addr type -- IPv6 + .WriteNetAddr(addr); + } else { + LOGERROR(("socks5: destination address of unknown type!")); + HandshakeFinished(PR_BAD_ADDRESS_ERROR); + return PR_FAILURE; + } + + auto buf3 = buf2.WriteNetPort(addr); // port + mDataLength = buf3.Written(); + + return PR_SUCCESS; +} + +PRStatus +nsSOCKSSocketInfo::ReadV5AddrTypeAndLength(uint8_t *type, uint32_t *len) +{ + MOZ_ASSERT(mState == SOCKS5_READ_CONNECT_RESPONSE_TOP || + mState == SOCKS5_READ_CONNECT_RESPONSE_BOTTOM, + "Invalid state!"); + MOZ_ASSERT(mDataLength >= 5, + "SOCKS 5 connection reply must be at least 5 bytes!"); + + // Seek to the address location + mReadOffset = 3; + + *type = ReadUint8(); + + switch (*type) { + case 0x01: // ipv4 + *len = 4 - 1; + break; + case 0x04: // ipv6 + *len = 16 - 1; + break; + case 0x03: // fqdn + *len = ReadUint8(); + break; + default: // wrong address type + LOGERROR(("socks5: wrong address type in connection reply!")); + return PR_FAILURE; + } + + return PR_SUCCESS; +} + +PRStatus +nsSOCKSSocketInfo::ReadV5ConnectResponseTop() +{ + uint8_t res; + uint32_t len; + + MOZ_ASSERT(mState == SOCKS5_READ_CONNECT_RESPONSE_TOP, + "Invalid state!"); + MOZ_ASSERT(mDataLength == 5, + "SOCKS 5 connection reply must be exactly 5 bytes!"); + + LOGDEBUG(("socks5: checking connection reply")); + + // Check version number + if (ReadUint8() != 0x05) { + LOGERROR(("socks5: unexpected version in the reply")); + HandshakeFinished(PR_CONNECT_REFUSED_ERROR); + return PR_FAILURE; + } + + // Check response + res = ReadUint8(); + if (res != 0x00) { + PRErrorCode c = PR_CONNECT_REFUSED_ERROR; + + switch (res) { + case 0x01: + LOGERROR(("socks5: connect failed: " + "01, General SOCKS server failure.")); + break; + case 0x02: + LOGERROR(("socks5: connect failed: " + "02, Connection not allowed by ruleset.")); + break; + case 0x03: + LOGERROR(("socks5: connect failed: 03, Network unreachable.")); + c = PR_NETWORK_UNREACHABLE_ERROR; + break; + case 0x04: + LOGERROR(("socks5: connect failed: 04, Host unreachable.")); + break; + case 0x05: + LOGERROR(("socks5: connect failed: 05, Connection refused.")); + break; + case 0x06: + LOGERROR(("socks5: connect failed: 06, TTL expired.")); + c = PR_CONNECT_TIMEOUT_ERROR; + break; + case 0x07: + LOGERROR(("socks5: connect failed: " + "07, Command not supported.")); + break; + case 0x08: + LOGERROR(("socks5: connect failed: " + "08, Address type not supported.")); + c = PR_BAD_ADDRESS_ERROR; + break; + default: + LOGERROR(("socks5: connect failed.")); + break; + } + + HandshakeFinished(c); + return PR_FAILURE; + } + + if (ReadV5AddrTypeAndLength(&res, &len) != PR_SUCCESS) { + HandshakeFinished(PR_BAD_ADDRESS_ERROR); + return PR_FAILURE; + } + + mState = SOCKS5_READ_CONNECT_RESPONSE_BOTTOM; + WantRead(len + 2); + + return PR_SUCCESS; +} + +PRStatus +nsSOCKSSocketInfo::ReadV5ConnectResponseBottom() +{ + uint8_t type; + uint32_t len; + + MOZ_ASSERT(mState == SOCKS5_READ_CONNECT_RESPONSE_BOTTOM, + "Invalid state!"); + + if (ReadV5AddrTypeAndLength(&type, &len) != PR_SUCCESS) { + HandshakeFinished(PR_BAD_ADDRESS_ERROR); + return PR_FAILURE; + } + + MOZ_ASSERT(mDataLength == 7+len, + "SOCKS 5 unexpected length of connection reply!"); + + LOGDEBUG(("socks5: loading source addr and port")); + // Read what the proxy says is our source address + switch (type) { + case 0x01: // ipv4 + ReadNetAddr(&mExternalProxyAddr, AF_INET); + break; + case 0x04: // ipv6 + ReadNetAddr(&mExternalProxyAddr, AF_INET6); + break; + case 0x03: // fqdn (skip) + mReadOffset += len; + mExternalProxyAddr.raw.family = AF_INET; + break; + } + + ReadNetPort(&mExternalProxyAddr); + + LOGDEBUG(("socks5: connected!")); + HandshakeFinished(); + + return PR_SUCCESS; +} + +void +nsSOCKSSocketInfo::SetConnectTimeout(PRIntervalTime to) +{ + mTimeout = to; +} + +PRStatus +nsSOCKSSocketInfo::DoHandshake(PRFileDesc *fd, int16_t oflags) +{ + LOGDEBUG(("socks: DoHandshake(), state = %d", mState)); + + switch (mState) { + case SOCKS_INITIAL: + if (IsLocalProxy()) { + mState = SOCKS_DNS_COMPLETE; + mLookupStatus = NS_OK; + return ConnectToProxy(fd); + } + + return StartDNS(fd); + case SOCKS_DNS_IN_PROGRESS: + PR_SetError(PR_IN_PROGRESS_ERROR, 0); + return PR_FAILURE; + case SOCKS_DNS_COMPLETE: + return ConnectToProxy(fd); + case SOCKS_CONNECTING_TO_PROXY: + return ContinueConnectingToProxy(fd, oflags); + case SOCKS4_WRITE_CONNECT_REQUEST: + if (WriteToSocket(fd) != PR_SUCCESS) + return PR_FAILURE; + WantRead(8); + mState = SOCKS4_READ_CONNECT_RESPONSE; + return PR_SUCCESS; + case SOCKS4_READ_CONNECT_RESPONSE: + if (ReadFromSocket(fd) != PR_SUCCESS) + return PR_FAILURE; + return ReadV4ConnectResponse(); + + case SOCKS5_WRITE_AUTH_REQUEST: + if (WriteToSocket(fd) != PR_SUCCESS) + return PR_FAILURE; + WantRead(2); + mState = SOCKS5_READ_AUTH_RESPONSE; + return PR_SUCCESS; + case SOCKS5_READ_AUTH_RESPONSE: + if (ReadFromSocket(fd) != PR_SUCCESS) + return PR_FAILURE; + return ReadV5AuthResponse(); + case SOCKS5_WRITE_USERNAME_REQUEST: + if (WriteToSocket(fd) != PR_SUCCESS) + return PR_FAILURE; + WantRead(2); + mState = SOCKS5_READ_USERNAME_RESPONSE; + return PR_SUCCESS; + case SOCKS5_READ_USERNAME_RESPONSE: + if (ReadFromSocket(fd) != PR_SUCCESS) + return PR_FAILURE; + return ReadV5UsernameResponse(); + case SOCKS5_WRITE_CONNECT_REQUEST: + if (WriteToSocket(fd) != PR_SUCCESS) + return PR_FAILURE; + + // The SOCKS 5 response to the connection request is variable + // length. First, we'll read enough to tell how long the response + // is, and will read the rest later. + WantRead(5); + mState = SOCKS5_READ_CONNECT_RESPONSE_TOP; + return PR_SUCCESS; + case SOCKS5_READ_CONNECT_RESPONSE_TOP: + if (ReadFromSocket(fd) != PR_SUCCESS) + return PR_FAILURE; + return ReadV5ConnectResponseTop(); + case SOCKS5_READ_CONNECT_RESPONSE_BOTTOM: + if (ReadFromSocket(fd) != PR_SUCCESS) + return PR_FAILURE; + return ReadV5ConnectResponseBottom(); + + case SOCKS_CONNECTED: + LOGERROR(("socks: already connected")); + HandshakeFinished(PR_IS_CONNECTED_ERROR); + return PR_FAILURE; + case SOCKS_FAILED: + LOGERROR(("socks: already failed")); + return PR_FAILURE; + } + + LOGERROR(("socks: executing handshake in invalid state, %d", mState)); + HandshakeFinished(PR_INVALID_STATE_ERROR); + + return PR_FAILURE; +} + +int16_t +nsSOCKSSocketInfo::GetPollFlags() const +{ + switch (mState) { + case SOCKS_DNS_IN_PROGRESS: + case SOCKS_DNS_COMPLETE: + case SOCKS_CONNECTING_TO_PROXY: + return PR_POLL_EXCEPT | PR_POLL_WRITE; + case SOCKS4_WRITE_CONNECT_REQUEST: + case SOCKS5_WRITE_AUTH_REQUEST: + case SOCKS5_WRITE_USERNAME_REQUEST: + case SOCKS5_WRITE_CONNECT_REQUEST: + return PR_POLL_WRITE; + case SOCKS4_READ_CONNECT_RESPONSE: + case SOCKS5_READ_AUTH_RESPONSE: + case SOCKS5_READ_USERNAME_RESPONSE: + case SOCKS5_READ_CONNECT_RESPONSE_TOP: + case SOCKS5_READ_CONNECT_RESPONSE_BOTTOM: + return PR_POLL_READ; + default: + break; + } + + return 0; +} + +inline uint8_t +nsSOCKSSocketInfo::ReadUint8() +{ + uint8_t rv; + MOZ_ASSERT(mReadOffset + sizeof(rv) <= mDataLength, + "Not enough space to pop a uint8_t!"); + rv = mData[mReadOffset]; + mReadOffset += sizeof(rv); + return rv; +} + +inline uint16_t +nsSOCKSSocketInfo::ReadUint16() +{ + uint16_t rv; + MOZ_ASSERT(mReadOffset + sizeof(rv) <= mDataLength, + "Not enough space to pop a uint16_t!"); + memcpy(&rv, mData + mReadOffset, sizeof(rv)); + mReadOffset += sizeof(rv); + return rv; +} + +inline uint32_t +nsSOCKSSocketInfo::ReadUint32() +{ + uint32_t rv; + MOZ_ASSERT(mReadOffset + sizeof(rv) <= mDataLength, + "Not enough space to pop a uint32_t!"); + memcpy(&rv, mData + mReadOffset, sizeof(rv)); + mReadOffset += sizeof(rv); + return rv; +} + +void +nsSOCKSSocketInfo::ReadNetAddr(NetAddr *addr, uint16_t fam) +{ + uint32_t amt = 0; + const uint8_t *ip = mData + mReadOffset; + + addr->raw.family = fam; + if (fam == AF_INET) { + amt = sizeof(addr->inet.ip); + MOZ_ASSERT(mReadOffset + amt <= mDataLength, + "Not enough space to pop an ipv4 addr!"); + memcpy(&addr->inet.ip, ip, amt); + } else if (fam == AF_INET6) { + amt = sizeof(addr->inet6.ip.u8); + MOZ_ASSERT(mReadOffset + amt <= mDataLength, + "Not enough space to pop an ipv6 addr!"); + memcpy(addr->inet6.ip.u8, ip, amt); + } + + mReadOffset += amt; +} + +void +nsSOCKSSocketInfo::ReadNetPort(NetAddr *addr) +{ + addr->inet.port = ReadUint16(); +} + +void +nsSOCKSSocketInfo::WantRead(uint32_t sz) +{ + MOZ_ASSERT(mDataIoPtr == nullptr, + "WantRead() called while I/O already in progress!"); + MOZ_ASSERT(mDataLength + sz <= BUFFER_SIZE, + "Can't read that much data!"); + mAmountToRead = sz; +} + +PRStatus +nsSOCKSSocketInfo::ReadFromSocket(PRFileDesc *fd) +{ + int32_t rc; + const uint8_t *end; + + if (!mAmountToRead) { + LOGDEBUG(("socks: ReadFromSocket(), nothing to do")); + return PR_SUCCESS; + } + + if (!mDataIoPtr) { + mDataIoPtr = mData + mDataLength; + mDataLength += mAmountToRead; + } + + end = mData + mDataLength; + + while (mDataIoPtr < end) { + rc = PR_Read(fd, mDataIoPtr, end - mDataIoPtr); + if (rc <= 0) { + if (rc == 0) { + LOGERROR(("socks: proxy server closed connection")); + HandshakeFinished(PR_CONNECT_REFUSED_ERROR); + return PR_FAILURE; + } else if (PR_GetError() == PR_WOULD_BLOCK_ERROR) { + LOGDEBUG(("socks: ReadFromSocket(), want read")); + } + break; + } + + mDataIoPtr += rc; + } + + LOGDEBUG(("socks: ReadFromSocket(), have %u bytes total", + unsigned(mDataIoPtr - mData))); + if (mDataIoPtr == end) { + mDataIoPtr = nullptr; + mAmountToRead = 0; + mReadOffset = 0; + return PR_SUCCESS; + } + + return PR_FAILURE; +} + +PRStatus +nsSOCKSSocketInfo::WriteToSocket(PRFileDesc *fd) +{ + int32_t rc; + const uint8_t *end; + + if (!mDataLength) { + LOGDEBUG(("socks: WriteToSocket(), nothing to do")); + return PR_SUCCESS; + } + + if (!mDataIoPtr) + mDataIoPtr = mData; + + end = mData + mDataLength; + + while (mDataIoPtr < end) { + rc = PR_Write(fd, mDataIoPtr, end - mDataIoPtr); + if (rc < 0) { + if (PR_GetError() == PR_WOULD_BLOCK_ERROR) { + LOGDEBUG(("socks: WriteToSocket(), want write")); + } + break; + } + + mDataIoPtr += rc; + } + + if (mDataIoPtr == end) { + mDataIoPtr = nullptr; + mDataLength = 0; + mReadOffset = 0; + return PR_SUCCESS; + } + + return PR_FAILURE; +} + +static PRStatus +nsSOCKSIOLayerConnect(PRFileDesc *fd, const PRNetAddr *addr, PRIntervalTime to) +{ + PRStatus status; + NetAddr dst; + + nsSOCKSSocketInfo * info = (nsSOCKSSocketInfo*) fd->secret; + if (info == nullptr) return PR_FAILURE; + + if (addr->raw.family == PR_AF_INET6 && + PR_IsNetAddrType(addr, PR_IpAddrV4Mapped)) { + const uint8_t *srcp; + + LOGDEBUG(("socks: converting ipv4-mapped ipv6 address to ipv4")); + + // copied from _PR_ConvertToIpv4NetAddr() + dst.raw.family = AF_INET; + dst.inet.ip = htonl(INADDR_ANY); + dst.inet.port = htons(0); + srcp = addr->ipv6.ip.pr_s6_addr; + memcpy(&dst.inet.ip, srcp + 12, 4); + dst.inet.family = AF_INET; + dst.inet.port = addr->ipv6.port; + } else { + memcpy(&dst, addr, sizeof(dst)); + } + + info->SetDestinationAddr(&dst); + info->SetConnectTimeout(to); + + do { + status = info->DoHandshake(fd, -1); + } while (status == PR_SUCCESS && !info->IsConnected()); + + return status; +} + +static PRStatus +nsSOCKSIOLayerConnectContinue(PRFileDesc *fd, int16_t oflags) +{ + PRStatus status; + + nsSOCKSSocketInfo * info = (nsSOCKSSocketInfo*) fd->secret; + if (info == nullptr) return PR_FAILURE; + + do { + status = info->DoHandshake(fd, oflags); + } while (status == PR_SUCCESS && !info->IsConnected()); + + return status; +} + +static int16_t +nsSOCKSIOLayerPoll(PRFileDesc *fd, int16_t in_flags, int16_t *out_flags) +{ + nsSOCKSSocketInfo * info = (nsSOCKSSocketInfo*) fd->secret; + if (info == nullptr) return PR_FAILURE; + + if (!info->IsConnected()) { + *out_flags = 0; + return info->GetPollFlags(); + } + + return fd->lower->methods->poll(fd->lower, in_flags, out_flags); +} + +static PRStatus +nsSOCKSIOLayerClose(PRFileDesc *fd) +{ + nsSOCKSSocketInfo * info = (nsSOCKSSocketInfo*) fd->secret; + PRDescIdentity id = PR_GetLayersIdentity(fd); + + if (info && id == nsSOCKSIOLayerIdentity) + { + info->ForgetFD(); + NS_RELEASE(info); + fd->identity = PR_INVALID_IO_LAYER; + } + + return fd->lower->methods->close(fd->lower); +} + +static PRFileDesc* +nsSOCKSIOLayerAccept(PRFileDesc *fd, PRNetAddr *addr, PRIntervalTime timeout) +{ + // TODO: implement SOCKS support for accept + return fd->lower->methods->accept(fd->lower, addr, timeout); +} + +static int32_t +nsSOCKSIOLayerAcceptRead(PRFileDesc *sd, PRFileDesc **nd, PRNetAddr **raddr, void *buf, int32_t amount, PRIntervalTime timeout) +{ + // TODO: implement SOCKS support for accept, then read from it + return sd->lower->methods->acceptread(sd->lower, nd, raddr, buf, amount, timeout); +} + +static PRStatus +nsSOCKSIOLayerBind(PRFileDesc *fd, const PRNetAddr *addr) +{ + // TODO: implement SOCKS support for bind (very similar to connect) + return fd->lower->methods->bind(fd->lower, addr); +} + +static PRStatus +nsSOCKSIOLayerGetName(PRFileDesc *fd, PRNetAddr *addr) +{ + nsSOCKSSocketInfo * info = (nsSOCKSSocketInfo*) fd->secret; + + if (info != nullptr && addr != nullptr) { + NetAddr temp; + NetAddr *tempPtr = &temp; + if (info->GetExternalProxyAddr(&tempPtr) == NS_OK) { + NetAddrToPRNetAddr(tempPtr, addr); + return PR_SUCCESS; + } + } + + return PR_FAILURE; +} + +static PRStatus +nsSOCKSIOLayerGetPeerName(PRFileDesc *fd, PRNetAddr *addr) +{ + nsSOCKSSocketInfo * info = (nsSOCKSSocketInfo*) fd->secret; + + if (info != nullptr && addr != nullptr) { + NetAddr temp; + NetAddr *tempPtr = &temp; + if (info->GetDestinationAddr(&tempPtr) == NS_OK) { + NetAddrToPRNetAddr(tempPtr, addr); + return PR_SUCCESS; + } + } + + return PR_FAILURE; +} + +static PRStatus +nsSOCKSIOLayerListen(PRFileDesc *fd, int backlog) +{ + // TODO: implement SOCKS support for listen + return fd->lower->methods->listen(fd->lower, backlog); +} + +// add SOCKS IO layer to an existing socket +nsresult +nsSOCKSIOLayerAddToSocket(int32_t family, + const char *host, + int32_t port, + nsIProxyInfo *proxy, + int32_t socksVersion, + uint32_t flags, + PRFileDesc *fd, + nsISupports** info) +{ + NS_ENSURE_TRUE((socksVersion == 4) || (socksVersion == 5), NS_ERROR_NOT_INITIALIZED); + + + if (firstTime) + { + //XXX hack until NSPR provides an official way to detect system IPv6 + // support (bug 388519) + PRFileDesc *tmpfd = PR_OpenTCPSocket(PR_AF_INET6); + if (!tmpfd) { + ipv6Supported = false; + } else { + // If the system does not support IPv6, NSPR will push + // IPv6-to-IPv4 emulation layer onto the native layer + ipv6Supported = PR_GetIdentitiesLayer(tmpfd, PR_NSPR_IO_LAYER) == tmpfd; + PR_Close(tmpfd); + } + + nsSOCKSIOLayerIdentity = PR_GetUniqueIdentity("SOCKS layer"); + nsSOCKSIOLayerMethods = *PR_GetDefaultIOMethods(); + + nsSOCKSIOLayerMethods.connect = nsSOCKSIOLayerConnect; + nsSOCKSIOLayerMethods.connectcontinue = nsSOCKSIOLayerConnectContinue; + nsSOCKSIOLayerMethods.poll = nsSOCKSIOLayerPoll; + nsSOCKSIOLayerMethods.bind = nsSOCKSIOLayerBind; + nsSOCKSIOLayerMethods.acceptread = nsSOCKSIOLayerAcceptRead; + nsSOCKSIOLayerMethods.getsockname = nsSOCKSIOLayerGetName; + nsSOCKSIOLayerMethods.getpeername = nsSOCKSIOLayerGetPeerName; + nsSOCKSIOLayerMethods.accept = nsSOCKSIOLayerAccept; + nsSOCKSIOLayerMethods.listen = nsSOCKSIOLayerListen; + nsSOCKSIOLayerMethods.close = nsSOCKSIOLayerClose; + + firstTime = false; + } + + LOGDEBUG(("Entering nsSOCKSIOLayerAddToSocket().")); + + PRFileDesc *layer; + PRStatus rv; + + layer = PR_CreateIOLayerStub(nsSOCKSIOLayerIdentity, &nsSOCKSIOLayerMethods); + if (! layer) + { + LOGERROR(("PR_CreateIOLayerStub() failed.")); + return NS_ERROR_FAILURE; + } + + nsSOCKSSocketInfo * infoObject = new nsSOCKSSocketInfo(); + if (!infoObject) + { + // clean up IOLayerStub + LOGERROR(("Failed to create nsSOCKSSocketInfo().")); + PR_DELETE(layer); + return NS_ERROR_FAILURE; + } + + NS_ADDREF(infoObject); + infoObject->Init(socksVersion, family, proxy, host, flags); + layer->secret = (PRFilePrivate*) infoObject; + + PRDescIdentity fdIdentity = PR_GetLayersIdentity(fd); +#if defined(XP_WIN) + if (fdIdentity == mozilla::net::nsNamedPipeLayerIdentity) { + // remember named pipe fd on the info object so that we can switch + // blocking and non-blocking mode on the pipe later. + infoObject->SetNamedPipeFD(fd); + } +#endif + rv = PR_PushIOLayer(fd, fdIdentity, layer); + + if (rv == PR_FAILURE) { + LOGERROR(("PR_PushIOLayer() failed. rv = %x.", rv)); + NS_RELEASE(infoObject); + PR_DELETE(layer); + return NS_ERROR_FAILURE; + } + + *info = static_cast<nsISOCKSSocketInfo*>(infoObject); + NS_ADDREF(*info); + return NS_OK; +} + +bool +IsHostLocalTarget(const nsACString& aHost) +{ +#if defined(XP_UNIX) + return StringBeginsWith(aHost, NS_LITERAL_CSTRING("file:")); +#elif defined(XP_WIN) + return IsNamedPipePath(aHost); +#else + return false; +#endif // XP_UNIX +} diff --git a/netwerk/socket/nsSOCKSIOLayer.h b/netwerk/socket/nsSOCKSIOLayer.h new file mode 100644 index 0000000000..afbbc36067 --- /dev/null +++ b/netwerk/socket/nsSOCKSIOLayer.h @@ -0,0 +1,25 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * 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/. */ + +#ifndef nsSOCKSIOLayer_h__ +#define nsSOCKSIOLayer_h__ + +#include "prio.h" +#include "nscore.h" +#include "nsIProxyInfo.h" + +nsresult nsSOCKSIOLayerAddToSocket(int32_t family, + const char *host, + int32_t port, + nsIProxyInfo *proxyInfo, + int32_t socksVersion, + uint32_t flags, + PRFileDesc *fd, + nsISupports **info); + +bool IsHostLocalTarget(const nsACString& aHost); + +#endif /* nsSOCKSIOLayer_h__ */ diff --git a/netwerk/socket/nsSOCKSSocketProvider.cpp b/netwerk/socket/nsSOCKSSocketProvider.cpp new file mode 100644 index 0000000000..c62534f7bf --- /dev/null +++ b/netwerk/socket/nsSOCKSSocketProvider.cpp @@ -0,0 +1,110 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * 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/. */ + +#include "nsIServiceManager.h" +#include "nsNamedPipeIOLayer.h" +#include "nsSOCKSSocketProvider.h" +#include "nsSOCKSIOLayer.h" +#include "nsCOMPtr.h" +#include "nsError.h" + +using mozilla::NeckoOriginAttributes; + +////////////////////////////////////////////////////////////////////////// + +NS_IMPL_ISUPPORTS(nsSOCKSSocketProvider, nsISocketProvider) + +nsresult +nsSOCKSSocketProvider::CreateV4(nsISupports *aOuter, REFNSIID aIID, void **aResult) +{ + nsresult rv; + nsCOMPtr<nsISocketProvider> inst = + new nsSOCKSSocketProvider(NS_SOCKS_VERSION_4); + if (!inst) + rv = NS_ERROR_OUT_OF_MEMORY; + else + rv = inst->QueryInterface(aIID, aResult); + return rv; +} + +nsresult +nsSOCKSSocketProvider::CreateV5(nsISupports *aOuter, REFNSIID aIID, void **aResult) +{ + nsresult rv; + nsCOMPtr<nsISocketProvider> inst = + new nsSOCKSSocketProvider(NS_SOCKS_VERSION_5); + if (!inst) + rv = NS_ERROR_OUT_OF_MEMORY; + else + rv = inst->QueryInterface(aIID, aResult); + return rv; +} + +NS_IMETHODIMP +nsSOCKSSocketProvider::NewSocket(int32_t family, + const char *host, + int32_t port, + nsIProxyInfo *proxy, + const NeckoOriginAttributes &originAttributes, + uint32_t flags, + PRFileDesc **result, + nsISupports **socksInfo) +{ + PRFileDesc *sock; + +#if defined(XP_WIN) + nsAutoCString proxyHost; + proxy->GetHost(proxyHost); + if (IsNamedPipePath(proxyHost)) { + sock = CreateNamedPipeLayer(); + } else +#endif + { + sock = PR_OpenTCPSocket(family); + if (!sock) { + return NS_ERROR_OUT_OF_MEMORY; + } + } + + nsresult rv = nsSOCKSIOLayerAddToSocket(family, + host, + port, + proxy, + mVersion, + flags, + sock, + socksInfo); + if (NS_SUCCEEDED(rv)) { + *result = sock; + return NS_OK; + } + + return NS_ERROR_SOCKET_CREATE_FAILED; +} + +NS_IMETHODIMP +nsSOCKSSocketProvider::AddToSocket(int32_t family, + const char *host, + int32_t port, + nsIProxyInfo *proxy, + const NeckoOriginAttributes &originAttributes, + uint32_t flags, + PRFileDesc *sock, + nsISupports **socksInfo) +{ + nsresult rv = nsSOCKSIOLayerAddToSocket(family, + host, + port, + proxy, + mVersion, + flags, + sock, + socksInfo); + + if (NS_FAILED(rv)) + rv = NS_ERROR_SOCKET_CREATE_FAILED; + return rv; +} diff --git a/netwerk/socket/nsSOCKSSocketProvider.h b/netwerk/socket/nsSOCKSSocketProvider.h new file mode 100644 index 0000000000..d468ec36ed --- /dev/null +++ b/netwerk/socket/nsSOCKSSocketProvider.h @@ -0,0 +1,35 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * 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/. */ + +#ifndef nsSOCKSSocketProvider_h__ +#define nsSOCKSSocketProvider_h__ + +#include "nsISocketProvider.h" + +// values for ctor's |version| argument +enum { + NS_SOCKS_VERSION_4 = 4, + NS_SOCKS_VERSION_5 = 5 +}; + +class nsSOCKSSocketProvider : public nsISocketProvider +{ +public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSISOCKETPROVIDER + + explicit nsSOCKSSocketProvider(uint32_t version) : mVersion(version) {} + + static nsresult CreateV4(nsISupports *, REFNSIID aIID, void **aResult); + static nsresult CreateV5(nsISupports *, REFNSIID aIID, void **aResult); + +private: + virtual ~nsSOCKSSocketProvider() {} + + uint32_t mVersion; // NS_SOCKS_VERSION_4 or 5 +}; + +#endif /* nsSOCKSSocketProvider_h__ */ diff --git a/netwerk/socket/nsSocketProviderService.cpp b/netwerk/socket/nsSocketProviderService.cpp new file mode 100644 index 0000000000..b3fc5d9e72 --- /dev/null +++ b/netwerk/socket/nsSocketProviderService.cpp @@ -0,0 +1,45 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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/. */ + +#include "nsString.h" +#include "nsIServiceManager.h" +#include "nsISocketProvider.h" +#include "nsSocketProviderService.h" +#include "nsError.h" + +//////////////////////////////////////////////////////////////////////////////// + +nsresult +nsSocketProviderService::Create(nsISupports *aOuter, REFNSIID aIID, void **aResult) +{ + nsresult rv; + nsCOMPtr<nsISocketProviderService> inst = new nsSocketProviderService(); + if (!inst) + rv = NS_ERROR_OUT_OF_MEMORY; + else + rv = inst->QueryInterface(aIID, aResult); + return rv; +} + +NS_IMPL_ISUPPORTS(nsSocketProviderService, nsISocketProviderService) + +//////////////////////////////////////////////////////////////////////////////// + +NS_IMETHODIMP +nsSocketProviderService::GetSocketProvider(const char *type, + nsISocketProvider **result) +{ + nsresult rv; + nsAutoCString contractID( + NS_LITERAL_CSTRING(NS_NETWORK_SOCKET_CONTRACTID_PREFIX) + + nsDependentCString(type)); + + rv = CallGetService(contractID.get(), result); + if (NS_FAILED(rv)) + rv = NS_ERROR_UNKNOWN_SOCKET_TYPE; + return rv; +} + +//////////////////////////////////////////////////////////////////////////////// diff --git a/netwerk/socket/nsSocketProviderService.h b/netwerk/socket/nsSocketProviderService.h new file mode 100644 index 0000000000..4082abcfa5 --- /dev/null +++ b/netwerk/socket/nsSocketProviderService.h @@ -0,0 +1,24 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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/. */ + +#ifndef nsSocketProviderService_h__ +#define nsSocketProviderService_h__ + +#include "nsISocketProviderService.h" + +class nsSocketProviderService : public nsISocketProviderService +{ + virtual ~nsSocketProviderService() {} + +public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSISOCKETPROVIDERSERVICE + + nsSocketProviderService() {} + + static nsresult Create(nsISupports *, REFNSIID aIID, void **aResult); +}; + +#endif /* nsSocketProviderService_h__ */ diff --git a/netwerk/socket/nsUDPSocketProvider.cpp b/netwerk/socket/nsUDPSocketProvider.cpp new file mode 100644 index 0000000000..552c26ba2e --- /dev/null +++ b/netwerk/socket/nsUDPSocketProvider.cpp @@ -0,0 +1,50 @@ +/* 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/. */ + +#include "nsUDPSocketProvider.h" + +#include "nspr.h" + +using mozilla::NeckoOriginAttributes; + +NS_IMPL_ISUPPORTS(nsUDPSocketProvider, nsISocketProvider) + +nsUDPSocketProvider::~nsUDPSocketProvider() +{ +} + +NS_IMETHODIMP +nsUDPSocketProvider::NewSocket(int32_t aFamily, + const char *aHost, + int32_t aPort, + nsIProxyInfo *aProxy, + const NeckoOriginAttributes &originAttributes, + uint32_t aFlags, + PRFileDesc * *aFileDesc, + nsISupports **aSecurityInfo) +{ + NS_ENSURE_ARG_POINTER(aFileDesc); + + PRFileDesc* udpFD = PR_OpenUDPSocket(aFamily); + if (!udpFD) + return NS_ERROR_FAILURE; + + *aFileDesc = udpFD; + return NS_OK; +} + +NS_IMETHODIMP +nsUDPSocketProvider::AddToSocket(int32_t aFamily, + const char *aHost, + int32_t aPort, + nsIProxyInfo *aProxy, + const NeckoOriginAttributes &originAttributes, + uint32_t aFlags, + struct PRFileDesc * aFileDesc, + nsISupports **aSecurityInfo) +{ + // does not make sense to strap a UDP socket onto an existing socket + NS_NOTREACHED("Cannot layer UDP socket on an existing socket"); + return NS_ERROR_UNEXPECTED; +} diff --git a/netwerk/socket/nsUDPSocketProvider.h b/netwerk/socket/nsUDPSocketProvider.h new file mode 100644 index 0000000000..32c0e3b255 --- /dev/null +++ b/netwerk/socket/nsUDPSocketProvider.h @@ -0,0 +1,23 @@ +/* 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/. */ + +#ifndef nsUDPSocketProvider_h__ +#define nsUDPSocketProvider_h__ + +#include "nsISocketProvider.h" +#include "mozilla/Attributes.h" + +class nsUDPSocketProvider final : public nsISocketProvider +{ +public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSISOCKETPROVIDER + +private: + ~nsUDPSocketProvider(); + +}; + +#endif /* nsUDPSocketProvider_h__ */ + |