Source code

Revision control

Copy as Markdown

Other Tools

/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:set ts=4 sw=2 et cindent: */
/* 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 "nsSocketTransport2.h"
#include "NSSErrorsService.h"
#include "NetworkDataCountLayer.h"
#include "QuicSocketControl.h"
#include "mozilla/Attributes.h"
#include "mozilla/StaticPrefs_network.h"
#include "mozilla/SyncRunnable.h"
#include "mozilla/Telemetry.h"
#include "mozilla/dom/ToJSValue.h"
#include "mozilla/net/NeckoChild.h"
#include "mozilla/net/SSLTokensCache.h"
#include "mozilla/ProfilerBandwidthCounter.h"
#include "nsCOMPtr.h"
#include "nsICancelable.h"
#include "nsIClassInfoImpl.h"
#include "nsIDNSByTypeRecord.h"
#include "nsIDNSRecord.h"
#include "nsIDNSService.h"
#include "nsIOService.h"
#include "nsIPipe.h"
#include "nsISocketProvider.h"
#include "nsITLSSocketControl.h"
#include "nsNetAddr.h"
#include "nsNetCID.h"
#include "nsNetSegmentUtils.h"
#include "nsNetUtil.h"
#include "nsPrintfCString.h"
#include "nsProxyInfo.h"
#include "nsSocketProviderService.h"
#include "nsStreamUtils.h"
#include "nsThreadUtils.h"
#include "nsTransportUtils.h"
#include "nsURLHelper.h"
#include "prerr.h"
#include "sslexp.h"
#include "xpcpublic.h"
#if defined(FUZZING)
# include "FuzzyLayer.h"
# include "FuzzySocketControl.h"
# include "mozilla/StaticPrefs_fuzzing.h"
#endif
#if defined(XP_WIN)
# include "ShutdownLayer.h"
#endif
/* Following inclusions required for keepalive config not supported by NSPR. */
#include "private/pprio.h"
#if defined(XP_WIN)
# include <winsock2.h>
# include <mstcpip.h>
#elif defined(XP_UNIX)
# include <errno.h>
# include <netinet/tcp.h>
#endif
/* End keepalive config inclusions. */
#define SUCCESSFUL_CONNECTING_TO_IPV4_ADDRESS 0
#define UNSUCCESSFUL_CONNECTING_TO_IPV4_ADDRESS 1
#define SUCCESSFUL_CONNECTING_TO_IPV6_ADDRESS 2
#define UNSUCCESSFUL_CONNECTING_TO_IPV6_ADDRESS 3
//-----------------------------------------------------------------------------
static NS_DEFINE_CID(kDNSServiceCID, NS_DNSSERVICE_CID);
//-----------------------------------------------------------------------------
namespace mozilla {
namespace net {
class nsSocketEvent : public Runnable {
public:
nsSocketEvent(nsSocketTransport* transport, uint32_t type,
nsresult status = NS_OK, nsISupports* param = nullptr,
std::function<void()>&& task = nullptr)
: Runnable("net::nsSocketEvent"),
mTransport(transport),
mType(type),
mStatus(status),
mParam(param),
mTask(std::move(task)) {}
NS_IMETHOD Run() override {
mTransport->OnSocketEvent(mType, mStatus, mParam, std::move(mTask));
return NS_OK;
}
private:
RefPtr<nsSocketTransport> mTransport;
uint32_t mType;
nsresult mStatus;
nsCOMPtr<nsISupports> mParam;
std::function<void()> mTask;
};
//-----------------------------------------------------------------------------
// #define TEST_CONNECT_ERRORS
#ifdef TEST_CONNECT_ERRORS
# include <stdlib.h>
static PRErrorCode RandomizeConnectError(PRErrorCode code) {
//
// To test out these errors, load http://www.yahoo.com/. It should load
// correctly despite the random occurrence of these errors.
//
int n = rand();
if (n > RAND_MAX / 2) {
struct {
PRErrorCode err_code;
const char* err_name;
} errors[] = {
//
// These errors should be recoverable provided there is another
// IP address in mDNSRecord.
//
{PR_CONNECT_REFUSED_ERROR, "PR_CONNECT_REFUSED_ERROR"},
{PR_CONNECT_TIMEOUT_ERROR, "PR_CONNECT_TIMEOUT_ERROR"},
//
// This error will cause this socket transport to error out;
// however, if the consumer is HTTP, then the HTTP transaction
// should be restarted when this error occurs.
//
{PR_CONNECT_RESET_ERROR, "PR_CONNECT_RESET_ERROR"},
};
n = n % (sizeof(errors) / sizeof(errors[0]));
code = errors[n].err_code;
SOCKET_LOG(("simulating NSPR error %d [%s]\n", code, errors[n].err_name));
}
return code;
}
#endif
//-----------------------------------------------------------------------------
nsresult ErrorAccordingToNSPR(PRErrorCode errorCode) {
nsresult rv = NS_ERROR_FAILURE;
switch (errorCode) {
case PR_WOULD_BLOCK_ERROR:
rv = NS_BASE_STREAM_WOULD_BLOCK;
break;
case PR_CONNECT_ABORTED_ERROR:
case PR_CONNECT_RESET_ERROR:
rv = NS_ERROR_NET_RESET;
break;
case PR_END_OF_FILE_ERROR: // XXX document this correlation
rv = NS_ERROR_NET_INTERRUPT;
break;
case PR_CONNECT_REFUSED_ERROR:
// We lump the following NSPR codes in with PR_CONNECT_REFUSED_ERROR. We
// could get better diagnostics by adding distinct XPCOM error codes for
// each of these, but there are a lot of places in Gecko that check
// specifically for NS_ERROR_CONNECTION_REFUSED, all of which would need to
// be checked.
case PR_NETWORK_UNREACHABLE_ERROR:
case PR_HOST_UNREACHABLE_ERROR:
case PR_ADDRESS_NOT_AVAILABLE_ERROR:
// Treat EACCES as a soft error since (at least on Linux) connect() returns
// EACCES when an IPv6 connection is blocked by a firewall. See bug 270784.
case PR_NO_ACCESS_RIGHTS_ERROR:
rv = NS_ERROR_CONNECTION_REFUSED;
break;
case PR_ADDRESS_NOT_SUPPORTED_ERROR:
rv = NS_ERROR_SOCKET_ADDRESS_NOT_SUPPORTED;
break;
case PR_IO_TIMEOUT_ERROR:
case PR_CONNECT_TIMEOUT_ERROR:
rv = NS_ERROR_NET_TIMEOUT;
break;
case PR_OUT_OF_MEMORY_ERROR:
// These really indicate that the descriptor table filled up, or that the
// kernel ran out of network buffers - but nobody really cares which part of
// the system ran out of memory.
case PR_PROC_DESC_TABLE_FULL_ERROR:
case PR_SYS_DESC_TABLE_FULL_ERROR:
case PR_INSUFFICIENT_RESOURCES_ERROR:
rv = NS_ERROR_OUT_OF_MEMORY;
break;
case PR_ADDRESS_IN_USE_ERROR:
rv = NS_ERROR_SOCKET_ADDRESS_IN_USE;
break;
// These filename-related errors can arise when using Unix-domain sockets.
case PR_FILE_NOT_FOUND_ERROR:
rv = NS_ERROR_FILE_NOT_FOUND;
break;
case PR_IS_DIRECTORY_ERROR:
rv = NS_ERROR_FILE_IS_DIRECTORY;
break;
case PR_LOOP_ERROR:
rv = NS_ERROR_FILE_UNRESOLVABLE_SYMLINK;
break;
case PR_NAME_TOO_LONG_ERROR:
rv = NS_ERROR_FILE_NAME_TOO_LONG;
break;
case PR_NO_DEVICE_SPACE_ERROR:
rv = NS_ERROR_FILE_NO_DEVICE_SPACE;
break;
case PR_NOT_DIRECTORY_ERROR:
rv = NS_ERROR_FILE_NOT_DIRECTORY;
break;
case PR_READ_ONLY_FILESYSTEM_ERROR:
rv = NS_ERROR_FILE_READ_ONLY;
break;
case PR_BAD_ADDRESS_ERROR:
rv = NS_ERROR_UNKNOWN_HOST;
break;
default:
if (psm::IsNSSErrorCode(errorCode)) {
rv = psm::GetXPCOMFromNSSError(errorCode);
}
break;
// NSPR's socket code can return these, but they're not worth breaking out
// into their own error codes, distinct from NS_ERROR_FAILURE:
//
// PR_BAD_DESCRIPTOR_ERROR
// PR_INVALID_ARGUMENT_ERROR
// PR_NOT_SOCKET_ERROR
// PR_NOT_TCP_SOCKET_ERROR
// These would indicate a bug internal to the component.
//
// PR_PROTOCOL_NOT_SUPPORTED_ERROR
// This means that we can't use the given "protocol" (like
// IPPROTO_TCP or IPPROTO_UDP) with a socket of the given type. As
// above, this indicates an internal bug.
//
// PR_IS_CONNECTED_ERROR
// This indicates that we've applied a system call like 'bind' or
// 'connect' to a socket that is already connected. The socket
// components manage each file descriptor's state, and in some cases
// handle this error result internally. We shouldn't be returning
// this to our callers.
//
// PR_IO_ERROR
// This is so vague that NS_ERROR_FAILURE is just as good.
}
SOCKET_LOG(("ErrorAccordingToNSPR [in=%d out=%" PRIx32 "]\n", errorCode,
static_cast<uint32_t>(rv)));
return rv;
}
//-----------------------------------------------------------------------------
// socket input stream impl
//-----------------------------------------------------------------------------
nsSocketInputStream::nsSocketInputStream(nsSocketTransport* trans)
: mTransport(trans) {}
// called on the socket transport thread...
//
// condition : failure code if socket has been closed
//
void nsSocketInputStream::OnSocketReady(nsresult condition) {
SOCKET_LOG(("nsSocketInputStream::OnSocketReady [this=%p cond=%" PRIx32 "]\n",
this, static_cast<uint32_t>(condition)));
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
nsCOMPtr<nsIInputStreamCallback> callback;
{
MutexAutoLock lock(mTransport->mLock);
// update condition, but be careful not to erase an already
// existing error condition.
if (NS_SUCCEEDED(mCondition)) mCondition = condition;
// ignore event if only waiting for closure and not closed.
if (NS_FAILED(mCondition) || !(mCallbackFlags & WAIT_CLOSURE_ONLY)) {
callback = std::move(mCallback);
mCallbackFlags = 0;
}
}
if (callback) callback->OnInputStreamReady(this);
}
NS_IMPL_QUERY_INTERFACE(nsSocketInputStream, nsIInputStream,
nsIAsyncInputStream)
NS_IMETHODIMP_(MozExternalRefCountType)
nsSocketInputStream::AddRef() {
++mReaderRefCnt;
return mTransport->AddRef();
}
NS_IMETHODIMP_(MozExternalRefCountType)
nsSocketInputStream::Release() {
if (--mReaderRefCnt == 0) Close();
return mTransport->Release();
}
NS_IMETHODIMP
nsSocketInputStream::Close() { return CloseWithStatus(NS_BASE_STREAM_CLOSED); }
NS_IMETHODIMP
nsSocketInputStream::Available(uint64_t* avail) {
SOCKET_LOG(("nsSocketInputStream::Available [this=%p]\n", this));
*avail = 0;
PRFileDesc* fd;
{
MutexAutoLock lock(mTransport->mLock);
if (NS_FAILED(mCondition)) return mCondition;
fd = mTransport->GetFD_Locked();
if (!fd) return NS_OK;
}
// cannot hold lock while calling NSPR. (worried about the fact that PSM
// synchronously proxies notifications over to the UI thread, which could
// mistakenly try to re-enter this code.)
int32_t n = PR_Available(fd);
// PSM does not implement PR_Available() so do a best approximation of it
// with MSG_PEEK
if ((n == -1) && (PR_GetError() == PR_NOT_IMPLEMENTED_ERROR)) {
char c;
n = PR_Recv(fd, &c, 1, PR_MSG_PEEK, 0);
SOCKET_LOG(
("nsSocketInputStream::Available [this=%p] "
"using PEEK backup n=%d]\n",
this, n));
}
nsresult rv;
{
MutexAutoLock lock(mTransport->mLock);
mTransport->ReleaseFD_Locked(fd);
if (n >= 0) {
*avail = n;
} else {
PRErrorCode code = PR_GetError();
if (code == PR_WOULD_BLOCK_ERROR) return NS_OK;
mCondition = ErrorAccordingToNSPR(code);
}
rv = mCondition;
}
if (NS_FAILED(rv)) mTransport->OnInputClosed(rv);
return rv;
}
NS_IMETHODIMP
nsSocketInputStream::StreamStatus() {
SOCKET_LOG(("nsSocketInputStream::StreamStatus [this=%p]\n", this));
MutexAutoLock lock(mTransport->mLock);
return mCondition;
}
NS_IMETHODIMP
nsSocketInputStream::Read(char* buf, uint32_t count, uint32_t* countRead) {
SOCKET_LOG(("nsSocketInputStream::Read [this=%p count=%u]\n", this, count));
*countRead = 0;
PRFileDesc* fd = nullptr;
{
MutexAutoLock lock(mTransport->mLock);
if (NS_FAILED(mCondition)) {
return (mCondition == NS_BASE_STREAM_CLOSED) ? NS_OK : mCondition;
}
fd = mTransport->GetFD_Locked();
if (!fd) return NS_BASE_STREAM_WOULD_BLOCK;
}
SOCKET_LOG((" calling PR_Read [count=%u]\n", count));
// cannot hold lock while calling NSPR. (worried about the fact that PSM
// synchronously proxies notifications over to the UI thread, which could
// mistakenly try to re-enter this code.)
int32_t n = PR_Read(fd, buf, count);
SOCKET_LOG((" PR_Read returned [n=%d]\n", n));
nsresult rv = NS_OK;
{
MutexAutoLock lock(mTransport->mLock);
#ifdef ENABLE_SOCKET_TRACING
if (n > 0) mTransport->TraceInBuf(buf, n);
#endif
mTransport->ReleaseFD_Locked(fd);
if (n > 0) {
mByteCount += (*countRead = n);
profiler_count_bandwidth_read_bytes(n);
} else if (n < 0) {
PRErrorCode code = PR_GetError();
if (code == PR_WOULD_BLOCK_ERROR) return NS_BASE_STREAM_WOULD_BLOCK;
mCondition = ErrorAccordingToNSPR(code);
}
rv = mCondition;
}
if (NS_FAILED(rv)) mTransport->OnInputClosed(rv);
// only send this notification if we have indeed read some data.
// see bug 196827 for an example of why this is important.
if (n > 0) mTransport->SendStatus(NS_NET_STATUS_RECEIVING_FROM);
return rv;
}
NS_IMETHODIMP
nsSocketInputStream::ReadSegments(nsWriteSegmentFun writer, void* closure,
uint32_t count, uint32_t* countRead) {
// socket stream is unbuffered
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
nsSocketInputStream::IsNonBlocking(bool* nonblocking) {
*nonblocking = true;
return NS_OK;
}
NS_IMETHODIMP
nsSocketInputStream::CloseWithStatus(nsresult reason) {
SOCKET_LOG(("nsSocketInputStream::CloseWithStatus [this=%p reason=%" PRIx32
"]\n",
this, static_cast<uint32_t>(reason)));
// may be called from any thread
nsresult rv;
{
MutexAutoLock lock(mTransport->mLock);
if (NS_SUCCEEDED(mCondition)) {
rv = mCondition = reason;
} else {
rv = NS_OK;
}
}
if (NS_FAILED(rv)) mTransport->OnInputClosed(rv);
return NS_OK;
}
NS_IMETHODIMP
nsSocketInputStream::AsyncWait(nsIInputStreamCallback* callback, uint32_t flags,
uint32_t amount, nsIEventTarget* target) {
SOCKET_LOG(("nsSocketInputStream::AsyncWait [this=%p]\n", this));
bool hasError = false;
{
MutexAutoLock lock(mTransport->mLock);
if (callback && target) {
//
// build event proxy
//
mCallback = NS_NewInputStreamReadyEvent("nsSocketInputStream::AsyncWait",
callback, target);
} else {
mCallback = callback;
}
mCallbackFlags = flags;
hasError = NS_FAILED(mCondition);
} // unlock mTransport->mLock
if (hasError) {
// OnSocketEvent will call OnInputStreamReady with an error code after
// going through the event loop. We do this because most socket callers
// do not expect AsyncWait() to synchronously execute the OnInputStreamReady
// callback.
mTransport->PostEvent(nsSocketTransport::MSG_INPUT_PENDING);
} else {
mTransport->OnInputPending();
}
return NS_OK;
}
//-----------------------------------------------------------------------------
// socket output stream impl
//-----------------------------------------------------------------------------
nsSocketOutputStream::nsSocketOutputStream(nsSocketTransport* trans)
: mTransport(trans) {}
// called on the socket transport thread...
//
// condition : failure code if socket has been closed
//
void nsSocketOutputStream::OnSocketReady(nsresult condition) {
SOCKET_LOG(("nsSocketOutputStream::OnSocketReady [this=%p cond=%" PRIx32
"]\n",
this, static_cast<uint32_t>(condition)));
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
nsCOMPtr<nsIOutputStreamCallback> callback;
{
MutexAutoLock lock(mTransport->mLock);
// update condition, but be careful not to erase an already
// existing error condition.
if (NS_SUCCEEDED(mCondition)) mCondition = condition;
// ignore event if only waiting for closure and not closed.
if (NS_FAILED(mCondition) || !(mCallbackFlags & WAIT_CLOSURE_ONLY)) {
callback = std::move(mCallback);
mCallbackFlags = 0;
}
}
if (callback) callback->OnOutputStreamReady(this);
}
NS_IMPL_QUERY_INTERFACE(nsSocketOutputStream, nsIOutputStream,
nsIAsyncOutputStream)
NS_IMETHODIMP_(MozExternalRefCountType)
nsSocketOutputStream::AddRef() {
++mWriterRefCnt;
return mTransport->AddRef();
}
NS_IMETHODIMP_(MozExternalRefCountType)
nsSocketOutputStream::Release() {
if (--mWriterRefCnt == 0) Close();
return mTransport->Release();
}
NS_IMETHODIMP
nsSocketOutputStream::Close() { return CloseWithStatus(NS_BASE_STREAM_CLOSED); }
NS_IMETHODIMP
nsSocketOutputStream::Flush() { return NS_OK; }
NS_IMETHODIMP
nsSocketOutputStream::StreamStatus() {
MutexAutoLock lock(mTransport->mLock);
return mCondition;
}
NS_IMETHODIMP
nsSocketOutputStream::Write(const char* buf, uint32_t count,
uint32_t* countWritten) {
SOCKET_LOG(("nsSocketOutputStream::Write [this=%p count=%u]\n", this, count));
*countWritten = 0;
// A write of 0 bytes can be used to force the initial SSL handshake, so do
// not reject that.
PRFileDesc* fd = nullptr;
{
MutexAutoLock lock(mTransport->mLock);
if (NS_FAILED(mCondition)) return mCondition;
fd = mTransport->GetFD_Locked();
if (!fd) return NS_BASE_STREAM_WOULD_BLOCK;
}
SOCKET_LOG((" calling PR_Write [count=%u]\n", count));
// cannot hold lock while calling NSPR. (worried about the fact that PSM
// synchronously proxies notifications over to the UI thread, which could
// mistakenly try to re-enter this code.)
int32_t n = PR_Write(fd, buf, count);
SOCKET_LOG((" PR_Write returned [n=%d]\n", n));
nsresult rv = NS_OK;
{
MutexAutoLock lock(mTransport->mLock);
#ifdef ENABLE_SOCKET_TRACING
if (n > 0) mTransport->TraceOutBuf(buf, n);
#endif
mTransport->ReleaseFD_Locked(fd);
if (n > 0) {
mByteCount += (*countWritten = n);
profiler_count_bandwidth_written_bytes(n);
} else if (n < 0) {
PRErrorCode code = PR_GetError();
if (code == PR_WOULD_BLOCK_ERROR) return NS_BASE_STREAM_WOULD_BLOCK;
mCondition = ErrorAccordingToNSPR(code);
}
rv = mCondition;
}
if (NS_FAILED(rv)) mTransport->OnOutputClosed(rv);
// only send this notification if we have indeed written some data.
// see bug 196827 for an example of why this is important.
if ((n > 0)) {
mTransport->SendStatus(NS_NET_STATUS_SENDING_TO);
}
return rv;
}
NS_IMETHODIMP
nsSocketOutputStream::WriteSegments(nsReadSegmentFun reader, void* closure,
uint32_t count, uint32_t* countRead) {
// socket stream is unbuffered
return NS_ERROR_NOT_IMPLEMENTED;
}
nsresult nsSocketOutputStream::WriteFromSegments(
nsIInputStream* input, void* closure, const char* fromSegment,
uint32_t offset, uint32_t count, uint32_t* countRead) {
nsSocketOutputStream* self = (nsSocketOutputStream*)closure;
return self->Write(fromSegment, count, countRead);
}
NS_IMETHODIMP
nsSocketOutputStream::WriteFrom(nsIInputStream* stream, uint32_t count,
uint32_t* countRead) {
return stream->ReadSegments(WriteFromSegments, this, count, countRead);
}
NS_IMETHODIMP
nsSocketOutputStream::IsNonBlocking(bool* nonblocking) {
*nonblocking = true;
return NS_OK;
}
NS_IMETHODIMP
nsSocketOutputStream::CloseWithStatus(nsresult reason) {
SOCKET_LOG(("nsSocketOutputStream::CloseWithStatus [this=%p reason=%" PRIx32
"]\n",
this, static_cast<uint32_t>(reason)));
// may be called from any thread
nsresult rv;
{
MutexAutoLock lock(mTransport->mLock);
if (NS_SUCCEEDED(mCondition)) {
rv = mCondition = reason;
} else {
rv = NS_OK;
}
}
if (NS_FAILED(rv)) mTransport->OnOutputClosed(rv);
return NS_OK;
}
NS_IMETHODIMP
nsSocketOutputStream::AsyncWait(nsIOutputStreamCallback* callback,
uint32_t flags, uint32_t amount,
nsIEventTarget* target) {
SOCKET_LOG(("nsSocketOutputStream::AsyncWait [this=%p]\n", this));
{
MutexAutoLock lock(mTransport->mLock);
if (callback && target) {
//
// build event proxy
//
mCallback = NS_NewOutputStreamReadyEvent(callback, target);
} else {
mCallback = callback;
}
mCallbackFlags = flags;
}
mTransport->OnOutputPending();
return NS_OK;
}
//-----------------------------------------------------------------------------
// socket transport impl
//-----------------------------------------------------------------------------
nsSocketTransport::nsSocketTransport()
: mFD(this),
mSocketTransportService(gSocketTransportService),
mInput(new nsSocketInputStream(this)),
mOutput(new nsSocketOutputStream(this)) {
SOCKET_LOG(("creating nsSocketTransport @%p\n", this));
mTimeouts[TIMEOUT_CONNECT] = UINT16_MAX; // no timeout
mTimeouts[TIMEOUT_READ_WRITE] = UINT16_MAX; // no timeout
}
nsSocketTransport::~nsSocketTransport() {
MOZ_RELEASE_ASSERT(!mAttached);
SOCKET_LOG(("destroying nsSocketTransport @%p\n", this));
}
nsresult nsSocketTransport::Init(const nsTArray<nsCString>& types,
const nsACString& host, uint16_t port,
const nsACString& hostRoute,
uint16_t portRoute,
nsIProxyInfo* givenProxyInfo,
nsIDNSRecord* dnsRecord) {
nsCOMPtr<nsProxyInfo> proxyInfo;
if (givenProxyInfo) {
proxyInfo = do_QueryInterface(givenProxyInfo);
NS_ENSURE_ARG(proxyInfo);
}
if (dnsRecord) {
mExternalDNSResolution = true;
mDNSRecord = do_QueryInterface(dnsRecord);
mDNSRecord->IsTRR(&mResolvedByTRR);
mDNSRecord->GetEffectiveTRRMode(&mEffectiveTRRMode);
mDNSRecord->GetTrrSkipReason(&mTRRSkipReason);
}
// init socket type info
mOriginHost = host;
mOriginPort = port;
if (!hostRoute.IsEmpty()) {
mHost = hostRoute;
mPort = portRoute;
} else {
mHost = host;
mPort = port;
}
// A subtle check we don't enter this method more than once for the socket
// transport lifetime. Disable on TSan builds to prevent race checking, we
// don't want an atomic here for perf reasons!
#ifndef MOZ_TSAN
MOZ_ASSERT(!mPortRemappingApplied);
#endif // !MOZ_TSAN
if (proxyInfo) {
mHttpsProxy = proxyInfo->IsHTTPS();
}
const char* proxyType = nullptr;
mProxyInfo = proxyInfo;
if (proxyInfo) {
mProxyPort = proxyInfo->Port();
mProxyHost = proxyInfo->Host();
// grab proxy type (looking for "socks" for example)
proxyType = proxyInfo->Type();
if (proxyType && (proxyInfo->IsHTTP() || proxyInfo->IsHTTPS() ||
proxyInfo->IsDirect() || !strcmp(proxyType, "unknown"))) {
proxyType = nullptr;
}
}
SOCKET_LOG1(
("nsSocketTransport::Init [this=%p host=%s:%hu origin=%s:%d "
"proxy=%s:%hu]\n",
this, mHost.get(), mPort, mOriginHost.get(), mOriginPort,
mProxyHost.get(), mProxyPort));
// include proxy type as a socket type if proxy type is not "http"
uint32_t typeCount = types.Length() + (proxyType != nullptr);
if (!typeCount) return NS_OK;
// if we have socket types, then the socket provider service had
// better exist!
nsresult rv;
nsCOMPtr<nsISocketProviderService> spserv =
nsSocketProviderService::GetOrCreate();
if (!mTypes.SetCapacity(typeCount, fallible)) {
return NS_ERROR_OUT_OF_MEMORY;
}
// now verify that each socket type has a registered socket provider.
for (uint32_t i = 0, type = 0; i < typeCount; ++i) {
// store socket types
if (i == 0 && proxyType) {
mTypes.AppendElement(proxyType);
} else {
mTypes.AppendElement(types[type++]);
}
nsCOMPtr<nsISocketProvider> provider;
rv = spserv->GetSocketProvider(mTypes[i].get(), getter_AddRefs(provider));
if (NS_FAILED(rv)) {
NS_WARNING("no registered socket provider");
return rv;
}
// note if socket type corresponds to a transparent proxy
// XXX don't hardcode SOCKS here (use proxy info's flags instead).
if (mTypes[i].EqualsLiteral("socks") || mTypes[i].EqualsLiteral("socks4")) {
mProxyTransparent = true;
if (proxyInfo->Flags() & nsIProxyInfo::TRANSPARENT_PROXY_RESOLVES_HOST) {
// we want the SOCKS layer to send the hostname
// and port to the proxy and let it do the DNS.
mProxyTransparentResolvesHost = true;
}
}
}
return NS_OK;
}
#if defined(XP_UNIX)
nsresult nsSocketTransport::InitWithFilename(const char* filename) {
return InitWithName(filename, strlen(filename));
}
nsresult nsSocketTransport::InitWithName(const char* name, size_t length) {
if (length > sizeof(mNetAddr.local.path) - 1) {
return NS_ERROR_FILE_NAME_TOO_LONG;
}
if (!name[0] && length > 1) {
// name is abstract address name that is supported on Linux only
# if defined(XP_LINUX)
mHost.Assign(name + 1, length - 1);
# else
return NS_ERROR_SOCKET_ADDRESS_NOT_SUPPORTED;
# endif
} else {
// The name isn't abstract socket address. So this is Unix domain
// socket that has file path.
mHost.Assign(name, length);
}
mPort = 0;
mNetAddr.local.family = AF_LOCAL;
memcpy(mNetAddr.local.path, name, length);
mNetAddr.local.path[length] = '\0';
mNetAddrIsSet = true;
return NS_OK;
}
#endif
nsresult nsSocketTransport::InitWithConnectedSocket(PRFileDesc* fd,
const NetAddr* addr) {
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
char buf[kNetAddrMaxCStrBufSize];
addr->ToStringBuffer(buf, sizeof(buf));
mHost.Assign(buf);
uint16_t port;
if (addr->raw.family == AF_INET) {
port = addr->inet.port;
} else if (addr->raw.family == AF_INET6) {
port = addr->inet6.port;
} else {
port = 0;
}
mPort = ntohs(port);
memcpy(&mNetAddr, addr, sizeof(NetAddr));
mPollFlags = (PR_POLL_READ | PR_POLL_WRITE | PR_POLL_EXCEPT);
mState = STATE_TRANSFERRING;
SetSocketName(fd);
mNetAddrIsSet = true;
{
MutexAutoLock lock(mLock);
NS_ASSERTION(!mFD.IsInitialized(), "already initialized");
mFD = fd;
mFDref = 1;
mFDconnected = true;
mPollTimeout = mTimeouts[TIMEOUT_READ_WRITE];
}
// make sure new socket is non-blocking
PRSocketOptionData opt;
opt.option = PR_SockOpt_Nonblocking;
opt.value.non_blocking = true;
PR_SetSocketOption(fd, &opt);
SOCKET_LOG(
("nsSocketTransport::InitWithConnectedSocket [this=%p addr=%s:%hu]\n",
this, mHost.get(), mPort));
// jump to InitiateSocket to get ourselves attached to the STS poll list.
return PostEvent(MSG_RETRY_INIT_SOCKET);
}
nsresult nsSocketTransport::InitWithConnectedSocket(
PRFileDesc* aFD, const NetAddr* aAddr, nsIInterfaceRequestor* aCallbacks) {
{
MutexAutoLock lock(mLock);
mCallbacks = aCallbacks;
}
return InitWithConnectedSocket(aFD, aAddr);
}
nsresult nsSocketTransport::PostEvent(uint32_t type, nsresult status,
nsISupports* param,
std::function<void()>&& task) {
SOCKET_LOG(("nsSocketTransport::PostEvent [this=%p type=%u status=%" PRIx32
" param=%p]\n",
this, type, static_cast<uint32_t>(status), param));
nsCOMPtr<nsIRunnable> event =
new nsSocketEvent(this, type, status, param, std::move(task));
if (!event) return NS_ERROR_OUT_OF_MEMORY;
return mSocketTransportService->Dispatch(event, NS_DISPATCH_NORMAL);
}
void nsSocketTransport::SendStatus(nsresult status) {
SOCKET_LOG1(("nsSocketTransport::SendStatus [this=%p status=%" PRIx32 "]\n",
this, static_cast<uint32_t>(status)));
nsCOMPtr<nsITransportEventSink> sink;
uint64_t progress;
{
MutexAutoLock lock(mLock);
sink = mEventSink;
switch (status) {
case NS_NET_STATUS_SENDING_TO:
progress = mOutput->ByteCount(lock);
break;
case NS_NET_STATUS_RECEIVING_FROM:
progress = mInput->ByteCount(lock);
break;
default:
progress = 0;
break;
}
}
if (sink) {
sink->OnTransportStatus(this, status, progress, -1);
}
}
nsresult nsSocketTransport::ResolveHost() {
SOCKET_LOG((
"nsSocketTransport::ResolveHost [this=%p %s:%d%s] "
"mProxyTransparentResolvesHost=%d\n",
this, SocketHost().get(), SocketPort(),
mConnectionFlags & nsSocketTransport::BYPASS_CACHE ? " bypass cache" : "",
mProxyTransparentResolvesHost));
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
nsresult rv;
if (!mProxyHost.IsEmpty()) {
if (!mProxyTransparent || mProxyTransparentResolvesHost) {
#if defined(XP_UNIX)
MOZ_ASSERT(!mNetAddrIsSet || mNetAddr.raw.family != AF_LOCAL,
"Unix domain sockets can't be used with proxies");
#endif
// When not resolving mHost locally, we still want to ensure that
// it only contains valid characters. See bug 304904 for details.
// Sometimes the end host is not yet known and mHost is *
if (!net_IsValidDNSHost(mHost) && !mHost.EqualsLiteral("*")) {
SOCKET_LOG((" invalid hostname %s\n", mHost.get()));
return NS_ERROR_UNKNOWN_HOST;
}
}
if (mProxyTransparentResolvesHost) {
// Name resolution is done on the server side. Just pretend
// client resolution is complete, this will get picked up later.
// since we don't need to do DNS now, we bypass the resolving
// step by initializing mNetAddr to an empty address, but we
// must keep the port. The SOCKS IO layer will use the hostname
// we send it when it's created, rather than the empty address
// we send with the connect call.
mState = STATE_RESOLVING;
mNetAddr.raw.family = AF_INET;
mNetAddr.inet.port = htons(SocketPort());
mNetAddr.inet.ip = htonl(INADDR_ANY);
return PostEvent(MSG_DNS_LOOKUP_COMPLETE, NS_OK, nullptr);
}
}
if (mExternalDNSResolution) {
MOZ_ASSERT(mDNSRecord);
mState = STATE_RESOLVING;
return PostEvent(MSG_DNS_LOOKUP_COMPLETE, NS_OK, nullptr);
}
nsCOMPtr<nsIDNSService> dns = nullptr;
auto initTask = [&dns]() { dns = do_GetService(kDNSServiceCID); };
if (!NS_IsMainThread()) {
// Forward to the main thread synchronously.
RefPtr<nsIThread> mainThread = do_GetMainThread();
if (!mainThread) {
return NS_ERROR_FAILURE;
}
SyncRunnable::DispatchToThread(
mainThread,
NS_NewRunnableFunction("nsSocketTransport::ResolveHost->GetDNSService",
initTask));
} else {
initTask();
}
if (!dns) {
return NS_ERROR_FAILURE;
}
mResolving = true;
nsIDNSService::DNSFlags dnsFlags = nsIDNSService::RESOLVE_DEFAULT_FLAGS;
if (mConnectionFlags & nsSocketTransport::BYPASS_CACHE) {
dnsFlags = nsIDNSService::RESOLVE_BYPASS_CACHE;
}
if (mConnectionFlags & nsSocketTransport::REFRESH_CACHE) {
dnsFlags = nsIDNSService::RESOLVE_REFRESH_CACHE;
}
if (mConnectionFlags & nsSocketTransport::DISABLE_IPV6) {
dnsFlags |= nsIDNSService::RESOLVE_DISABLE_IPV6;
}
if (mConnectionFlags & nsSocketTransport::DISABLE_IPV4) {
dnsFlags |= nsIDNSService::RESOLVE_DISABLE_IPV4;
}
if (mConnectionFlags & nsSocketTransport::DISABLE_TRR) {
dnsFlags |= nsIDNSService::RESOLVE_DISABLE_TRR;
}
if (mConnectionFlags & nsSocketTransport::USE_IP_HINT_ADDRESS) {
dnsFlags |= nsIDNSService::RESOLVE_IP_HINT;
}
dnsFlags |= nsIDNSService::GetFlagsFromTRRMode(
nsISocketTransport::GetTRRModeFromFlags(mConnectionFlags));
// When we get here, we are not resolving using any configured proxy likely
// because of individual proxy setting on the request or because the host is
// excluded from proxying. Hence, force resolution despite global proxy-DNS
// configuration.
dnsFlags |= nsIDNSService::RESOLVE_IGNORE_SOCKS_DNS;
NS_ASSERTION(!(dnsFlags & nsIDNSService::RESOLVE_DISABLE_IPV6) ||
!(dnsFlags & nsIDNSService::RESOLVE_DISABLE_IPV4),
"Setting both RESOLVE_DISABLE_IPV6 and RESOLVE_DISABLE_IPV4");
SendStatus(NS_NET_STATUS_RESOLVING_HOST);
if (!SocketHost().Equals(mOriginHost)) {
SOCKET_LOG(("nsSocketTransport %p origin %s doing dns for %s\n", this,
mOriginHost.get(), SocketHost().get()));
}
rv =
dns->AsyncResolveNative(SocketHost(), nsIDNSService::RESOLVE_TYPE_DEFAULT,
dnsFlags, nullptr, this, mSocketTransportService,
mOriginAttributes, getter_AddRefs(mDNSRequest));
if (NS_SUCCEEDED(rv)) {
SOCKET_LOG((" advancing to STATE_RESOLVING\n"));
mState = STATE_RESOLVING;
}
return rv;
}
nsresult nsSocketTransport::BuildSocket(PRFileDesc*& fd, bool& proxyTransparent,
bool& usingSSL) {
SOCKET_LOG(("nsSocketTransport::BuildSocket [this=%p]\n", this));
nsresult rv = NS_OK;
proxyTransparent = false;
usingSSL = false;
if (mTypes.IsEmpty()) {
fd = PR_OpenTCPSocket(mNetAddr.raw.family);
if (!fd) {
SOCKET_LOG((" error creating TCP nspr socket [rv=%" PRIx32 "]\n",
static_cast<uint32_t>(rv)));
return NS_ERROR_OUT_OF_MEMORY;
}
return NS_OK;
}
#if defined(XP_UNIX)
MOZ_ASSERT(!mNetAddrIsSet || mNetAddr.raw.family != AF_LOCAL,
"Unix domain sockets can't be used with socket types");
#endif
fd = nullptr;
uint32_t controlFlags = 0;
if (mProxyTransparentResolvesHost) {
controlFlags |= nsISocketProvider::PROXY_RESOLVES_HOST;
}
if (mConnectionFlags & nsISocketTransport::ANONYMOUS_CONNECT) {
controlFlags |= nsISocketProvider::ANONYMOUS_CONNECT;
}
if (mConnectionFlags & nsISocketTransport::NO_PERMANENT_STORAGE) {
controlFlags |= nsISocketProvider::NO_PERMANENT_STORAGE;
}
if (mConnectionFlags & nsISocketTransport::BE_CONSERVATIVE) {
controlFlags |= nsISocketProvider::BE_CONSERVATIVE;
}
if (mConnectionFlags & nsISocketTransport::DONT_TRY_ECH) {
controlFlags |= nsISocketProvider::DONT_TRY_ECH;
}
if (mConnectionFlags & nsISocketTransport::IS_RETRY) {
controlFlags |= nsISocketProvider::IS_RETRY;
}
if (mConnectionFlags &
nsISocketTransport::ANONYMOUS_CONNECT_ALLOW_CLIENT_CERT) {
controlFlags |= nsISocketProvider::ANONYMOUS_CONNECT_ALLOW_CLIENT_CERT;
}
if (mConnectionFlags & nsISocketTransport::IS_SPECULATIVE_CONNECTION) {
controlFlags |= nsISocketProvider::IS_SPECULATIVE_CONNECTION;
}
if (mResolvedByTRR) {
controlFlags |= nsISocketProvider::USED_PRIVATE_DNS;
}
// by setting host to mOriginHost, instead of mHost we send the
// SocketProvider (e.g. PSM) the origin hostname but can still do DNS
// on an explicit alternate service host name
const char* host = mOriginHost.get();
int32_t port = (int32_t)mOriginPort;
nsCOMPtr<nsISocketProviderService> spserv =
nsSocketProviderService::GetOrCreate();
nsCOMPtr<nsIProxyInfo> proxyInfo = mProxyInfo;
uint32_t i;
for (i = 0; i < mTypes.Length(); ++i) {
nsCOMPtr<nsISocketProvider> provider;
SOCKET_LOG((" pushing io layer [%u:%s]\n", i, mTypes[i].get()));
rv = spserv->GetSocketProvider(mTypes[i].get(), getter_AddRefs(provider));
if (NS_FAILED(rv)) break;
nsCOMPtr<nsITLSSocketControl> tlsSocketControl;
if (i == 0) {
// if this is the first type, we'll want the
// service to allocate a new socket
// Most layers _ESPECIALLY_ PSM want the origin name here as they
// will use it for secure checks, etc.. and any connection management
// differences between the origin name and the routed name can be
// taken care of via DNS. However, SOCKS is a special case as there is
// no DNS. in the case of SOCKS and PSM the PSM is a separate layer
// and receives the origin name.
const char* socketProviderHost = host;
int32_t socketProviderPort = port;
if (mProxyTransparentResolvesHost &&
(mTypes[0].EqualsLiteral("socks") ||
mTypes[0].EqualsLiteral("socks4"))) {
SOCKET_LOG(("SOCKS %d Host/Route override: %s:%d -> %s:%d\n",
mHttpsProxy, socketProviderHost, socketProviderPort,
mHost.get(), mPort));
socketProviderHost = mHost.get();
socketProviderPort = mPort;
}
// when https proxying we want to just connect to the proxy as if
// it were the end host (i.e. expect the proxy's cert)
rv = provider->NewSocket(
mNetAddr.raw.family,
mHttpsProxy ? mProxyHost.get() : socketProviderHost,
mHttpsProxy ? mProxyPort : socketProviderPort, proxyInfo,
mOriginAttributes, controlFlags, mTlsFlags, &fd,
getter_AddRefs(tlsSocketControl));
if (NS_SUCCEEDED(rv) && !fd) {
MOZ_ASSERT_UNREACHABLE(
"NewSocket succeeded but failed to "
"create a PRFileDesc");
rv = NS_ERROR_UNEXPECTED;
}
} else {
// the socket has already been allocated,
// so we just want the service to add itself
// to the stack (such as pushing an io layer)
rv = provider->AddToSocket(mNetAddr.raw.family, host, port, proxyInfo,
mOriginAttributes, controlFlags, mTlsFlags, fd,
getter_AddRefs(tlsSocketControl));
}
// controlFlags = 0; not used below this point...
if (NS_FAILED(rv)) break;
// if the service was ssl or starttls, we want to hold onto the socket
// info
bool isSSL = mTypes[i].EqualsLiteral("ssl");
if (isSSL || mTypes[i].EqualsLiteral("starttls")) {
// remember security info
{
MutexAutoLock lock(mLock);
mTLSSocketControl = tlsSocketControl;
SOCKET_LOG((" [tlsSocketControl=%p callbacks=%p]\n",
mTLSSocketControl.get(), mCallbacks.get()));
}
// remember if socket type is SSL so we can ProxyStartSSL if need be.
usingSSL = isSSL;
} else if (mTypes[i].EqualsLiteral("socks") ||
mTypes[i].EqualsLiteral("socks4")) {
// since socks is transparent, any layers above
// it do not have to worry about proxy stuff
proxyInfo = nullptr;
proxyTransparent = true;
}
}
if (