diff options
Diffstat (limited to 'netwerk/base/nsSocketTransport2.cpp')
-rw-r--r-- | netwerk/base/nsSocketTransport2.cpp | 3245 |
1 files changed, 3245 insertions, 0 deletions
diff --git a/netwerk/base/nsSocketTransport2.cpp b/netwerk/base/nsSocketTransport2.cpp new file mode 100644 index 000000000..1bfd1fc91 --- /dev/null +++ b/netwerk/base/nsSocketTransport2.cpp @@ -0,0 +1,3245 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* vim:set ts=4 sw=4 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/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 "nsAutoPtr.h" +#include "nsCOMPtr.h" +#include "plstr.h" +#include "prerr.h" +#include "NetworkActivityMonitor.h" +#include "NSSErrorsService.h" +#include "mozilla/dom/ToJSValue.h" +#include "mozilla/net/NeckoChild.h" +#include "nsThreadUtils.h" +#include "nsISocketProviderService.h" +#include "nsISocketProvider.h" +#include "nsISSLSocketControl.h" +#include "nsIPipe.h" +#include "nsIClassInfoImpl.h" +#include "nsURLHelper.h" +#include "nsIDNSService.h" +#include "nsIDNSRecord.h" +#include "nsICancelable.h" +#include <algorithm> + +#include "nsPrintfCString.h" +#include "xpcpublic.h" + +#if defined(XP_WIN) +#include "mozilla/WindowsVersion.h" +#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(kSocketProviderServiceCID, NS_SOCKETPROVIDERSERVICE_CID); +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) + : 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; + 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=%x]\n", errorCode, rv)); + return rv; +} + +//----------------------------------------------------------------------------- +// socket input stream impl +//----------------------------------------------------------------------------- + +nsSocketInputStream::nsSocketInputStream(nsSocketTransport *trans) + : mTransport(trans) + , mReaderRefCnt(0) + , mCondition(NS_OK) + , mCallbackFlags(0) + , mByteCount(0) +{ +} + +nsSocketInputStream::~nsSocketInputStream() +{ +} + +// 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=%x]\n", + this, condition)); + + NS_ASSERTION(PR_GetCurrentThread() == gSocketThread, "wrong 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 = mCallback; + mCallback = nullptr; + 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=%x]\n", this, 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(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) + , mWriterRefCnt(0) + , mCondition(NS_OK) + , mCallbackFlags(0) + , mByteCount(0) +{ +} + +nsSocketOutputStream::~nsSocketOutputStream() +{ +} + +// 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=%x]\n", + this, condition)); + + NS_ASSERTION(PR_GetCurrentThread() == gSocketThread, "wrong 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 = mCallback; + mCallback = nullptr; + 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=%x]\n", this, 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() + : mTypes(nullptr) + , mTypeCount(0) + , mPort(0) + , mProxyPort(0) + , mOriginPort(0) + , mProxyTransparent(false) + , mProxyTransparentResolvesHost(false) + , mHttpsProxy(false) + , mConnectionFlags(0) + , mState(STATE_CLOSED) + , mAttached(false) + , mInputClosed(true) + , mOutputClosed(true) + , mResolving(false) + , mNetAddrIsSet(false) + , mSelfAddrIsSet(false) + , mNetAddrPreResolved(false) + , mLock("nsSocketTransport.mLock") + , mFD(this) + , mFDref(0) + , mFDconnected(false) + , mSocketTransportService(gSocketTransportService) + , mInput(this) + , mOutput(this) + , mQoSBits(0x00) + , mKeepaliveEnabled(false) + , mKeepaliveIdleTimeS(-1) + , mKeepaliveRetryIntervalS(-1) + , mKeepaliveProbeCount(-1) + , mDoNotRetryToConnect(false) +{ + 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)); + + CleanupTypes(); +} + +void +nsSocketTransport::CleanupTypes() +{ + // cleanup socket type info + if (mTypes) { + for (uint32_t i = 0; i < mTypeCount; ++i) { + PL_strfree(mTypes[i]); + } + free(mTypes); + mTypes = nullptr; + } + mTypeCount = 0; +} + +nsresult +nsSocketTransport::Init(const char **types, uint32_t typeCount, + const nsACString &host, uint16_t port, + const nsACString &hostRoute, uint16_t portRoute, + nsIProxyInfo *givenProxyInfo) +{ + nsCOMPtr<nsProxyInfo> proxyInfo; + if (givenProxyInfo) { + proxyInfo = do_QueryInterface(givenProxyInfo); + NS_ENSURE_ARG(proxyInfo); + } + + // init socket type info + + mOriginHost = host; + mOriginPort = port; + if (!hostRoute.IsEmpty()) { + mHost = hostRoute; + mPort = portRoute; + } else { + mHost = host; + mPort = port; + } + + 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_LOG(("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" + mTypeCount = typeCount + (proxyType != nullptr); + if (!mTypeCount) + return NS_OK; + + // if we have socket types, then the socket provider service had + // better exist! + nsresult rv; + nsCOMPtr<nsISocketProviderService> spserv = + do_GetService(kSocketProviderServiceCID, &rv); + if (NS_FAILED(rv)) return rv; + + mTypes = (char **) malloc(mTypeCount * sizeof(char *)); + if (!mTypes) + 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 < mTypeCount; ++i) { + // store socket types + if (i == 0 && proxyType) + mTypes[i] = PL_strdup(proxyType); + else + mTypes[i] = PL_strdup(types[type++]); + + if (!mTypes[i]) { + mTypeCount = i; + return NS_ERROR_OUT_OF_MEMORY; + } + nsCOMPtr<nsISocketProvider> provider; + rv = spserv->GetSocketProvider(mTypes[i], 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 ((strcmp(mTypes[i], "socks") == 0) || + (strcmp(mTypes[i], "socks4") == 0)) { + 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; +} + +nsresult +nsSocketTransport::InitPreResolved(const char **socketTypes, uint32_t typeCount, + const nsACString &host, uint16_t port, + const nsACString &hostRoute, uint16_t portRoute, + nsIProxyInfo *proxyInfo, + const mozilla::net::NetAddr* addr) +{ + nsresult rv = Init(socketTypes, typeCount, host, port, hostRoute, portRoute, proxyInfo); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + mNetAddr = *addr; + mNetAddrPreResolved = true; + return NS_OK; +} + +nsresult +nsSocketTransport::InitWithFilename(const char *filename) +{ +#if defined(XP_UNIX) + size_t filenameLength = strlen(filename); + + if (filenameLength > sizeof(mNetAddr.local.path) - 1) + return NS_ERROR_FILE_NAME_TOO_LONG; + + mHost.Assign(filename); + mPort = 0; + mTypeCount = 0; + + mNetAddr.local.family = AF_LOCAL; + memcpy(mNetAddr.local.path, filename, filenameLength); + mNetAddr.local.path[filenameLength] = '\0'; + mNetAddrIsSet = true; + + return NS_OK; +#else + return NS_ERROR_SOCKET_ADDRESS_NOT_SUPPORTED; +#endif +} + +nsresult +nsSocketTransport::InitWithConnectedSocket(PRFileDesc *fd, const NetAddr *addr) +{ + MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread, "wrong thread"); + NS_ASSERTION(!mFD.IsInitialized(), "already initialized"); + + char buf[kNetAddrMaxCStrBufSize]; + NetAddrToString(addr, 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); + mPollTimeout = mTimeouts[TIMEOUT_READ_WRITE]; + mState = STATE_TRANSFERRING; + SetSocketName(fd); + mNetAddrIsSet = true; + + { + MutexAutoLock lock(mLock); + + mFD = fd; + mFDref = 1; + mFDconnected = 1; + } + + // 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=%x param=%p]\n", + this, type, 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_LOG(("nsSocketTransport::SendStatus [this=%p status=%x]\n", this, 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]\n", + this, SocketHost().get(), SocketPort(), + mConnectionFlags & nsSocketTransport::BYPASS_CACHE ? + " bypass cache" : "")); + + nsresult rv; + + if (mNetAddrPreResolved) { + mState = STATE_RESOLVING; + return PostEvent(MSG_DNS_LOOKUP_COMPLETE, NS_OK, nullptr); + } + + 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.Equals(NS_LITERAL_CSTRING("*"))) { + 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); + } + } + + nsCOMPtr<nsIDNSService> dns = do_GetService(kDNSServiceCID, &rv); + if (NS_FAILED(rv)) return rv; + + mResolving = true; + + uint32_t dnsFlags = 0; + if (mConnectionFlags & nsSocketTransport::BYPASS_CACHE) + dnsFlags = nsIDNSService::RESOLVE_BYPASS_CACHE; + if (mConnectionFlags & nsSocketTransport::DISABLE_IPV6) + dnsFlags |= nsIDNSService::RESOLVE_DISABLE_IPV6; + if (mConnectionFlags & nsSocketTransport::DISABLE_IPV4) + dnsFlags |= nsIDNSService::RESOLVE_DISABLE_IPV4; + + 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->AsyncResolveExtended(SocketHost(), dnsFlags, mNetworkInterfaceId, this, + nullptr, 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; + + proxyTransparent = false; + usingSSL = false; + + if (mTypeCount == 0) { + fd = PR_OpenTCPSocket(mNetAddr.raw.family); + rv = fd ? NS_OK : NS_ERROR_OUT_OF_MEMORY; + } + else { +#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; + + nsCOMPtr<nsISocketProviderService> spserv = + do_GetService(kSocketProviderServiceCID, &rv); + if (NS_FAILED(rv)) return rv; + + // 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<nsIProxyInfo> proxyInfo = mProxyInfo; + uint32_t controlFlags = 0; + + uint32_t i; + for (i=0; i<mTypeCount; ++i) { + nsCOMPtr<nsISocketProvider> provider; + + SOCKET_LOG((" pushing io layer [%u:%s]\n", i, mTypes[i])); + + rv = spserv->GetSocketProvider(mTypes[i], getter_AddRefs(provider)); + if (NS_FAILED(rv)) + break; + + 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::MITM_OK) + controlFlags |= nsISocketProvider::MITM_OK; + + if (mConnectionFlags & nsISocketTransport::BE_CONSERVATIVE) + controlFlags |= nsISocketProvider::BE_CONSERVATIVE; + + nsCOMPtr<nsISupports> secinfo; + if (i == 0) { + // if this is the first type, we'll want the + // service to allocate a new socket + + // 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() : host, + mHttpsProxy ? mProxyPort : port, + proxyInfo, mOriginAttributes, + controlFlags, &fd, + getter_AddRefs(secinfo)); + + if (NS_SUCCEEDED(rv) && !fd) { + NS_NOTREACHED("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, 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 = (strcmp(mTypes[i], "ssl") == 0); + if (isSSL || (strcmp(mTypes[i], "starttls") == 0)) { + // remember security info and give notification callbacks to PSM... + nsCOMPtr<nsIInterfaceRequestor> callbacks; + { + MutexAutoLock lock(mLock); + mSecInfo = secinfo; + callbacks = mCallbacks; + SOCKET_LOG((" [secinfo=%x callbacks=%x]\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 ((strcmp(mTypes[i], "socks") == 0) || + (strcmp(mTypes[i], "socks4") == 0)) { + // 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=%x]\n", i, mTypes[i], 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() && + !(IsIPAddrAny(&mNetAddr) || IsIPAddrLocal(&mNetAddr))) { + 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 && + IsIPAddrLocal(&mNetAddr)) { + if (SOCKET_LOG_ENABLED()) { + nsAutoCString netAddrCString; + netAddrCString.SetCapacity(kIPv6CStrBufSize); + if (!NetAddrToString(&mNetAddr, + netAddrCString.BeginWriting(), + kIPv6CStrBufSize)) + netAddrCString = NS_LITERAL_CSTRING("<IP-to-string failed>"); + 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=%x]\n", rv)); + return rv; + } + + // Attach network activity monitor + NetworkActivityMonitor::AttachIOLayer(fd); + + 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"); + + // 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; + } + + SOCKET_LOG((" advancing to STATE_CONNECTING\n")); + mState = STATE_CONNECTING; + mPollTimeout = mTimeouts[TIMEOUT_CONNECT]; + SendStatus(NS_NET_STATUS_CONNECTING_TO); + + if (SOCKET_LOG_ENABLED()) { + char buf[kNetAddrMaxCStrBufSize]; + NetAddrToString(&mNetAddr, buf, sizeof(buf)); + SOCKET_LOG((" trying address: %s\n", buf)); + } + + // + // Initiate the connect() to the host... + // + PRNetAddr 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 + + // 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(); + } + + status = PR_Connect(fd, &prAddr, NS_SOCKET_CONNECT_TIMEOUT); + + if (gSocketTransportService->IsTelemetryEnabledAndNotSleepPhase() && + connectStarted) { + 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 { + PRErrorCode code = PR_GetError(); +#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) { + 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=%x]\n", + this, mState, 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 + + // can only recover from errors in these states + if (mState != STATE_RESOLVING && mState != STATE_CONNECTING) + 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()); + } + + // can only recover from these errors + 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) + return false; + + bool tryAgain = false; + + if ((mState == STATE_CONNECTING) && mDNSRecord && + mSocketTransportService->IsTelemetryEnabledAndNotSleepPhase()) { + if (mNetAddr.raw.family == AF_INET) { + Telemetry::Accumulate(Telemetry::IPV4_AND_IPV6_ADDRESS_CONNECTIVITY, + UNSUCCESSFUL_CONNECTING_TO_IPV4_ADDRESS); + } else if (mNetAddr.raw.family == AF_INET6) { + Telemetry::Accumulate(Telemetry::IPV4_AND_IPV6_ADDRESS_CONNECTIVITY, + UNSUCCESSFUL_CONNECTING_TO_IPV6_ADDRESS); + } + } + + if (mConnectionFlags & (DISABLE_IPV6 | DISABLE_IPV4) && + mCondition == NS_ERROR_UNKNOWN_HOST && + mState == STATE_RESOLVING && + !mProxyTransparentResolvesHost) { + SOCKET_LOG((" trying lookup again with both ipv4/ipv6 enabled\n")); + mConnectionFlags &= ~(DISABLE_IPV6 | DISABLE_IPV4); + tryAgain = true; + } + + // try next ip address only if past the resolver stage... + if (mState == STATE_CONNECTING && mDNSRecord) { + nsresult rv = mDNSRecord->GetNextAddr(SocketPort(), &mNetAddr); + if (NS_SUCCEEDED(rv)) { + SOCKET_LOG((" trying again with next ip address\n")); + tryAgain = true; + } + else if (mConnectionFlags & (DISABLE_IPV6 | DISABLE_IPV4)) { + // Drop state to closed. This will trigger new round of DNS + // resolving bellow. + // XXX Could be optimized to only switch the flags to save duplicate + // connection attempts. + SOCKET_LOG((" failed to connect all ipv4-only or ipv6-only hosts," + " trying lookup/connect again with both ipv4/ipv6\n")); + mState = STATE_CLOSED; + mConnectionFlags &= ~(DISABLE_IPV6 | DISABLE_IPV4); + 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=%x]\n", + this, reason)); + + NS_ASSERTION(PR_GetCurrentThread() == gSocketThread, "wrong 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=%x]\n", + this, reason)); + + NS_ASSERTION(PR_GetCurrentThread() == gSocketThread, "wrong 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(PR_GetCurrentThread() == gSocketThread, "wrong thread"); + SOCKET_LOG((" advancing to STATE_TRANSFERRING\n")); + + mPollFlags = (PR_POLL_READ | PR_POLL_WRITE | PR_POLL_EXCEPT); + mPollTimeout = mTimeouts[TIMEOUT_READ_WRITE]; + 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; + +#ifdef XP_WIN + if (!IsWin2003OrLater()) { // windows xp + PRSocketOptionData opt; + opt.option = PR_SockOpt_RecvBufferSize; + if (PR_GetSocketOption(mFD, &opt) == PR_SUCCESS) { + SOCKET_LOG(("%p checking rwin on xp originally=%u\n", + this, opt.value.recv_buffer_size)); + if (opt.value.recv_buffer_size < 65535) { + opt.value.recv_buffer_size = 65535; + PR_SetSocketOption(mFD, &opt); + } + } + } +#endif + } + + // 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%x]", rv)); + } + } + + SendStatus(NS_NET_STATUS_CONNECTED_TO); +} + +void +nsSocketTransport::SetSocketName(PRFileDesc *fd) +{ + MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread, "wrong 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) : mFD(fd) {} + + NS_IMETHOD Run() override + { + nsSocketTransport::CloseSocket(mFD, + gSocketTransportService->IsTelemetryEnabledAndNotSleepPhase()); + return NS_OK; + } +private: + PRFileDesc *mFD; +}; + +void +STS_PRCloseOnSocketTransport(PRFileDesc *fd) +{ + if (gSocketTransportService) { + // Can't PR_Close() a socket off STS thread. Thunk it to STS to die + // FIX - Should use RUN_ON_THREAD once it's generally available + // RUN_ON_THREAD(gSocketThread,WrapRunnableNM(&PR_Close, mFD); + 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"); + SOCKET_LOG(("JIMB: ReleaseFD_Locked: mFDref = %d\n", mFDref)); + + 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 (PR_GetCurrentThread() == gSocketThread) { + 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); + } + 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=%x param=%p]\n", + this, type, status, param)); + + if (NS_FAILED(mCondition)) { + // block event since we're apparently already dead. + SOCKET_LOG((" blocking event [condition=%x]\n", 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")); + // + // 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 +#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 (param) { + mDNSRecord = static_cast<nsIDNSRecord *>(param); + mDNSRecord->GetNextAddr(SocketPort(), &mNetAddr); + } + // 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")); + 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=%x]\n", this, 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_LOG(("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 + 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 (mSocketTransportService->IsTelemetryEnabledAndNotSleepPhase()) { + if (mNetAddr.raw.family == AF_INET) { + Telemetry::Accumulate( + Telemetry::IPV4_AND_IPV6_ADDRESS_CONNECTIVITY, + SUCCESSFUL_CONNECTING_TO_IPV4_ADDRESS); + } else if (mNetAddr.raw.family == AF_INET6) { + 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 + 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=%x]\n", 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=%x]\n", + this, mCondition)); + + NS_ASSERTION(PR_GetCurrentThread() == gSocketThread, "wrong thread"); + + // 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 = IsLoopBackAddress(&mNetAddr); + } +} + +//----------------------------------------------------------------------------- +// 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) +{ + 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, + NS_GetCurrentThread(), + 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::GetNetworkInterfaceId(nsACString_internal &aNetworkInterfaceId) +{ + MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread, "wrong thread"); + aNetworkInterfaceId = mNetworkInterfaceId; + return NS_OK; +} + +NS_IMETHODIMP +nsSocketTransport::SetNetworkInterfaceId(const nsACString_internal &aNetworkInterfaceId) +{ + MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread, "wrong thread"); + mNetworkInterfaceId = aNetworkInterfaceId; + 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); + + NeckoOriginAttributes attrs; + if (!aOriginAttributes.isObject() || !attrs.Init(aCx, aOriginAttributes)) { + return NS_ERROR_INVALID_ARG; + } + + mOriginAttributes = attrs; + return NS_OK; +} + +nsresult +nsSocketTransport::GetOriginAttributes(NeckoOriginAttributes* aOriginAttributes) +{ + NS_ENSURE_ARG(aOriginAttributes); + *aOriginAttributes = mOriginAttributes; + return NS_OK; +} + +nsresult +nsSocketTransport::SetOriginAttributes(const NeckoOriginAttributes& 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 = new 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; + + NS_ADDREF(*addr = new nsNetAddr(&rawAddr)); + + return NS_OK; +} + +NS_IMETHODIMP +nsSocketTransport::GetScriptableSelfAddr(nsINetAddr * *addr) +{ + NetAddr rawAddr; + + nsresult rv; + rv = GetSelfAddr(&rawAddr); + if (NS_FAILED(rv)) + return rv; + + NS_ADDREF(*addr = new nsNetAddr(&rawAddr)); + + return NS_OK; +} + +NS_IMETHODIMP +nsSocketTransport::GetTimeout(uint32_t type, uint32_t *value) +{ + NS_ENSURE_ARG_MAX(type, nsISocketTransport::TIMEOUT_READ_WRITE); + *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); + // truncate overly large timeout values. + mTimeouts[type] = (uint16_t) std::min<uint32_t>(value, UINT16_MAX); + PostEvent(MSG_TIMEOUT_CHANGED); + 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) +{ + // flag host lookup complete for the benefit of the ResolveHost method. + mResolving = false; + + nsresult rv = PostEvent(MSG_DNS_LOOKUP_COMPLETE, status, rec); + + // 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))) { + return mDNSRecord ? + mDNSRecord->QueryInterface(iid, result) : NS_ERROR_NO_INTERFACE; + } + return this->QueryInterface(iid, result); +} + +NS_IMETHODIMP +nsSocketTransport::GetInterfaces(uint32_t *count, nsIID * **array) +{ + return NS_CI_INTERFACE_GETTER_NAME(nsSocketTransport)(count, array); +} + +NS_IMETHODIMP +nsSocketTransport::GetScriptableHelper(nsIXPCScriptable **_retval) +{ + *_retval = nullptr; + return NS_OK; +} + +NS_IMETHODIMP +nsSocketTransport::GetContractID(char * *aContractID) +{ + *aContractID = nullptr; + return NS_OK; +} + +NS_IMETHODIMP +nsSocketTransport::GetClassDescription(char * *aClassDescription) +{ + *aClassDescription = nullptr; + 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) +{ + mConnectionFlags = value; + mIsPrivate = value & nsISocketTransport::NO_PERMANENT_STORAGE; + return NS_OK; +} + +void +nsSocketTransport::OnKeepaliveEnabledPrefChange(bool aEnabled) +{ + MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread, "wrong 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%x]", + aEnabled ? "enable" : "disable", 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%x]", rv)); + return rv; + } + rv = fd.SetKeepaliveEnabled(enable); + if (NS_WARN_IF(NS_FAILED(rv))) { + SOCKET_LOG((" SetKeepaliveEnabled failed rv[0x%x]", 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(PR_GetCurrentThread() == gSocketThread, "wrong 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%x] initializing keepalive vals", + this, 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%x]", 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(PR_GetCurrentThread() == gSocketThread, "wrong 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(PR_GetCurrentThread() == gSocketThread, "wrong 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(PR_GetCurrentThread() == gSocketThread, "wrong 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(PR_GetCurrentThread() == gSocketThread, "wrong 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::ID aIDNormal, + Telemetry::ID aIDShutdown, + Telemetry::ID aIDConnectivityChange, + Telemetry::ID aIDLinkChange, + Telemetry::ID 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)); + } +} + +} // namespace net +} // namespace mozilla |