gecko-dev/netwerk/base/nsSocketTransport2.cpp
Valentin Gosu 1a1f42da37 Bug 1714307 - Run modernize-use-default-member-init --fix check on netwerk r=necko-reviewers,kershaw
This changeset is the result of adding modernize-use-default-member-init to
tools/clang-tidy/config.yaml then proceeding to run
`./mach static-analysis check netwerk/ --fix`
I then went through the resulting fix and manually updated all of the member
variables which were missed due to them having a non-trivial constructor.

Note that the tool was only run on Linux, so code that only runs on some
platforms may have been missed.

The member variables that are still initialized in the contructor definition
are:
  - bitfields (not all currently supported compilers allow default-member-init
  - variables that are initialized via a parameter
  - variables that use code not visible in the header file

There are a few advantages to landing this change:
- fewer lines of code - now declaration is in the same place as initialization
  this also makes it easier to see when looking at the header.
- it makes it harder to miss initializing a member when adding a new contructor
- variables that depend on an include guard look much nicer now

Additionally I removed some unnecessary reinitialization of NetAddr members
(it has a constructor that does that now), and changed nsWifiScannerDBus to
use the thread-safe strtok_r instead of strtok.

Differential Revision: https://phabricator.services.mozilla.com/D116980
2021-06-11 07:10:41 +00:00

3313 lines
103 KiB
C++

/* -*- 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 "nsSocketTransport2.h"
#include "mozilla/Attributes.h"
#include "mozilla/SyncRunnable.h"
#include "mozilla/Telemetry.h"
#include "nsIOService.h"
#include "nsStreamUtils.h"
#include "nsNetSegmentUtils.h"
#include "nsNetAddr.h"
#include "nsTransportUtils.h"
#include "nsProxyInfo.h"
#include "nsNetCID.h"
#include "nsNetUtil.h"
#include "nsCOMPtr.h"
#include "plstr.h"
#include "prerr.h"
#include "IOActivityMonitor.h"
#include "NSSErrorsService.h"
#include "mozilla/dom/ToJSValue.h"
#include "mozilla/net/NeckoChild.h"
#include "nsThreadUtils.h"
#include "nsSocketProviderService.h"
#include "nsISocketProvider.h"
#include "nsISSLSocketControl.h"
#include "nsIPipe.h"
#include "nsIClassInfoImpl.h"
#include "nsURLHelper.h"
#include "nsIDNSService.h"
#include "nsIDNSRecord.h"
#include "nsIDNSByTypeRecord.h"
#include "nsICancelable.h"
#include "NetworkDataCountLayer.h"
#include "QuicSocketControl.h"
#include <algorithm>
#include "sslexp.h"
#include "mozilla/net/SSLTokensCache.h"
#include "mozilla/StaticPrefs_network.h"
#include "nsPrintfCString.h"
#include "xpcpublic.h"
#if defined(FUZZING)
# include "FuzzyLayer.h"
# include "FuzzySecurityInfo.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)
: Runnable("net::nsSocketEvent"),
mTransport(transport),
mType(type),
mStatus(status),
mParam(param) {}
NS_IMETHOD Run() override {
mTransport->OnSocketEvent(mType, mStatus, mParam);
return NS_OK;
}
private:
RefPtr<nsSocketTransport> mTransport;
uint32_t mType;
nsresult mStatus;
nsCOMPtr<nsISupports> mParam;
};
//-----------------------------------------------------------------------------
//#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::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);
} 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::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);
} 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(this),
mOutput(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() {
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);
}
// 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");
NS_ASSERTION(!mFD.IsInitialized(), "already initialized");
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);
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,
nsISupports* aSecInfo) {
mSecInfo = aSecInfo;
return InitWithConnectedSocket(aFD, aAddr);
}
nsresult nsSocketTransport::PostEvent(uint32_t type, nsresult status,
nsISupports* param) {
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);
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();
break;
case NS_NET_STATUS_RECEIVING_FROM:
progress = mInput.ByteCount();
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));
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_IsValidHostName(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,
new SyncRunnable(NS_NewRunnableFunction(
"nsSocketTransport::ResolveHost->GetDNSService", initTask)));
} else {
initTask();
}
if (!dns) {
return NS_ERROR_FAILURE;
}
mResolving = true;
uint32_t dnsFlags = 0;
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::ANONYMOUS_CONNECT_ALLOW_CLIENT_CERT) {
controlFlags |= nsISocketProvider::ANONYMOUS_CONNECT_ALLOW_CLIENT_CERT;
}
// 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<nsISupports> secinfo;
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(secinfo));
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(secinfo));
}
// 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 and give notification callbacks to PSM...
nsCOMPtr<nsIInterfaceRequestor> callbacks;
{
MutexAutoLock lock(mLock);
mSecInfo = secinfo;
callbacks = mCallbacks;
SOCKET_LOG((" [secinfo=%p callbacks=%p]\n", mSecInfo.get(),
mCallbacks.get()));
}
// don't call into PSM while holding mLock!!
nsCOMPtr<nsISSLSocketControl> secCtrl(do_QueryInterface(secinfo));
if (secCtrl) secCtrl->SetNotificationCallbacks(callbacks);
// 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 (NS_FAILED(rv)) {
SOCKET_LOG((" error pushing io layer [%u:%s rv=%" PRIx32 "]\n", i,
mTypes[i].get(), static_cast<uint32_t>(rv)));
if (fd) {
CloseSocket(
fd, mSocketTransportService->IsTelemetryEnabledAndNotSleepPhase());
}
}
return rv;
}
nsresult nsSocketTransport::InitiateSocket() {
SOCKET_LOG(("nsSocketTransport::InitiateSocket [this=%p]\n", this));
nsresult rv;
bool isLocal;
IsLocal(&isLocal);
if (gIOService->IsNetTearingDown()) {
return NS_ERROR_ABORT;
}
if (gIOService->IsOffline()) {
if (!isLocal) return NS_ERROR_OFFLINE;
} else if (!isLocal) {
#ifdef DEBUG
// all IP networking has to be done from the parent
if (NS_SUCCEEDED(mCondition) && ((mNetAddr.raw.family == AF_INET) ||
(mNetAddr.raw.family == AF_INET6))) {
MOZ_ASSERT(!IsNeckoChild());
}
#endif
if (NS_SUCCEEDED(mCondition) && xpc::AreNonLocalConnectionsDisabled() &&
!(mNetAddr.IsIPAddrAny() || mNetAddr.IsIPAddrLocal() ||
mNetAddr.IsIPAddrShared())) {
nsAutoCString ipaddr;
RefPtr<nsNetAddr> netaddr = new nsNetAddr(&mNetAddr);
netaddr->GetAddress(ipaddr);
fprintf_stderr(
stderr,
"FATAL ERROR: Non-local network connections are disabled and a "
"connection "
"attempt to %s (%s) was made.\nYou should only access hostnames "
"available via the test networking proxy (if running mochitests) "
"or from a test-specific httpd.js server (if running xpcshell "
"tests). "
"Browser services should be disabled or redirected to a local "
"server.\n",
mHost.get(), ipaddr.get());
MOZ_CRASH("Attempting to connect to non-local address!");
}
}
// Hosts/Proxy Hosts that are Local IP Literals should not be speculatively
// connected - Bug 853423.
if (mConnectionFlags & nsISocketTransport::DISABLE_RFC1918 &&
mNetAddr.IsIPAddrLocal()) {
if (SOCKET_LOG_ENABLED()) {
nsAutoCString netAddrCString;
netAddrCString.SetLength(kIPv6CStrBufSize);
if (!mNetAddr.ToStringBuffer(netAddrCString.BeginWriting(),
kIPv6CStrBufSize)) {
netAddrCString = "<IP-to-string failed>"_ns;
}
SOCKET_LOG(
("nsSocketTransport::InitiateSocket skipping "
"speculative connection for host [%s:%d] proxy "
"[%s:%d] with Local IP address [%s]",
mHost.get(), mPort, mProxyHost.get(), mProxyPort,
netAddrCString.get()));
}
mCondition = NS_ERROR_CONNECTION_REFUSED;
OnSocketDetached(nullptr);
return mCondition;
}
//
// find out if it is going to be ok to attach another socket to the STS.
// if not then we have to wait for the STS to tell us that it is ok.
// the notification is asynchronous, which means that when we could be
// in a race to call AttachSocket once notified. for this reason, when
// we get notified, we just re-enter this function. as a result, we are
// sure to ask again before calling AttachSocket. in this way we deal
// with the race condition. though it isn't the most elegant solution,
// it is far simpler than trying to build a system that would guarantee
// FIFO ordering (which wouldn't even be that valuable IMO). see bug
// 194402 for more info.
//
if (!mSocketTransportService->CanAttachSocket()) {
nsCOMPtr<nsIRunnable> event =
new nsSocketEvent(this, MSG_RETRY_INIT_SOCKET);
if (!event) return NS_ERROR_OUT_OF_MEMORY;
return mSocketTransportService->NotifyWhenCanAttachSocket(event);
}
//
// if we already have a connected socket, then just attach and return.
//
if (mFD.IsInitialized()) {
rv = mSocketTransportService->AttachSocket(mFD, this);
if (NS_SUCCEEDED(rv)) mAttached = true;
return rv;
}
//
// create new socket fd, push io layers, etc.
//
PRFileDesc* fd;
bool proxyTransparent;
bool usingSSL;
rv = BuildSocket(fd, proxyTransparent, usingSSL);
if (NS_FAILED(rv)) {
SOCKET_LOG(
(" BuildSocket failed [rv=%" PRIx32 "]\n", static_cast<uint32_t>(rv)));
return rv;
}
// create proxy via IOActivityMonitor
IOActivityMonitor::MonitorSocket(fd);
#ifdef FUZZING
if (StaticPrefs::fuzzing_necko_enabled()) {
rv = AttachFuzzyIOLayer(fd);
if (NS_FAILED(rv)) {
SOCKET_LOG(("Failed to attach fuzzing IOLayer [rv=%" PRIx32 "].\n",
static_cast<uint32_t>(rv)));
return rv;
}
SOCKET_LOG(("Successfully attached fuzzing IOLayer.\n"));
if (usingSSL) {
mSecInfo = static_cast<nsISupports*>(
static_cast<nsISSLSocketControl*>(new FuzzySecurityInfo()));
}
}
#endif
PRStatus status;
// Make the socket non-blocking...
PRSocketOptionData opt;
opt.option = PR_SockOpt_Nonblocking;
opt.value.non_blocking = true;
status = PR_SetSocketOption(fd, &opt);
NS_ASSERTION(status == PR_SUCCESS, "unable to make socket non-blocking");
if (mReuseAddrPort) {
SOCKET_LOG((" Setting port/addr reuse socket options\n"));
// Set ReuseAddr for TCP sockets to enable having several
// sockets bound to same local IP and port
PRSocketOptionData opt_reuseaddr;
opt_reuseaddr.option = PR_SockOpt_Reuseaddr;
opt_reuseaddr.value.reuse_addr = PR_TRUE;
status = PR_SetSocketOption(fd, &opt_reuseaddr);
if (status != PR_SUCCESS) {
SOCKET_LOG((" Couldn't set reuse addr socket option: %d\n", status));
}
// And also set ReusePort for platforms supporting this socket option
PRSocketOptionData opt_reuseport;
opt_reuseport.option = PR_SockOpt_Reuseport;
opt_reuseport.value.reuse_port = PR_TRUE;
status = PR_SetSocketOption(fd, &opt_reuseport);
if (status != PR_SUCCESS &&
PR_GetError() != PR_OPERATION_NOT_SUPPORTED_ERROR) {
SOCKET_LOG((" Couldn't set reuse port socket option: %d\n", status));
}
}
// disable the nagle algorithm - if we rely on it to coalesce writes into
// full packets the final packet of a multi segment POST/PUT or pipeline
// sequence is delayed a full rtt
opt.option = PR_SockOpt_NoDelay;
opt.value.no_delay = true;
PR_SetSocketOption(fd, &opt);
// if the network.tcp.sendbuffer preference is set, use it to size SO_SNDBUF
// The Windows default of 8KB is too small and as of vista sp1, autotuning
// only applies to receive window
int32_t sndBufferSize;
mSocketTransportService->GetSendBufferSize(&sndBufferSize);
if (sndBufferSize > 0) {
opt.option = PR_SockOpt_SendBufferSize;
opt.value.send_buffer_size = sndBufferSize;
PR_SetSocketOption(fd, &opt);
}
if (mQoSBits) {
opt.option = PR_SockOpt_IpTypeOfService;
opt.value.tos = mQoSBits;
PR_SetSocketOption(fd, &opt);
}
#if defined(XP_WIN)
// The linger is turned off by default. This is not a hard close, but
// closesocket should return immediately and operating system tries to send
// remaining data for certain, implementation specific, amount of time.
// https://msdn.microsoft.com/en-us/library/ms739165.aspx
//
// Turn the linger option on an set the interval to 0. This will cause hard
// close of the socket.
opt.option = PR_SockOpt_Linger;
opt.value.linger.polarity = 1;
opt.value.linger.linger = 0;
PR_SetSocketOption(fd, &opt);
#endif
// inform socket transport about this newly created socket...
rv = mSocketTransportService->AttachSocket(fd, this);
if (NS_FAILED(rv)) {
CloseSocket(fd,
mSocketTransportService->IsTelemetryEnabledAndNotSleepPhase());
return rv;
}
mAttached = true;
// assign mFD so that we can properly handle OnSocketDetached before we've
// established a connection.
{
MutexAutoLock lock(mLock);
mFD = fd;
mFDref = 1;
mFDconnected = false;
mPollTimeout = mTimeouts[TIMEOUT_CONNECT];
}
SOCKET_LOG((" advancing to STATE_CONNECTING\n"));
mState = STATE_CONNECTING;
SendStatus(NS_NET_STATUS_CONNECTING_TO);
if (SOCKET_LOG_ENABLED()) {
char buf[kNetAddrMaxCStrBufSize];
mNetAddr.ToStringBuffer(buf, sizeof(buf));
SOCKET_LOG((" trying address: %s\n", buf));
}
//
// Initiate the connect() to the host...
//
PRNetAddr prAddr;
memset(&prAddr, 0, sizeof(prAddr));
{
if (mBindAddr) {
MutexAutoLock lock(mLock);
NetAddrToPRNetAddr(mBindAddr.get(), &prAddr);
status = PR_Bind(fd, &prAddr);
if (status != PR_SUCCESS) {
return NS_ERROR_FAILURE;
}
mBindAddr = nullptr;
}
}
NetAddrToPRNetAddr(&mNetAddr, &prAddr);
#ifdef XP_WIN
// Find the real tcp socket and set non-blocking once again!
// Bug 1158189.
PRFileDesc* bottom = PR_GetIdentitiesLayer(fd, PR_NSPR_IO_LAYER);
if (bottom) {
PROsfd osfd = PR_FileDesc2NativeHandle(bottom);
u_long nonblocking = 1;
if (ioctlsocket(osfd, FIONBIO, &nonblocking) != 0) {
NS_WARNING("Socket could not be set non-blocking!");
return NS_ERROR_FAILURE;
}
}
#endif
nsCOMPtr<nsISSLSocketControl> secCtrl = do_QueryInterface(mSecInfo);
if (secCtrl) {
if (!mEchConfig.IsEmpty() &&
!(mConnectionFlags & (DONT_TRY_ECH | BE_CONSERVATIVE))) {
SOCKET_LOG(("nsSocketTransport::InitiateSocket set echconfig."));
rv = secCtrl->SetEchConfig(mEchConfig);
if (NS_FAILED(rv)) {
return rv;
}
mEchConfigUsed = true;
}
}
// We use PRIntervalTime here because we need
// nsIOService::LastOfflineStateChange time and
// nsIOService::LastConectivityChange time to be atomic.
PRIntervalTime connectStarted = 0;
if (gSocketTransportService->IsTelemetryEnabledAndNotSleepPhase()) {
connectStarted = PR_IntervalNow();
}
if (Telemetry::CanRecordPrereleaseData() ||
Telemetry::CanRecordReleaseData()) {
if (NS_FAILED(AttachNetworkDataCountLayer(fd))) {
SOCKET_LOG(
("nsSocketTransport::InitiateSocket "
"AttachNetworkDataCountLayer failed [this=%p]\n",
this));
}
}
bool connectCalled = true; // This is only needed for telemetry.
status = PR_Connect(fd, &prAddr, NS_SOCKET_CONNECT_TIMEOUT);
PRErrorCode code = PR_GetError();
if (status == PR_SUCCESS) {
PR_SetFDInheritable(fd, false);
}
if (gSocketTransportService->IsTelemetryEnabledAndNotSleepPhase() &&
connectStarted && connectCalled) {
SendPRBlockingTelemetry(
connectStarted, Telemetry::PRCONNECT_BLOCKING_TIME_NORMAL,
Telemetry::PRCONNECT_BLOCKING_TIME_SHUTDOWN,
Telemetry::PRCONNECT_BLOCKING_TIME_CONNECTIVITY_CHANGE,
Telemetry::PRCONNECT_BLOCKING_TIME_LINK_CHANGE,
Telemetry::PRCONNECT_BLOCKING_TIME_OFFLINE);
}
if (status == PR_SUCCESS) {
//
// we are connected!
//
OnSocketConnected();
} else {
#if defined(TEST_CONNECT_ERRORS)
code = RandomizeConnectError(code);
#endif
//
// If the PR_Connect(...) would block, then poll for a connection.
//
if ((PR_WOULD_BLOCK_ERROR == code) || (PR_IN_PROGRESS_ERROR == code)) {
mPollFlags = (PR_POLL_EXCEPT | PR_POLL_WRITE);
//
// If the socket is already connected, then return success...
//
} else if (PR_IS_CONNECTED_ERROR == code) {
//
// we are connected!
//
OnSocketConnected();
if (mSecInfo && !mProxyHost.IsEmpty() && proxyTransparent && usingSSL) {
// if the connection phase is finished, and the ssl layer has
// been pushed, and we were proxying (transparently; ie. nothing
// has to happen in the protocol layer above us), it's time for
// the ssl to start doing it's thing.
nsCOMPtr<nsISSLSocketControl> secCtrl = do_QueryInterface(mSecInfo);
if (secCtrl) {
SOCKET_LOG((" calling ProxyStartSSL()\n"));
secCtrl->ProxyStartSSL();
}
// XXX what if we were forced to poll on the socket for a successful
// connection... wouldn't we need to call ProxyStartSSL after a call
// to PR_ConnectContinue indicates that we are connected?
//
// XXX this appears to be what the old socket transport did. why
// isn't this broken?
}
}
//
// A SOCKS request was rejected; get the actual error code from
// the OS error
//
else if (PR_UNKNOWN_ERROR == code && mProxyTransparent &&
!mProxyHost.IsEmpty()) {
code = PR_GetOSError();
rv = ErrorAccordingToNSPR(code);
}
//
// The connection was refused...
//
else {
if (gSocketTransportService->IsTelemetryEnabledAndNotSleepPhase() &&
connectStarted && connectCalled) {
SendPRBlockingTelemetry(
connectStarted, Telemetry::PRCONNECT_FAIL_BLOCKING_TIME_NORMAL,
Telemetry::PRCONNECT_FAIL_BLOCKING_TIME_SHUTDOWN,
Telemetry::PRCONNECT_FAIL_BLOCKING_TIME_CONNECTIVITY_CHANGE,
Telemetry::PRCONNECT_FAIL_BLOCKING_TIME_LINK_CHANGE,
Telemetry::PRCONNECT_FAIL_BLOCKING_TIME_OFFLINE);
}
rv = ErrorAccordingToNSPR(code);
if ((rv == NS_ERROR_CONNECTION_REFUSED) && !mProxyHost.IsEmpty()) {
rv = NS_ERROR_PROXY_CONNECTION_REFUSED;
}
}
}
return rv;
}
bool nsSocketTransport::RecoverFromError() {
NS_ASSERTION(NS_FAILED(mCondition), "there should be something wrong");
SOCKET_LOG(
("nsSocketTransport::RecoverFromError [this=%p state=%x cond=%" PRIx32
"]\n",
this, mState, static_cast<uint32_t>(mCondition)));
if (mDoNotRetryToConnect) {
SOCKET_LOG(
("nsSocketTransport::RecoverFromError do not retry because "
"mDoNotRetryToConnect is set [this=%p]\n",
this));
return false;
}
#if defined(XP_UNIX)
// Unix domain connections don't have multiple addresses to try,
// so the recovery techniques here don't apply.
if (mNetAddrIsSet && mNetAddr.raw.family == AF_LOCAL) return false;
#endif
if ((mConnectionFlags & nsSocketTransport::USE_IP_HINT_ADDRESS) &&
mCondition == NS_ERROR_UNKNOWN_HOST &&
(mState == MSG_DNS_LOOKUP_COMPLETE || mState == MSG_ENSURE_CONNECT)) {
SOCKET_LOG((" try again without USE_IP_HINT_ADDRESS"));
mConnectionFlags &= ~nsSocketTransport::USE_IP_HINT_ADDRESS;
mState = STATE_CLOSED;
return NS_SUCCEEDED(PostEvent(MSG_ENSURE_CONNECT, NS_OK));
}
// can only recover from errors in these states
if (mState != STATE_RESOLVING && mState != STATE_CONNECTING) {
SOCKET_LOG((" not in a recoverable state"));
return false;
}
nsresult rv;
// OK to check this outside mLock
NS_ASSERTION(!mFDconnected, "socket should not be connected");
// all connection failures need to be reported to DNS so that the next
// time we will use a different address if available.
if (mState == STATE_CONNECTING && mDNSRecord) {
mDNSRecord->ReportUnusable(SocketPort());
}
if (mCondition != NS_ERROR_CONNECTION_REFUSED &&
mCondition != NS_ERROR_PROXY_CONNECTION_REFUSED &&
mCondition != NS_ERROR_NET_TIMEOUT &&
mCondition != NS_ERROR_UNKNOWN_HOST &&
mCondition != NS_ERROR_UNKNOWN_PROXY_HOST) {
SOCKET_LOG((" not a recoverable error %" PRIx32,
static_cast<uint32_t>(mCondition)));
return false;
}
bool tryAgain = false;
if ((mState == STATE_CONNECTING) && mDNSRecord) {
if (mNetAddr.raw.family == AF_INET) {
if (mSocketTransportService->IsTelemetryEnabledAndNotSleepPhase()) {
Telemetry::Accumulate(Telemetry::IPV4_AND_IPV6_ADDRESS_CONNECTIVITY,
UNSUCCESSFUL_CONNECTING_TO_IPV4_ADDRESS);
}
} else if (mNetAddr.raw.family == AF_INET6) {
if (mSocketTransportService->IsTelemetryEnabledAndNotSleepPhase()) {
Telemetry::Accumulate(Telemetry::IPV4_AND_IPV6_ADDRESS_CONNECTIVITY,
UNSUCCESSFUL_CONNECTING_TO_IPV6_ADDRESS);
}
}
}
if (mConnectionFlags & RETRY_WITH_DIFFERENT_IP_FAMILY &&
mCondition == NS_ERROR_UNKNOWN_HOST && mState == STATE_RESOLVING &&
!mProxyTransparentResolvesHost) {
SOCKET_LOG((" trying lookup again with opposite ip family\n"));
mConnectionFlags ^= (DISABLE_IPV6 | DISABLE_IPV4);
mConnectionFlags &= ~RETRY_WITH_DIFFERENT_IP_FAMILY;
// This will tell the consuming half-open to reset preference on the
// connection entry
mResetFamilyPreference = true;
tryAgain = true;
}
// try next ip address only if past the resolver stage...
if (mState == STATE_CONNECTING && mDNSRecord) {
nsresult rv = mDNSRecord->GetNextAddr(SocketPort(), &mNetAddr);
mDNSRecord->IsTRR(&mResolvedByTRR);
if (NS_SUCCEEDED(rv)) {
SOCKET_LOG((" trying again with next ip address\n"));
tryAgain = true;
} else if (mExternalDNSResolution) {
mRetryDnsIfPossible = true;
bool trrEnabled;
mDNSRecord->IsTRR(&trrEnabled);
// Bug 1648147 - If the server responded with `0.0.0.0` or `::` then we
// should intentionally not fallback to regular DNS.
if (trrEnabled && !StaticPrefs::network_trr_fallback_on_zero_response() &&
((mNetAddr.raw.family == AF_INET && mNetAddr.inet.ip == 0) ||
(mNetAddr.raw.family == AF_INET6 && mNetAddr.inet6.ip.u64[0] == 0 &&
mNetAddr.inet6.ip.u64[1] == 0))) {
SOCKET_LOG((" TRR returned 0.0.0.0 and there are no other IPs"));
mRetryDnsIfPossible = false;
}
} else if (mConnectionFlags & RETRY_WITH_DIFFERENT_IP_FAMILY) {
SOCKET_LOG((" failed to connect, trying with opposite ip family\n"));
// Drop state to closed. This will trigger new round of DNS
// resolving bellow.
mState = STATE_CLOSED;
mConnectionFlags ^= (DISABLE_IPV6 | DISABLE_IPV4);
mConnectionFlags &= ~RETRY_WITH_DIFFERENT_IP_FAMILY;
// This will tell the consuming half-open to reset preference on the
// connection entry
mResetFamilyPreference = true;
tryAgain = true;
} else if (!(mConnectionFlags & DISABLE_TRR)) {
bool trrEnabled;
mDNSRecord->IsTRR(&trrEnabled);
// Bug 1648147 - If the server responded with `0.0.0.0` or `::` then we
// should intentionally not fallback to regular DNS.
if (!StaticPrefs::network_trr_fallback_on_zero_response() &&
((mNetAddr.raw.family == AF_INET && mNetAddr.inet.ip == 0) ||
(mNetAddr.raw.family == AF_INET6 && mNetAddr.inet6.ip.u64[0] == 0 &&
mNetAddr.inet6.ip.u64[1] == 0))) {
SOCKET_LOG((" TRR returned 0.0.0.0 and there are no other IPs"));
} else if (trrEnabled) {
uint32_t trrMode = 0;
mDNSRecord->GetEffectiveTRRMode(&trrMode);
// If current trr mode is trr only, we should not retry.
if (trrMode != 3) {
// Drop state to closed. This will trigger a new round of
// DNS resolving. Bypass the cache this time since the
// cached data came from TRR and failed already!
SOCKET_LOG((" failed to connect with TRR enabled, try w/o\n"));
mState = STATE_CLOSED;
mConnectionFlags |= DISABLE_TRR | BYPASS_CACHE | REFRESH_CACHE;
tryAgain = true;
}
}
}
}
// prepare to try again.
if (tryAgain) {
uint32_t msg;
if (mState == STATE_CONNECTING) {
mState = STATE_RESOLVING;
msg = MSG_DNS_LOOKUP_COMPLETE;
} else {
mState = STATE_CLOSED;
msg = MSG_ENSURE_CONNECT;
}
rv = PostEvent(msg, NS_OK);
if (NS_FAILED(rv)) tryAgain = false;
}
return tryAgain;
}
// called on the socket thread only
void nsSocketTransport::OnMsgInputClosed(nsresult reason) {
SOCKET_LOG(("nsSocketTransport::OnMsgInputClosed [this=%p reason=%" PRIx32
"]\n",
this, static_cast<uint32_t>(reason)));
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
mInputClosed = true;
// check if event should affect entire transport
if (NS_FAILED(reason) && (reason != NS_BASE_STREAM_CLOSED)) {
mCondition = reason; // XXX except if NS_FAILED(mCondition), right??
} else if (mOutputClosed) {
mCondition =
NS_BASE_STREAM_CLOSED; // XXX except if NS_FAILED(mCondition), right??
} else {
if (mState == STATE_TRANSFERRING) mPollFlags &= ~PR_POLL_READ;
mInput.OnSocketReady(reason);
}
}
// called on the socket thread only
void nsSocketTransport::OnMsgOutputClosed(nsresult reason) {
SOCKET_LOG(("nsSocketTransport::OnMsgOutputClosed [this=%p reason=%" PRIx32
"]\n",
this, static_cast<uint32_t>(reason)));
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
mOutputClosed = true;
// check if event should affect entire transport
if (NS_FAILED(reason) && (reason != NS_BASE_STREAM_CLOSED)) {
mCondition = reason; // XXX except if NS_FAILED(mCondition), right??
} else if (mInputClosed) {
mCondition =
NS_BASE_STREAM_CLOSED; // XXX except if NS_FAILED(mCondition), right??
} else {
if (mState == STATE_TRANSFERRING) mPollFlags &= ~PR_POLL_WRITE;
mOutput.OnSocketReady(reason);
}
}
void nsSocketTransport::OnSocketConnected() {
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
SOCKET_LOG((" advancing to STATE_TRANSFERRING\n"));
mPollFlags = (PR_POLL_READ | PR_POLL_WRITE | PR_POLL_EXCEPT);
mState = STATE_TRANSFERRING;
// Set the m*AddrIsSet flags only when state has reached TRANSFERRING
// because we need to make sure its value does not change due to failover
mNetAddrIsSet = true;
// assign mFD (must do this within the transport lock), but take care not
// to trample over mFDref if mFD is already set.
{
MutexAutoLock lock(mLock);
NS_ASSERTION(mFD.IsInitialized(), "no socket");
NS_ASSERTION(mFDref == 1, "wrong socket ref count");
SetSocketName(mFD);
mFDconnected = true;
mPollTimeout = mTimeouts[TIMEOUT_READ_WRITE];
}
// Ensure keepalive is configured correctly if previously enabled.
if (mKeepaliveEnabled) {
nsresult rv = SetKeepaliveEnabledInternal(true);
if (NS_WARN_IF(NS_FAILED(rv))) {
SOCKET_LOG((" SetKeepaliveEnabledInternal failed rv[0x%" PRIx32 "]",
static_cast<uint32_t>(rv)));
}
}
SendStatus(NS_NET_STATUS_CONNECTED_TO);
}
void nsSocketTransport::SetSocketName(PRFileDesc* fd) {
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
if (mSelfAddrIsSet) {
return;
}
PRNetAddr prAddr;
memset(&prAddr, 0, sizeof(prAddr));
if (PR_GetSockName(fd, &prAddr) == PR_SUCCESS) {
PRNetAddrToNetAddr(&prAddr, &mSelfAddr);
mSelfAddrIsSet = true;
}
}
PRFileDesc* nsSocketTransport::GetFD_Locked() {
mLock.AssertCurrentThreadOwns();
// mFD is not available to the streams while disconnected.
if (!mFDconnected) return nullptr;
if (mFD.IsInitialized()) mFDref++;
return mFD;
}
class ThunkPRClose : public Runnable {
public:
explicit ThunkPRClose(PRFileDesc* fd)
: Runnable("net::ThunkPRClose"), mFD(fd) {}
NS_IMETHOD Run() override {
nsSocketTransport::CloseSocket(
mFD, gSocketTransportService->IsTelemetryEnabledAndNotSleepPhase());
return NS_OK;
}
private:
PRFileDesc* mFD;
};
void STS_PRCloseOnSocketTransport(PRFileDesc* fd, bool lingerPolarity,
int16_t lingerTimeout) {
if (gSocketTransportService) {
// Can't PR_Close() a socket off STS thread. Thunk it to STS to die
gSocketTransportService->Dispatch(new ThunkPRClose(fd), NS_DISPATCH_NORMAL);
} else {
// something horrible has happened
NS_ASSERTION(gSocketTransportService, "No STS service");
}
}
void nsSocketTransport::ReleaseFD_Locked(PRFileDesc* fd) {
mLock.AssertCurrentThreadOwns();
NS_ASSERTION(mFD == fd, "wrong fd");
if (--mFDref == 0) {
if (gIOService->IsNetTearingDown() &&
((PR_IntervalNow() - gIOService->NetTearingDownStarted()) >
gSocketTransportService->MaxTimeForPrClosePref())) {
// If shutdown last to long, let the socket leak and do not close it.
SOCKET_LOG(("Intentional leak"));
} else {
if (mLingerPolarity || mLingerTimeout) {
PRSocketOptionData socket_linger;
socket_linger.option = PR_SockOpt_Linger;
socket_linger.value.linger.polarity = mLingerPolarity;
socket_linger.value.linger.linger = mLingerTimeout;
PR_SetSocketOption(mFD, &socket_linger);
}
if (OnSocketThread()) {
SOCKET_LOG(("nsSocketTransport: calling PR_Close [this=%p]\n", this));
CloseSocket(
mFD, mSocketTransportService->IsTelemetryEnabledAndNotSleepPhase());
} else {
// Can't PR_Close() a socket off STS thread. Thunk it to STS to die
STS_PRCloseOnSocketTransport(mFD, mLingerPolarity, mLingerTimeout);
}
}
mFD = nullptr;
}
}
//-----------------------------------------------------------------------------
// socket event handler impl
void nsSocketTransport::OnSocketEvent(uint32_t type, nsresult status,
nsISupports* param) {
SOCKET_LOG(
("nsSocketTransport::OnSocketEvent [this=%p type=%u status=%" PRIx32
" param=%p]\n",
this, type, static_cast<uint32_t>(status), param));
if (NS_FAILED(mCondition)) {
// block event since we're apparently already dead.
SOCKET_LOG((" blocking event [condition=%" PRIx32 "]\n",
static_cast<uint32_t>(mCondition)));
//
// notify input/output streams in case either has a pending notify.
//
mInput.OnSocketReady(mCondition);
mOutput.OnSocketReady(mCondition);
return;
}
switch (type) {
case MSG_ENSURE_CONNECT:
SOCKET_LOG((" MSG_ENSURE_CONNECT\n"));
// Apply port remapping here so that we do it on the socket thread and
// before we process the resolved DNS name or create the socket the first
// time.
if (!mPortRemappingApplied) {
mPortRemappingApplied = true;
mSocketTransportService->ApplyPortRemap(&mPort);
mSocketTransportService->ApplyPortRemap(&mOriginPort);
}
//
// ensure that we have created a socket, attached it, and have a
// connection.
//
if (mState == STATE_CLOSED) {
// Unix domain sockets are ready to connect; mNetAddr is all we
// need. Internet address families require a DNS lookup (or possibly
// several) before we can connect.
#if defined(XP_UNIX)
if (mNetAddrIsSet && mNetAddr.raw.family == AF_LOCAL) {
mCondition = InitiateSocket();
} else {
#else
{
#endif
mCondition = ResolveHost();
}
} else {
SOCKET_LOG((" ignoring redundant event\n"));
}
break;
case MSG_DNS_LOOKUP_COMPLETE:
if (mDNSRequest) { // only send this if we actually resolved anything
SendStatus(NS_NET_STATUS_RESOLVED_HOST);
}
SOCKET_LOG((" MSG_DNS_LOOKUP_COMPLETE\n"));
mDNSRequest = nullptr;
if (mDNSRecord) {
mDNSRecord->GetNextAddr(SocketPort(), &mNetAddr);
mDNSRecord->IsTRR(&mResolvedByTRR);
}
// status contains DNS lookup status
if (NS_FAILED(status)) {
// When using a HTTP proxy, NS_ERROR_UNKNOWN_HOST means the HTTP
// proxy host is not found, so we fixup the error code.
// For SOCKS proxies (mProxyTransparent == true), the socket
// transport resolves the real host here, so there's no fixup
// (see bug 226943).
if ((status == NS_ERROR_UNKNOWN_HOST) && !mProxyTransparent &&
!mProxyHost.IsEmpty()) {
mCondition = NS_ERROR_UNKNOWN_PROXY_HOST;
} else {
mCondition = status;
}
} else if (mState == STATE_RESOLVING) {
mCondition = InitiateSocket();
}
break;
case MSG_RETRY_INIT_SOCKET:
mCondition = InitiateSocket();
break;
case MSG_INPUT_CLOSED:
SOCKET_LOG((" MSG_INPUT_CLOSED\n"));
OnMsgInputClosed(status);
break;
case MSG_INPUT_PENDING:
SOCKET_LOG((" MSG_INPUT_PENDING\n"));
OnMsgInputPending();
break;
case MSG_OUTPUT_CLOSED:
SOCKET_LOG((" MSG_OUTPUT_CLOSED\n"));
OnMsgOutputClosed(status);
break;
case MSG_OUTPUT_PENDING:
SOCKET_LOG((" MSG_OUTPUT_PENDING\n"));
OnMsgOutputPending();
break;
case MSG_TIMEOUT_CHANGED:
SOCKET_LOG((" MSG_TIMEOUT_CHANGED\n"));
{
MutexAutoLock lock(mLock);
mPollTimeout =
mTimeouts[(mState == STATE_TRANSFERRING) ? TIMEOUT_READ_WRITE
: TIMEOUT_CONNECT];
}
break;
default:
SOCKET_LOG((" unhandled event!\n"));
}
if (NS_FAILED(mCondition)) {
SOCKET_LOG((" after event [this=%p cond=%" PRIx32 "]\n", this,
static_cast<uint32_t>(mCondition)));
if (!mAttached) { // need to process this error ourselves...
OnSocketDetached(nullptr);
}
} else if (mPollFlags == PR_POLL_EXCEPT) {
mPollFlags = 0; // make idle
}
}
//-----------------------------------------------------------------------------
// socket handler impl
void nsSocketTransport::OnSocketReady(PRFileDesc* fd, int16_t outFlags) {
SOCKET_LOG1(("nsSocketTransport::OnSocketReady [this=%p outFlags=%hd]\n",
this, outFlags));
if (outFlags == -1) {
SOCKET_LOG(("socket timeout expired\n"));
mCondition = NS_ERROR_NET_TIMEOUT;
return;
}
if (mState == STATE_TRANSFERRING) {
// if waiting to write and socket is writable or hit an exception.
if ((mPollFlags & PR_POLL_WRITE) && (outFlags & ~PR_POLL_READ)) {
// assume that we won't need to poll any longer (the stream will
// request that we poll again if it is still pending).
mPollFlags &= ~PR_POLL_WRITE;
mOutput.OnSocketReady(NS_OK);
}
// if waiting to read and socket is readable or hit an exception.
if ((mPollFlags & PR_POLL_READ) && (outFlags & ~PR_POLL_WRITE)) {
// assume that we won't need to poll any longer (the stream will
// request that we poll again if it is still pending).
mPollFlags &= ~PR_POLL_READ;
mInput.OnSocketReady(NS_OK);
}
// Update poll timeout in case it was changed
{
MutexAutoLock lock(mLock);
mPollTimeout = mTimeouts[TIMEOUT_READ_WRITE];
}
} else if ((mState == STATE_CONNECTING) && !gIOService->IsNetTearingDown()) {
// We do not need to do PR_ConnectContinue when we are already
// shutting down.
// We use PRIntervalTime here because we need
// nsIOService::LastOfflineStateChange time and
// nsIOService::LastConectivityChange time to be atomic.
PRIntervalTime connectStarted = 0;
if (gSocketTransportService->IsTelemetryEnabledAndNotSleepPhase()) {
connectStarted = PR_IntervalNow();
}
PRStatus status = PR_ConnectContinue(fd, outFlags);
if (gSocketTransportService->IsTelemetryEnabledAndNotSleepPhase() &&
connectStarted) {
SendPRBlockingTelemetry(
connectStarted, Telemetry::PRCONNECTCONTINUE_BLOCKING_TIME_NORMAL,
Telemetry::PRCONNECTCONTINUE_BLOCKING_TIME_SHUTDOWN,
Telemetry::PRCONNECTCONTINUE_BLOCKING_TIME_CONNECTIVITY_CHANGE,
Telemetry::PRCONNECTCONTINUE_BLOCKING_TIME_LINK_CHANGE,
Telemetry::PRCONNECTCONTINUE_BLOCKING_TIME_OFFLINE);
}
if (status == PR_SUCCESS) {
//
// we are connected!
//
OnSocketConnected();
if (mNetAddr.raw.family == AF_INET) {
if (mSocketTransportService->IsTelemetryEnabledAndNotSleepPhase()) {
Telemetry::Accumulate(Telemetry::IPV4_AND_IPV6_ADDRESS_CONNECTIVITY,
SUCCESSFUL_CONNECTING_TO_IPV4_ADDRESS);
}
} else if (mNetAddr.raw.family == AF_INET6) {
if (mSocketTransportService->IsTelemetryEnabledAndNotSleepPhase()) {
Telemetry::Accumulate(Telemetry::IPV4_AND_IPV6_ADDRESS_CONNECTIVITY,
SUCCESSFUL_CONNECTING_TO_IPV6_ADDRESS);
}
}
} else {
PRErrorCode code = PR_GetError();
#if defined(TEST_CONNECT_ERRORS)
code = RandomizeConnectError(code);
#endif
//
// If the connect is still not ready, then continue polling...
//
if ((PR_WOULD_BLOCK_ERROR == code) || (PR_IN_PROGRESS_ERROR == code)) {
// Set up the select flags for connect...
mPollFlags = (PR_POLL_EXCEPT | PR_POLL_WRITE);
// Update poll timeout in case it was changed
{
MutexAutoLock lock(mLock);
mPollTimeout = mTimeouts[TIMEOUT_CONNECT];
}
}
//
// The SOCKS proxy rejected our request. Find out why.
//
else if (PR_UNKNOWN_ERROR == code && mProxyTransparent &&
!mProxyHost.IsEmpty()) {
code = PR_GetOSError();
mCondition = ErrorAccordingToNSPR(code);
} else {
//
// else, the connection failed...
//
mCondition = ErrorAccordingToNSPR(code);
if ((mCondition == NS_ERROR_CONNECTION_REFUSED) &&
!mProxyHost.IsEmpty()) {
mCondition = NS_ERROR_PROXY_CONNECTION_REFUSED;
}
SOCKET_LOG((" connection failed! [reason=%" PRIx32 "]\n",
static_cast<uint32_t>(mCondition)));
}
}
} else if ((mState == STATE_CONNECTING) && gIOService->IsNetTearingDown()) {
// We do not need to do PR_ConnectContinue when we are already
// shutting down.
SOCKET_LOG(
("We are in shutdown so skip PR_ConnectContinue and set "
"and error.\n"));
mCondition = NS_ERROR_ABORT;
} else {
NS_ERROR("unexpected socket state");
mCondition = NS_ERROR_UNEXPECTED;
}
if (mPollFlags == PR_POLL_EXCEPT) mPollFlags = 0; // make idle
}
// called on the socket thread only
void nsSocketTransport::OnSocketDetached(PRFileDesc* fd) {
SOCKET_LOG(("nsSocketTransport::OnSocketDetached [this=%p cond=%" PRIx32
"]\n",
this, static_cast<uint32_t>(mCondition)));
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
mAttached = false;
// if we didn't initiate this detach, then be sure to pass an error
// condition up to our consumers. (e.g., STS is shutting down.)
if (NS_SUCCEEDED(mCondition)) {
if (gIOService->IsOffline()) {
mCondition = NS_ERROR_OFFLINE;
} else {
mCondition = NS_ERROR_ABORT;
}
}
// If we are not shutting down try again.
if (!gIOService->IsNetTearingDown() && RecoverFromError()) {
mCondition = NS_OK;
} else {
mState = STATE_CLOSED;
// make sure there isn't any pending DNS request
if (mDNSRequest) {
mDNSRequest->Cancel(NS_ERROR_ABORT);
mDNSRequest = nullptr;
}
//
// notify input/output streams
//
mInput.OnSocketReady(mCondition);
mOutput.OnSocketReady(mCondition);
}
// break any potential reference cycle between the security info object
// and ourselves by resetting its notification callbacks object. see
// bug 285991 for details.
nsCOMPtr<nsISSLSocketControl> secCtrl = do_QueryInterface(mSecInfo);
if (secCtrl) secCtrl->SetNotificationCallbacks(nullptr);
// finally, release our reference to the socket (must do this within
// the transport lock) possibly closing the socket. Also release our
// listeners to break potential refcount cycles.
// We should be careful not to release mEventSink and mCallbacks while
// we're locked, because releasing it might require acquiring the lock
// again, so we just null out mEventSink and mCallbacks while we're
// holding the lock, and let the stack based objects' destuctors take
// care of destroying it if needed.
nsCOMPtr<nsIInterfaceRequestor> ourCallbacks;
nsCOMPtr<nsITransportEventSink> ourEventSink;
{
MutexAutoLock lock(mLock);
if (mFD.IsInitialized()) {
ReleaseFD_Locked(mFD);
// flag mFD as unusable; this prevents other consumers from
// acquiring a reference to mFD.
mFDconnected = false;
}
// We must release mCallbacks and mEventSink to avoid memory leak
// but only when RecoverFromError() above failed. Otherwise we lose
// link with UI and security callbacks on next connection attempt
// round. That would lead e.g. to a broken certificate exception page.
if (NS_FAILED(mCondition)) {
mCallbacks.swap(ourCallbacks);
mEventSink.swap(ourEventSink);
}
}
}
void nsSocketTransport::IsLocal(bool* aIsLocal) {
{
MutexAutoLock lock(mLock);
#if defined(XP_UNIX)
// Unix-domain sockets are always local.
if (mNetAddr.raw.family == PR_AF_LOCAL) {
*aIsLocal = true;
return;
}
#endif
*aIsLocal = mNetAddr.IsLoopbackAddr();
}
}
//-----------------------------------------------------------------------------
// xpcom api
NS_IMPL_ISUPPORTS(nsSocketTransport, nsISocketTransport, nsITransport,
nsIDNSListener, nsIClassInfo, nsIInterfaceRequestor)
NS_IMPL_CI_INTERFACE_GETTER(nsSocketTransport, nsISocketTransport, nsITransport,
nsIDNSListener, nsIInterfaceRequestor)
NS_IMETHODIMP
nsSocketTransport::OpenInputStream(uint32_t flags, uint32_t segsize,
uint32_t segcount, nsIInputStream** result) {
SOCKET_LOG(
("nsSocketTransport::OpenInputStream [this=%p flags=%x]\n", this, flags));
NS_ENSURE_TRUE(!mInput.IsReferenced(), NS_ERROR_UNEXPECTED);
nsresult rv;
nsCOMPtr<nsIAsyncInputStream> pipeIn;
if (!(flags & OPEN_UNBUFFERED) || (flags & OPEN_BLOCKING)) {
// XXX if the caller wants blocking, then the caller also gets buffered!
// bool openBuffered = !(flags & OPEN_UNBUFFERED);
bool openBlocking = (flags & OPEN_BLOCKING);
net_ResolveSegmentParams(segsize, segcount);
// create a pipe
nsCOMPtr<nsIAsyncOutputStream> pipeOut;
rv = NS_NewPipe2(getter_AddRefs(pipeIn), getter_AddRefs(pipeOut),
!openBlocking, true, segsize, segcount);
if (NS_FAILED(rv)) return rv;
// async copy from socket to pipe
rv = NS_AsyncCopy(&mInput, pipeOut, mSocketTransportService,
NS_ASYNCCOPY_VIA_WRITESEGMENTS, segsize);
if (NS_FAILED(rv)) return rv;
*result = pipeIn;
} else {
*result = &mInput;
}
// flag input stream as open
mInputClosed = false;
rv = PostEvent(MSG_ENSURE_CONNECT);
if (NS_FAILED(rv)) return rv;
NS_ADDREF(*result);
return NS_OK;
}
NS_IMETHODIMP
nsSocketTransport::OpenOutputStream(uint32_t flags, uint32_t segsize,
uint32_t segcount,
nsIOutputStream** result) {
SOCKET_LOG(("nsSocketTransport::OpenOutputStream [this=%p flags=%x]\n", this,
flags));
NS_ENSURE_TRUE(!mOutput.IsReferenced(), NS_ERROR_UNEXPECTED);
nsresult rv;
nsCOMPtr<nsIAsyncOutputStream> pipeOut;
if (!(flags & OPEN_UNBUFFERED) || (flags & OPEN_BLOCKING)) {
// XXX if the caller wants blocking, then the caller also gets buffered!
// bool openBuffered = !(flags & OPEN_UNBUFFERED);
bool openBlocking = (flags & OPEN_BLOCKING);
net_ResolveSegmentParams(segsize, segcount);
// create a pipe
nsCOMPtr<nsIAsyncInputStream> pipeIn;
rv = NS_NewPipe2(getter_AddRefs(pipeIn), getter_AddRefs(pipeOut), true,
!openBlocking, segsize, segcount);
if (NS_FAILED(rv)) return rv;
// async copy from socket to pipe
rv = NS_AsyncCopy(pipeIn, &mOutput, mSocketTransportService,
NS_ASYNCCOPY_VIA_READSEGMENTS, segsize);
if (NS_FAILED(rv)) return rv;
*result = pipeOut;
} else {
*result = &mOutput;
}
// flag output stream as open
mOutputClosed = false;
rv = PostEvent(MSG_ENSURE_CONNECT);
if (NS_FAILED(rv)) return rv;
NS_ADDREF(*result);
return NS_OK;
}
NS_IMETHODIMP
nsSocketTransport::Close(nsresult reason) {
SOCKET_LOG(("nsSocketTransport::Close %p reason=%" PRIx32, this,
static_cast<uint32_t>(reason)));
if (NS_SUCCEEDED(reason)) reason = NS_BASE_STREAM_CLOSED;
mDoNotRetryToConnect = true;
mInput.CloseWithStatus(reason);
mOutput.CloseWithStatus(reason);
return NS_OK;
}
NS_IMETHODIMP
nsSocketTransport::GetSecurityInfo(nsISupports** secinfo) {
MutexAutoLock lock(mLock);
NS_IF_ADDREF(*secinfo = mSecInfo);
return NS_OK;
}
NS_IMETHODIMP
nsSocketTransport::GetSecurityCallbacks(nsIInterfaceRequestor** callbacks) {
MutexAutoLock lock(mLock);
NS_IF_ADDREF(*callbacks = mCallbacks);
return NS_OK;
}
NS_IMETHODIMP
nsSocketTransport::SetSecurityCallbacks(nsIInterfaceRequestor* callbacks) {
nsCOMPtr<nsIInterfaceRequestor> threadsafeCallbacks;
NS_NewNotificationCallbacksAggregation(callbacks, nullptr,
GetCurrentEventTarget(),
getter_AddRefs(threadsafeCallbacks));
nsCOMPtr<nsISupports> secinfo;
{
MutexAutoLock lock(mLock);
mCallbacks = threadsafeCallbacks;
SOCKET_LOG(("Reset callbacks for secinfo=%p callbacks=%p\n", mSecInfo.get(),
mCallbacks.get()));
secinfo = mSecInfo;
}
// don't call into PSM while holding mLock!!
nsCOMPtr<nsISSLSocketControl> secCtrl(do_QueryInterface(secinfo));
if (secCtrl) secCtrl->SetNotificationCallbacks(threadsafeCallbacks);
return NS_OK;
}
NS_IMETHODIMP
nsSocketTransport::SetEventSink(nsITransportEventSink* sink,
nsIEventTarget* target) {
nsCOMPtr<nsITransportEventSink> temp;
if (target) {
nsresult rv =
net_NewTransportEventSinkProxy(getter_AddRefs(temp), sink, target);
if (NS_FAILED(rv)) return rv;
sink = temp.get();
}
MutexAutoLock lock(mLock);
mEventSink = sink;
return NS_OK;
}
NS_IMETHODIMP
nsSocketTransport::IsAlive(bool* result) {
*result = false;
nsresult conditionWhileLocked = NS_OK;
PRFileDescAutoLock fd(this, &conditionWhileLocked);
if (NS_FAILED(conditionWhileLocked) || !fd.IsInitialized()) {
return NS_OK;
}
// XXX do some idle-time based checks??
char c;
int32_t rval = PR_Recv(fd, &c, 1, PR_MSG_PEEK, 0);
if ((rval > 0) || (rval < 0 && PR_GetError() == PR_WOULD_BLOCK_ERROR)) {
*result = true;
}
return NS_OK;
}
NS_IMETHODIMP
nsSocketTransport::GetHost(nsACString& host) {
host = SocketHost();
return NS_OK;
}
NS_IMETHODIMP
nsSocketTransport::GetPort(int32_t* port) {
*port = (int32_t)SocketPort();
return NS_OK;
}
NS_IMETHODIMP
nsSocketTransport::GetScriptableOriginAttributes(
JSContext* aCx, JS::MutableHandle<JS::Value> aOriginAttributes) {
if (NS_WARN_IF(!ToJSValue(aCx, mOriginAttributes, aOriginAttributes))) {
return NS_ERROR_FAILURE;
}
return NS_OK;
}
NS_IMETHODIMP
nsSocketTransport::SetScriptableOriginAttributes(
JSContext* aCx, JS::Handle<JS::Value> aOriginAttributes) {
MutexAutoLock lock(mLock);
NS_ENSURE_FALSE(mFD.IsInitialized(), NS_ERROR_FAILURE);
OriginAttributes attrs;
if (!aOriginAttributes.isObject() || !attrs.Init(aCx, aOriginAttributes)) {
return NS_ERROR_INVALID_ARG;
}
mOriginAttributes = attrs;
return NS_OK;
}
nsresult nsSocketTransport::GetOriginAttributes(
OriginAttributes* aOriginAttributes) {
NS_ENSURE_ARG(aOriginAttributes);
*aOriginAttributes = mOriginAttributes;
return NS_OK;
}
nsresult nsSocketTransport::SetOriginAttributes(
const OriginAttributes& aOriginAttributes) {
MutexAutoLock lock(mLock);
NS_ENSURE_FALSE(mFD.IsInitialized(), NS_ERROR_FAILURE);
mOriginAttributes = aOriginAttributes;
return NS_OK;
}
NS_IMETHODIMP
nsSocketTransport::GetPeerAddr(NetAddr* addr) {
// once we are in the connected state, mNetAddr will not change.
// so if we can verify that we are in the connected state, then
// we can freely access mNetAddr from any thread without being
// inside a critical section.
if (!mNetAddrIsSet) {
SOCKET_LOG(
("nsSocketTransport::GetPeerAddr [this=%p state=%d] "
"NOT_AVAILABLE because not yet connected.",
this, mState));
return NS_ERROR_NOT_AVAILABLE;
}
memcpy(addr, &mNetAddr, sizeof(NetAddr));
return NS_OK;
}
NS_IMETHODIMP
nsSocketTransport::GetSelfAddr(NetAddr* addr) {
// once we are in the connected state, mSelfAddr will not change.
// so if we can verify that we are in the connected state, then
// we can freely access mSelfAddr from any thread without being
// inside a critical section.
if (!mSelfAddrIsSet) {
SOCKET_LOG(
("nsSocketTransport::GetSelfAddr [this=%p state=%d] "
"NOT_AVAILABLE because not yet connected.",
this, mState));
return NS_ERROR_NOT_AVAILABLE;
}
memcpy(addr, &mSelfAddr, sizeof(NetAddr));
return NS_OK;
}
NS_IMETHODIMP
nsSocketTransport::Bind(NetAddr* aLocalAddr) {
NS_ENSURE_ARG(aLocalAddr);
MutexAutoLock lock(mLock);
if (mAttached) {
return NS_ERROR_FAILURE;
}
mBindAddr = MakeUnique<NetAddr>();
memcpy(mBindAddr.get(), aLocalAddr, sizeof(NetAddr));
return NS_OK;
}
NS_IMETHODIMP
nsSocketTransport::GetScriptablePeerAddr(nsINetAddr** addr) {
NetAddr rawAddr;
nsresult rv;
rv = GetPeerAddr(&rawAddr);
if (NS_FAILED(rv)) return rv;
RefPtr<nsNetAddr> netaddr = new nsNetAddr(&rawAddr);
netaddr.forget(addr);
return NS_OK;
}
NS_IMETHODIMP
nsSocketTransport::GetScriptableSelfAddr(nsINetAddr** addr) {
NetAddr rawAddr;
nsresult rv;
rv = GetSelfAddr(&rawAddr);
if (NS_FAILED(rv)) return rv;
RefPtr<nsNetAddr> netaddr = new nsNetAddr(&rawAddr);
netaddr.forget(addr);
return NS_OK;
}
NS_IMETHODIMP
nsSocketTransport::GetTimeout(uint32_t type, uint32_t* value) {
NS_ENSURE_ARG_MAX(type, nsISocketTransport::TIMEOUT_READ_WRITE);
MutexAutoLock lock(mLock);
*value = (uint32_t)mTimeouts[type];
return NS_OK;
}
NS_IMETHODIMP
nsSocketTransport::SetTimeout(uint32_t type, uint32_t value) {
NS_ENSURE_ARG_MAX(type, nsISocketTransport::TIMEOUT_READ_WRITE);
SOCKET_LOG(("nsSocketTransport::SetTimeout %p type=%u, value=%u", this, type,
value));
// truncate overly large timeout values.
{
MutexAutoLock lock(mLock);
mTimeouts[type] = (uint16_t)std::min<uint32_t>(value, UINT16_MAX);
}
PostEvent(MSG_TIMEOUT_CHANGED);
return NS_OK;
}
NS_IMETHODIMP
nsSocketTransport::SetReuseAddrPort(bool reuseAddrPort) {
mReuseAddrPort = reuseAddrPort;
return NS_OK;
}
NS_IMETHODIMP
nsSocketTransport::SetLinger(bool aPolarity, int16_t aTimeout) {
MutexAutoLock lock(mLock);
mLingerPolarity = aPolarity;
mLingerTimeout = aTimeout;
return NS_OK;
}
NS_IMETHODIMP
nsSocketTransport::SetQoSBits(uint8_t aQoSBits) {
// Don't do any checking here of bits. Why? Because as of RFC-4594
// several different Class Selector and Assured Forwarding values
// have been defined, but that isn't to say more won't be added later.
// In that case, any checking would be an impediment to interoperating
// with newer QoS definitions.
mQoSBits = aQoSBits;
return NS_OK;
}
NS_IMETHODIMP
nsSocketTransport::GetQoSBits(uint8_t* aQoSBits) {
*aQoSBits = mQoSBits;
return NS_OK;
}
NS_IMETHODIMP
nsSocketTransport::GetRecvBufferSize(uint32_t* aSize) {
PRFileDescAutoLock fd(this);
if (!fd.IsInitialized()) return NS_ERROR_NOT_CONNECTED;
nsresult rv = NS_OK;
PRSocketOptionData opt;
opt.option = PR_SockOpt_RecvBufferSize;
if (PR_GetSocketOption(fd, &opt) == PR_SUCCESS) {
*aSize = opt.value.recv_buffer_size;
} else {
rv = NS_ERROR_FAILURE;
}
return rv;
}
NS_IMETHODIMP
nsSocketTransport::GetSendBufferSize(uint32_t* aSize) {
PRFileDescAutoLock fd(this);
if (!fd.IsInitialized()) return NS_ERROR_NOT_CONNECTED;
nsresult rv = NS_OK;
PRSocketOptionData opt;
opt.option = PR_SockOpt_SendBufferSize;
if (PR_GetSocketOption(fd, &opt) == PR_SUCCESS) {
*aSize = opt.value.send_buffer_size;
} else {
rv = NS_ERROR_FAILURE;
}
return rv;
}
NS_IMETHODIMP
nsSocketTransport::SetRecvBufferSize(uint32_t aSize) {
PRFileDescAutoLock fd(this);
if (!fd.IsInitialized()) return NS_ERROR_NOT_CONNECTED;
nsresult rv = NS_OK;
PRSocketOptionData opt;
opt.option = PR_SockOpt_RecvBufferSize;
opt.value.recv_buffer_size = aSize;
if (PR_SetSocketOption(fd, &opt) != PR_SUCCESS) rv = NS_ERROR_FAILURE;
return rv;
}
NS_IMETHODIMP
nsSocketTransport::SetSendBufferSize(uint32_t aSize) {
PRFileDescAutoLock fd(this);
if (!fd.IsInitialized()) return NS_ERROR_NOT_CONNECTED;
nsresult rv = NS_OK;
PRSocketOptionData opt;
opt.option = PR_SockOpt_SendBufferSize;
opt.value.send_buffer_size = aSize;
if (PR_SetSocketOption(fd, &opt) != PR_SUCCESS) rv = NS_ERROR_FAILURE;
return rv;
}
NS_IMETHODIMP
nsSocketTransport::OnLookupComplete(nsICancelable* request, nsIDNSRecord* rec,
nsresult status) {
SOCKET_LOG(("nsSocketTransport::OnLookupComplete: this=%p status %" PRIx32
".",
this, static_cast<uint32_t>(status)));
if (NS_SUCCEEDED(status)) {
mDNSRecord = do_QueryInterface(rec);
MOZ_ASSERT(mDNSRecord);
}
// flag host lookup complete for the benefit of the ResolveHost method.
mResolving = false;
nsresult rv = PostEvent(MSG_DNS_LOOKUP_COMPLETE, status, nullptr);
// if posting a message fails, then we should assume that the socket
// transport has been shutdown. this should never happen! if it does
// it means that the socket transport service was shutdown before the
// DNS service.
if (NS_FAILED(rv)) {
NS_WARNING("unable to post DNS lookup complete message");
}
return NS_OK;
}
// nsIInterfaceRequestor
NS_IMETHODIMP
nsSocketTransport::GetInterface(const nsIID& iid, void** result) {
if (iid.Equals(NS_GET_IID(nsIDNSRecord)) ||
iid.Equals(NS_GET_IID(nsIDNSAddrRecord))) {
return mDNSRecord ? mDNSRecord->QueryInterface(iid, result)
: NS_ERROR_NO_INTERFACE;
}
return this->QueryInterface(iid, result);
}
NS_IMETHODIMP
nsSocketTransport::GetInterfaces(nsTArray<nsIID>& array) {
return NS_CI_INTERFACE_GETTER_NAME(nsSocketTransport)(array);
}
NS_IMETHODIMP
nsSocketTransport::GetScriptableHelper(nsIXPCScriptable** _retval) {
*_retval = nullptr;
return NS_OK;
}
NS_IMETHODIMP
nsSocketTransport::GetContractID(nsACString& aContractID) {
aContractID.SetIsVoid(true);
return NS_OK;
}
NS_IMETHODIMP
nsSocketTransport::GetClassDescription(nsACString& aClassDescription) {
aClassDescription.SetIsVoid(true);
return NS_OK;
}
NS_IMETHODIMP
nsSocketTransport::GetClassID(nsCID** aClassID) {
*aClassID = nullptr;
return NS_OK;
}
NS_IMETHODIMP
nsSocketTransport::GetFlags(uint32_t* aFlags) {
*aFlags = nsIClassInfo::THREADSAFE;
return NS_OK;
}
NS_IMETHODIMP
nsSocketTransport::GetClassIDNoAlloc(nsCID* aClassIDNoAlloc) {
return NS_ERROR_NOT_AVAILABLE;
}
NS_IMETHODIMP
nsSocketTransport::GetConnectionFlags(uint32_t* value) {
*value = mConnectionFlags;
return NS_OK;
}
NS_IMETHODIMP
nsSocketTransport::SetConnectionFlags(uint32_t value) {
SOCKET_LOG(
("nsSocketTransport::SetConnectionFlags %p flags=%u", this, value));
mConnectionFlags = value;
return NS_OK;
}
NS_IMETHODIMP
nsSocketTransport::SetIsPrivate(bool aIsPrivate) {
mIsPrivate = aIsPrivate;
return NS_OK;
}
NS_IMETHODIMP
nsSocketTransport::GetTlsFlags(uint32_t* value) {
*value = mTlsFlags;
return NS_OK;
}
NS_IMETHODIMP
nsSocketTransport::SetTlsFlags(uint32_t value) {
mTlsFlags = value;
return NS_OK;
}
void nsSocketTransport::OnKeepaliveEnabledPrefChange(bool aEnabled) {
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
// The global pref toggles keepalive as a system feature; it only affects
// an individual socket if keepalive has been specifically enabled for it.
// So, ensure keepalive is configured correctly if previously enabled.
if (mKeepaliveEnabled) {
nsresult rv = SetKeepaliveEnabledInternal(aEnabled);
if (NS_WARN_IF(NS_FAILED(rv))) {
SOCKET_LOG((" SetKeepaliveEnabledInternal [%s] failed rv[0x%" PRIx32 "]",
aEnabled ? "enable" : "disable", static_cast<uint32_t>(rv)));
}
}
}
nsresult nsSocketTransport::SetKeepaliveEnabledInternal(bool aEnable) {
MOZ_ASSERT(mKeepaliveIdleTimeS > 0 && mKeepaliveIdleTimeS <= kMaxTCPKeepIdle);
MOZ_ASSERT(mKeepaliveRetryIntervalS > 0 &&
mKeepaliveRetryIntervalS <= kMaxTCPKeepIntvl);
MOZ_ASSERT(mKeepaliveProbeCount > 0 &&
mKeepaliveProbeCount <= kMaxTCPKeepCount);
PRFileDescAutoLock fd(this);
if (NS_WARN_IF(!fd.IsInitialized())) {
return NS_ERROR_NOT_INITIALIZED;
}
// Only enable if keepalives are globally enabled, but ensure other
// options are set correctly on the fd.
bool enable = aEnable && mSocketTransportService->IsKeepaliveEnabled();
nsresult rv =
fd.SetKeepaliveVals(enable, mKeepaliveIdleTimeS, mKeepaliveRetryIntervalS,
mKeepaliveProbeCount);
if (NS_WARN_IF(NS_FAILED(rv))) {
SOCKET_LOG((" SetKeepaliveVals failed rv[0x%" PRIx32 "]",
static_cast<uint32_t>(rv)));
return rv;
}
rv = fd.SetKeepaliveEnabled(enable);
if (NS_WARN_IF(NS_FAILED(rv))) {
SOCKET_LOG((" SetKeepaliveEnabled failed rv[0x%" PRIx32 "]",
static_cast<uint32_t>(rv)));
return rv;
}
return NS_OK;
}
NS_IMETHODIMP
nsSocketTransport::GetKeepaliveEnabled(bool* aResult) {
MOZ_ASSERT(aResult);
*aResult = mKeepaliveEnabled;
return NS_OK;
}
nsresult nsSocketTransport::EnsureKeepaliveValsAreInitialized() {
nsresult rv = NS_OK;
int32_t val = -1;
if (mKeepaliveIdleTimeS == -1) {
rv = mSocketTransportService->GetKeepaliveIdleTime(&val);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
mKeepaliveIdleTimeS = val;
}
if (mKeepaliveRetryIntervalS == -1) {
rv = mSocketTransportService->GetKeepaliveRetryInterval(&val);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
mKeepaliveRetryIntervalS = val;
}
if (mKeepaliveProbeCount == -1) {
rv = mSocketTransportService->GetKeepaliveProbeCount(&val);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
mKeepaliveProbeCount = val;
}
return NS_OK;
}
NS_IMETHODIMP
nsSocketTransport::SetKeepaliveEnabled(bool aEnable) {
#if defined(XP_WIN) || defined(XP_UNIX) || defined(XP_MACOSX)
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
if (aEnable == mKeepaliveEnabled) {
SOCKET_LOG(("nsSocketTransport::SetKeepaliveEnabled [%p] already %s.", this,
aEnable ? "enabled" : "disabled"));
return NS_OK;
}
nsresult rv = NS_OK;
if (aEnable) {
rv = EnsureKeepaliveValsAreInitialized();
if (NS_WARN_IF(NS_FAILED(rv))) {
SOCKET_LOG(
(" SetKeepaliveEnabled [%p] "
"error [0x%" PRIx32 "] initializing keepalive vals",
this, static_cast<uint32_t>(rv)));
return rv;
}
}
SOCKET_LOG(
("nsSocketTransport::SetKeepaliveEnabled [%p] "
"%s, idle time[%ds] retry interval[%ds] packet count[%d]: "
"globally %s.",
this, aEnable ? "enabled" : "disabled", mKeepaliveIdleTimeS,
mKeepaliveRetryIntervalS, mKeepaliveProbeCount,
mSocketTransportService->IsKeepaliveEnabled() ? "enabled" : "disabled"));
// Set mKeepaliveEnabled here so that state is maintained; it is possible
// that we're in between fds, e.g. the 1st IP address failed, so we're about
// to retry on a 2nd from the DNS record.
mKeepaliveEnabled = aEnable;
rv = SetKeepaliveEnabledInternal(aEnable);
if (NS_WARN_IF(NS_FAILED(rv))) {
SOCKET_LOG((" SetKeepaliveEnabledInternal failed rv[0x%" PRIx32 "]",
static_cast<uint32_t>(rv)));
return rv;
}
return NS_OK;
#else /* !(defined(XP_WIN) || defined(XP_UNIX) || defined(XP_MACOSX)) */
SOCKET_LOG(("nsSocketTransport::SetKeepaliveEnabled unsupported platform"));
return NS_ERROR_NOT_IMPLEMENTED;
#endif
}
NS_IMETHODIMP
nsSocketTransport::SetKeepaliveVals(int32_t aIdleTime, int32_t aRetryInterval) {
#if defined(XP_WIN) || defined(XP_UNIX) || defined(XP_MACOSX)
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
if (NS_WARN_IF(aIdleTime <= 0 || kMaxTCPKeepIdle < aIdleTime)) {
return NS_ERROR_INVALID_ARG;
}
if (NS_WARN_IF(aRetryInterval <= 0 || kMaxTCPKeepIntvl < aRetryInterval)) {
return NS_ERROR_INVALID_ARG;
}
if (aIdleTime == mKeepaliveIdleTimeS &&
aRetryInterval == mKeepaliveRetryIntervalS) {
SOCKET_LOG(
("nsSocketTransport::SetKeepaliveVals [%p] idle time "
"already %ds and retry interval already %ds.",
this, mKeepaliveIdleTimeS, mKeepaliveRetryIntervalS));
return NS_OK;
}
mKeepaliveIdleTimeS = aIdleTime;
mKeepaliveRetryIntervalS = aRetryInterval;
nsresult rv = NS_OK;
if (mKeepaliveProbeCount == -1) {
int32_t val = -1;
nsresult rv = mSocketTransportService->GetKeepaliveProbeCount(&val);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
mKeepaliveProbeCount = val;
}
SOCKET_LOG(
("nsSocketTransport::SetKeepaliveVals [%p] "
"keepalive %s, idle time[%ds] retry interval[%ds] "
"packet count[%d]",
this, mKeepaliveEnabled ? "enabled" : "disabled", mKeepaliveIdleTimeS,
mKeepaliveRetryIntervalS, mKeepaliveProbeCount));
PRFileDescAutoLock fd(this);
if (NS_WARN_IF(!fd.IsInitialized())) {
return NS_ERROR_NULL_POINTER;
}
rv = fd.SetKeepaliveVals(mKeepaliveEnabled, mKeepaliveIdleTimeS,
mKeepaliveRetryIntervalS, mKeepaliveProbeCount);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
return NS_OK;
#else
SOCKET_LOG(("nsSocketTransport::SetKeepaliveVals unsupported platform"));
return NS_ERROR_NOT_IMPLEMENTED;
#endif
}
#ifdef ENABLE_SOCKET_TRACING
# include <stdio.h>
# include <ctype.h>
# include "prenv.h"
static void DumpBytesToFile(const char* path, const char* header,
const char* buf, int32_t n) {
FILE* fp = fopen(path, "a");
fprintf(fp, "\n%s [%d bytes]\n", header, n);
const unsigned char* p;
while (n) {
p = (const unsigned char*)buf;
int32_t i, row_max = std::min(16, n);
for (i = 0; i < row_max; ++i) fprintf(fp, "%02x ", *p++);
for (i = row_max; i < 16; ++i) fprintf(fp, " ");
p = (const unsigned char*)buf;
for (i = 0; i < row_max; ++i, ++p) {
if (isprint(*p))
fprintf(fp, "%c", *p);
else
fprintf(fp, ".");
}
fprintf(fp, "\n");
buf += row_max;
n -= row_max;
}
fprintf(fp, "\n");
fclose(fp);
}
void nsSocketTransport::TraceInBuf(const char* buf, int32_t n) {
char* val = PR_GetEnv("NECKO_SOCKET_TRACE_LOG");
if (!val || !*val) return;
nsAutoCString header;
header.AssignLiteral("Reading from: ");
header.Append(mHost);
header.Append(':');
header.AppendInt(mPort);
DumpBytesToFile(val, header.get(), buf, n);
}
void nsSocketTransport::TraceOutBuf(const char* buf, int32_t n) {
char* val = PR_GetEnv("NECKO_SOCKET_TRACE_LOG");
if (!val || !*val) return;
nsAutoCString header;
header.AssignLiteral("Writing to: ");
header.Append(mHost);
header.Append(':');
header.AppendInt(mPort);
DumpBytesToFile(val, header.get(), buf, n);
}
#endif
static void LogNSPRError(const char* aPrefix, const void* aObjPtr) {
#if defined(DEBUG)
PRErrorCode errCode = PR_GetError();
int errLen = PR_GetErrorTextLength();
nsAutoCString errStr;
if (errLen > 0) {
errStr.SetLength(errLen);
PR_GetErrorText(errStr.BeginWriting());
}
NS_WARNING(
nsPrintfCString("%s [%p] NSPR error[0x%x] %s.",
aPrefix ? aPrefix : "nsSocketTransport", aObjPtr, errCode,
errLen > 0 ? errStr.BeginReading() : "<no error text>")
.get());
#endif
}
nsresult nsSocketTransport::PRFileDescAutoLock::SetKeepaliveEnabled(
bool aEnable) {
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
MOZ_ASSERT(!(aEnable && !gSocketTransportService->IsKeepaliveEnabled()),
"Cannot enable keepalive if global pref is disabled!");
if (aEnable && !gSocketTransportService->IsKeepaliveEnabled()) {
return NS_ERROR_ILLEGAL_VALUE;
}
PRSocketOptionData opt;
opt.option = PR_SockOpt_Keepalive;
opt.value.keep_alive = aEnable;
PRStatus status = PR_SetSocketOption(mFd, &opt);
if (NS_WARN_IF(status != PR_SUCCESS)) {
LogNSPRError("nsSocketTransport::PRFileDescAutoLock::SetKeepaliveEnabled",
mSocketTransport);
return ErrorAccordingToNSPR(PR_GetError());
}
return NS_OK;
}
static void LogOSError(const char* aPrefix, const void* aObjPtr) {
#if defined(DEBUG)
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
# ifdef XP_WIN
DWORD errCode = WSAGetLastError();
LPVOID errMessage;
FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM |
FORMAT_MESSAGE_IGNORE_INSERTS,
NULL, errCode, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
(LPTSTR)&errMessage, 0, NULL);
# else
int errCode = errno;
char* errMessage = strerror(errno);
# endif
NS_WARNING(nsPrintfCString("%s [%p] OS error[0x%x] %s",
aPrefix ? aPrefix : "nsSocketTransport", aObjPtr,
errCode,
errMessage ? errMessage : "<no error text>")
.get());
# ifdef XP_WIN
LocalFree(errMessage);
# endif
#endif
}
/* XXX PR_SetSockOpt does not support setting keepalive values, so native
* handles and platform specific apis (setsockopt, WSAIOCtl) are used in this
* file. Requires inclusion of NSPR private/pprio.h, and platform headers.
*/
nsresult nsSocketTransport::PRFileDescAutoLock::SetKeepaliveVals(
bool aEnabled, int aIdleTime, int aRetryInterval, int aProbeCount) {
#if defined(XP_WIN) || defined(XP_UNIX) || defined(XP_MACOSX)
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
if (NS_WARN_IF(aIdleTime <= 0 || kMaxTCPKeepIdle < aIdleTime)) {
return NS_ERROR_INVALID_ARG;
}
if (NS_WARN_IF(aRetryInterval <= 0 || kMaxTCPKeepIntvl < aRetryInterval)) {
return NS_ERROR_INVALID_ARG;
}
if (NS_WARN_IF(aProbeCount <= 0 || kMaxTCPKeepCount < aProbeCount)) {
return NS_ERROR_INVALID_ARG;
}
PROsfd sock = PR_FileDesc2NativeHandle(mFd);
if (NS_WARN_IF(sock == -1)) {
LogNSPRError("nsSocketTransport::PRFileDescAutoLock::SetKeepaliveVals",
mSocketTransport);
return ErrorAccordingToNSPR(PR_GetError());
}
#endif
#if defined(XP_WIN)
// Windows allows idle time and retry interval to be set; NOT ping count.
struct tcp_keepalive keepalive_vals = {(u_long)aEnabled,
// Windows uses msec.
(u_long)(aIdleTime * 1000UL),
(u_long)(aRetryInterval * 1000UL)};
DWORD bytes_returned;
int err =
WSAIoctl(sock, SIO_KEEPALIVE_VALS, &keepalive_vals,
sizeof(keepalive_vals), NULL, 0, &bytes_returned, NULL, NULL);
if (NS_WARN_IF(err)) {
LogOSError("nsSocketTransport WSAIoctl failed", mSocketTransport);
return NS_ERROR_UNEXPECTED;
}
return NS_OK;
#elif defined(XP_DARWIN)
// Darwin uses sec; only supports idle time being set.
int err = setsockopt(sock, IPPROTO_TCP, TCP_KEEPALIVE, &aIdleTime,
sizeof(aIdleTime));
if (NS_WARN_IF(err)) {
LogOSError("nsSocketTransport Failed setting TCP_KEEPALIVE",
mSocketTransport);
return NS_ERROR_UNEXPECTED;
}
return NS_OK;
#elif defined(XP_UNIX)
// Not all *nix OSes support the following setsockopt() options
// ... but we assume they are supported in the Android kernel;
// build errors will tell us if they are not.
# if defined(ANDROID) || defined(TCP_KEEPIDLE)
// Idle time until first keepalive probe; interval between ack'd probes;
// seconds.
int err = setsockopt(sock, IPPROTO_TCP, TCP_KEEPIDLE, &aIdleTime,
sizeof(aIdleTime));
if (NS_WARN_IF(err)) {
LogOSError("nsSocketTransport Failed setting TCP_KEEPIDLE",
mSocketTransport);
return NS_ERROR_UNEXPECTED;
}
# endif
# if defined(ANDROID) || defined(TCP_KEEPINTVL)
// Interval between unack'd keepalive probes; seconds.
err = setsockopt(sock, IPPROTO_TCP, TCP_KEEPINTVL, &aRetryInterval,
sizeof(aRetryInterval));
if (NS_WARN_IF(err)) {
LogOSError("nsSocketTransport Failed setting TCP_KEEPINTVL",
mSocketTransport);
return NS_ERROR_UNEXPECTED;
}
# endif
# if defined(ANDROID) || defined(TCP_KEEPCNT)
// Number of unack'd keepalive probes before connection times out.
err = setsockopt(sock, IPPROTO_TCP, TCP_KEEPCNT, &aProbeCount,
sizeof(aProbeCount));
if (NS_WARN_IF(err)) {
LogOSError("nsSocketTransport Failed setting TCP_KEEPCNT",
mSocketTransport);
return NS_ERROR_UNEXPECTED;
}
# endif
return NS_OK;
#else
MOZ_ASSERT(false,
"nsSocketTransport::PRFileDescAutoLock::SetKeepaliveVals "
"called on unsupported platform!");
return NS_ERROR_UNEXPECTED;
#endif
}
void nsSocketTransport::CloseSocket(PRFileDesc* aFd, bool aTelemetryEnabled) {
#if defined(XP_WIN)
AttachShutdownLayer(aFd);
#endif
// We use PRIntervalTime here because we need
// nsIOService::LastOfflineStateChange time and
// nsIOService::LastConectivityChange time to be atomic.
PRIntervalTime closeStarted;
if (aTelemetryEnabled) {
closeStarted = PR_IntervalNow();
}
PR_Close(aFd);
if (aTelemetryEnabled) {
SendPRBlockingTelemetry(
closeStarted, Telemetry::PRCLOSE_TCP_BLOCKING_TIME_NORMAL,
Telemetry::PRCLOSE_TCP_BLOCKING_TIME_SHUTDOWN,
Telemetry::PRCLOSE_TCP_BLOCKING_TIME_CONNECTIVITY_CHANGE,
Telemetry::PRCLOSE_TCP_BLOCKING_TIME_LINK_CHANGE,
Telemetry::PRCLOSE_TCP_BLOCKING_TIME_OFFLINE);
}
}
void nsSocketTransport::SendPRBlockingTelemetry(
PRIntervalTime aStart, Telemetry::HistogramID aIDNormal,
Telemetry::HistogramID aIDShutdown,
Telemetry::HistogramID aIDConnectivityChange,
Telemetry::HistogramID aIDLinkChange, Telemetry::HistogramID aIDOffline) {
PRIntervalTime now = PR_IntervalNow();
if (gIOService->IsNetTearingDown()) {
Telemetry::Accumulate(aIDShutdown, PR_IntervalToMilliseconds(now - aStart));
} else if (PR_IntervalToSeconds(now - gIOService->LastConnectivityChange()) <
60) {
Telemetry::Accumulate(aIDConnectivityChange,
PR_IntervalToMilliseconds(now - aStart));
} else if (PR_IntervalToSeconds(now - gIOService->LastNetworkLinkChange()) <
60) {
Telemetry::Accumulate(aIDLinkChange,
PR_IntervalToMilliseconds(now - aStart));
} else if (PR_IntervalToSeconds(now - gIOService->LastOfflineStateChange()) <
60) {
Telemetry::Accumulate(aIDOffline, PR_IntervalToMilliseconds(now - aStart));
} else {
Telemetry::Accumulate(aIDNormal, PR_IntervalToMilliseconds(now - aStart));
}
}
NS_IMETHODIMP
nsSocketTransport::GetResetIPFamilyPreference(bool* aReset) {
*aReset = mResetFamilyPreference;
return NS_OK;
}
NS_IMETHODIMP
nsSocketTransport::GetEchConfigUsed(bool* aEchConfigUsed) {
*aEchConfigUsed = mEchConfigUsed;
return NS_OK;
}
NS_IMETHODIMP
nsSocketTransport::SetEchConfig(const nsACString& aEchConfig) {
mEchConfig = aEchConfig;
return NS_OK;
}
NS_IMETHODIMP
nsSocketTransport::ResolvedByTRR(bool* aResolvedByTRR) {
*aResolvedByTRR = mResolvedByTRR;
return NS_OK;
}
NS_IMETHODIMP
nsSocketTransport::GetRetryDnsIfPossible(bool* aRetryDns) {
*aRetryDns = mRetryDnsIfPossible;
return NS_OK;
}
NS_IMETHODIMP
nsSocketTransport::GetStatus(nsresult* aStatus) {
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
*aStatus = mCondition;
return NS_OK;
}
} // namespace net
} // namespace mozilla