gecko-dev/netwerk/socket/nsSOCKSIOLayer.cpp
Nika Layzell 852d02ec16 Bug 1809753 - Part 3: Replace all callers of GetCurrentEventTarget with GetCurrentSerialEventTarget, r=mccr8,necko-reviewers,valentin
This only changes the behaviour when called with a TaskQueue or other type
using SerialEventTargetGuard on the stack. They are being switched over as the
existing GetCurrentEventTarget method is being removed, as it is somewhat
confusing, and poorly documented.

Callers which need to get the current thread even when on a threadpool or
behind a TaskQueue were switched to GetCurrentEventTarget in the previous part.

Differential Revision: https://phabricator.services.mozilla.com/D166607
2023-01-16 23:14:11 +00:00

1472 lines
44 KiB
C++

/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:set expandtab ts=4 sw=2 sts=2 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 "nsIDNSService.h"
#include "nsIDNSRecord.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 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_NSIDNSLISTENER
void Init(int32_t version, int32_t family, nsIProxyInfo* proxy,
const char* destinationHost, uint32_t flags, uint32_t tlsFlags);
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; }
void GetExternalProxyAddr(NetAddr& aExternalProxyAddr);
void GetDestinationAddr(NetAddr& aDestinationAddr);
void SetDestinationAddr(const NetAddr& aDestinationAddr);
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{SOCKS_INITIAL};
uint8_t* mData{nullptr};
uint8_t* mDataIoPtr{nullptr};
uint32_t mDataLength{0};
uint32_t mReadOffset{0};
uint32_t mAmountToRead{0};
nsCOMPtr<nsIDNSRecord> mDnsRec;
nsCOMPtr<nsICancelable> mLookup;
nsresult mLookupStatus{NS_ERROR_NOT_INITIALIZED};
PRFileDesc* mFD{nullptr};
nsCString mDestinationHost;
nsCOMPtr<nsIProxyInfo> mProxy;
int32_t mVersion{-1}; // SOCKS version 4 or 5
int32_t mDestinationFamily{AF_INET};
uint32_t mFlags{0};
uint32_t mTlsFlags{0};
NetAddr mInternalProxyAddr;
NetAddr mExternalProxyAddr;
NetAddr mDestinationAddr;
PRIntervalTime mTimeout{PR_INTERVAL_NO_TIMEOUT};
nsCString mProxyUsername; // Cache, from mProxy
};
nsSOCKSSocketInfo::nsSOCKSSocketInfo() {
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() = default;
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);
}
if (aAddr->raw.family == AF_INET6) {
return Write(aAddr->inet6.ip.u8);
}
MOZ_ASSERT_UNREACHABLE("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{nullptr};
size_t mLength{0};
};
void nsSOCKSSocketInfo::Init(int32_t version, int32_t family,
nsIProxyInfo* proxy, const char* host,
uint32_t flags, uint32_t tlsFlags) {
mVersion = version;
mDestinationFamily = family;
mProxy = proxy;
mDestinationHost = host;
mFlags = flags;
mTlsFlags = tlsFlags;
mProxy->GetUsername(mProxyUsername); // cache
}
NS_IMPL_ISUPPORTS(nsSOCKSSocketInfo, nsIDNSListener)
void nsSOCKSSocketInfo::GetExternalProxyAddr(NetAddr& aExternalProxyAddr) {
aExternalProxyAddr = mExternalProxyAddr;
}
void nsSOCKSSocketInfo::GetDestinationAddr(NetAddr& aDestinationAddr) {
aDestinationAddr = mDestinationAddr;
}
void nsSOCKSSocketInfo::SetDestinationAddr(const NetAddr& aDestinationAddr) {
mDestinationAddr = aDestinationAddr;
}
// 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);
mozilla::OriginAttributes attrs;
mFD = fd;
nsresult rv = dns->AsyncResolveNative(
proxyHost, nsIDNSService::RESOLVE_TYPE_DEFAULT,
nsIDNSService::RESOLVE_IGNORE_SOCKS_DNS, nullptr, this,
mozilla::GetCurrentSerialEventTarget(), attrs, 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 {
nsCOMPtr<nsIDNSAddrRecord> record = do_QueryInterface(mDnsRec);
MOZ_ASSERT(record);
if (addresses++) {
record->ReportUnusable(proxyPort);
}
rv = record->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];
mInternalProxyAddr.ToStringBuffer(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;
}
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();
}
if (!mProxyUsername.IsEmpty() && authMethod == 0x02) { // username/pw
LOGDEBUG(("socks5: auth method accepted by server"));
return WriteV5UsernameRequest();
} // 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."));
c = PR_BAD_ADDRESS_ERROR;
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;
}
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;
info->GetExternalProxyAddr(temp);
NetAddrToPRNetAddr(&temp, 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;
info->GetDestinationAddr(temp);
NetAddrToPRNetAddr(&temp, 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,
uint32_t tlsFlags, PRFileDesc* fd) {
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_Free(layer); // PR_CreateIOLayerStub() uses PR_Malloc().
return NS_ERROR_FAILURE;
}
NS_ADDREF(infoObject);
infoObject->Init(socksVersion, family, proxy, host, flags, tlsFlags);
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_Free(layer); // PR_CreateIOLayerStub() uses PR_Malloc().
return NS_ERROR_FAILURE;
}
return NS_OK;
}
bool IsHostLocalTarget(const nsACString& aHost) {
#if defined(XP_UNIX)
return StringBeginsWith(aHost, "file:"_ns);
#elif defined(XP_WIN)
return IsNamedPipePath(aHost);
#else
return false;
#endif // XP_UNIX
}