summaryrefslogtreecommitdiffstats
path: root/netwerk/base/nsSocketTransport2.cpp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
commit36d22d82aa202bb199967e9512281e9a53db42c9 (patch)
tree105e8c98ddea1c1e4784a60a5a6410fa416be2de /netwerk/base/nsSocketTransport2.cpp
parentInitial commit. (diff)
downloadfirefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.tar.xz
firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.zip
Adding upstream version 115.7.0esr.upstream/115.7.0esr
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'netwerk/base/nsSocketTransport2.cpp')
-rw-r--r--netwerk/base/nsSocketTransport2.cpp3404
1 files changed, 3404 insertions, 0 deletions
diff --git a/netwerk/base/nsSocketTransport2.cpp b/netwerk/base/nsSocketTransport2.cpp
new file mode 100644
index 0000000000..b998ef886a
--- /dev/null
+++ b/netwerk/base/nsSocketTransport2.cpp
@@ -0,0 +1,3404 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=4 sw=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <algorithm>
+
+#include "nsSocketTransport2.h"
+
+#include "IOActivityMonitor.h"
+#include "NSSErrorsService.h"
+#include "NetworkDataCountLayer.h"
+#include "QuicSocketControl.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/StaticPrefs_network.h"
+#include "mozilla/SyncRunnable.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/dom/ToJSValue.h"
+#include "mozilla/net/NeckoChild.h"
+#include "mozilla/net/SSLTokensCache.h"
+#include "nsCOMPtr.h"
+#include "nsICancelable.h"
+#include "nsIClassInfoImpl.h"
+#include "nsIDNSByTypeRecord.h"
+#include "nsIDNSRecord.h"
+#include "nsIDNSService.h"
+#include "nsIOService.h"
+#include "nsIPipe.h"
+#include "nsISocketProvider.h"
+#include "nsITLSSocketControl.h"
+#include "nsNetAddr.h"
+#include "nsNetCID.h"
+#include "nsNetSegmentUtils.h"
+#include "nsNetUtil.h"
+#include "nsPrintfCString.h"
+#include "nsProxyInfo.h"
+#include "nsSocketProviderService.h"
+#include "nsStreamUtils.h"
+#include "nsThreadUtils.h"
+#include "nsTransportUtils.h"
+#include "nsURLHelper.h"
+#include "prerr.h"
+#include "sslexp.h"
+#include "xpcpublic.h"
+
+#if defined(FUZZING)
+# include "FuzzyLayer.h"
+# include "FuzzySocketControl.h"
+# include "mozilla/StaticPrefs_fuzzing.h"
+#endif
+
+#if defined(XP_WIN)
+# include "ShutdownLayer.h"
+#endif
+
+/* Following inclusions required for keepalive config not supported by NSPR. */
+#include "private/pprio.h"
+#if defined(XP_WIN)
+# include <winsock2.h>
+# include <mstcpip.h>
+#elif defined(XP_UNIX)
+# include <errno.h>
+# include <netinet/tcp.h>
+#endif
+/* End keepalive config inclusions. */
+
+#define SUCCESSFUL_CONNECTING_TO_IPV4_ADDRESS 0
+#define UNSUCCESSFUL_CONNECTING_TO_IPV4_ADDRESS 1
+#define SUCCESSFUL_CONNECTING_TO_IPV6_ADDRESS 2
+#define UNSUCCESSFUL_CONNECTING_TO_IPV6_ADDRESS 3
+
+//-----------------------------------------------------------------------------
+
+static NS_DEFINE_CID(kDNSServiceCID, NS_DNSSERVICE_CID);
+
+//-----------------------------------------------------------------------------
+
+namespace mozilla {
+namespace net {
+
+class nsSocketEvent : public Runnable {
+ public:
+ nsSocketEvent(nsSocketTransport* transport, uint32_t type,
+ nsresult status = NS_OK, nsISupports* param = nullptr,
+ std::function<void()>&& task = nullptr)
+ : Runnable("net::nsSocketEvent"),
+ mTransport(transport),
+ mType(type),
+ mStatus(status),
+ mParam(param),
+ mTask(std::move(task)) {}
+
+ NS_IMETHOD Run() override {
+ mTransport->OnSocketEvent(mType, mStatus, mParam, std::move(mTask));
+ return NS_OK;
+ }
+
+ private:
+ RefPtr<nsSocketTransport> mTransport;
+
+ uint32_t mType;
+ nsresult mStatus;
+ nsCOMPtr<nsISupports> mParam;
+ std::function<void()> mTask;
+};
+
+//-----------------------------------------------------------------------------
+
+// #define TEST_CONNECT_ERRORS
+#ifdef TEST_CONNECT_ERRORS
+# include <stdlib.h>
+static PRErrorCode RandomizeConnectError(PRErrorCode code) {
+ //
+ // To test out these errors, load http://www.yahoo.com/. It should load
+ // correctly despite the random occurrence of these errors.
+ //
+ int n = rand();
+ if (n > RAND_MAX / 2) {
+ struct {
+ PRErrorCode err_code;
+ const char* err_name;
+ } errors[] = {
+ //
+ // These errors should be recoverable provided there is another
+ // IP address in mDNSRecord.
+ //
+ {PR_CONNECT_REFUSED_ERROR, "PR_CONNECT_REFUSED_ERROR"},
+ {PR_CONNECT_TIMEOUT_ERROR, "PR_CONNECT_TIMEOUT_ERROR"},
+ //
+ // This error will cause this socket transport to error out;
+ // however, if the consumer is HTTP, then the HTTP transaction
+ // should be restarted when this error occurs.
+ //
+ {PR_CONNECT_RESET_ERROR, "PR_CONNECT_RESET_ERROR"},
+ };
+ n = n % (sizeof(errors) / sizeof(errors[0]));
+ code = errors[n].err_code;
+ SOCKET_LOG(("simulating NSPR error %d [%s]\n", code, errors[n].err_name));
+ }
+ return code;
+}
+#endif
+
+//-----------------------------------------------------------------------------
+
+nsresult ErrorAccordingToNSPR(PRErrorCode errorCode) {
+ nsresult rv = NS_ERROR_FAILURE;
+ switch (errorCode) {
+ case PR_WOULD_BLOCK_ERROR:
+ rv = NS_BASE_STREAM_WOULD_BLOCK;
+ break;
+ case PR_CONNECT_ABORTED_ERROR:
+ case PR_CONNECT_RESET_ERROR:
+ rv = NS_ERROR_NET_RESET;
+ break;
+ case PR_END_OF_FILE_ERROR: // XXX document this correlation
+ rv = NS_ERROR_NET_INTERRUPT;
+ break;
+ case PR_CONNECT_REFUSED_ERROR:
+ // We lump the following NSPR codes in with PR_CONNECT_REFUSED_ERROR. We
+ // could get better diagnostics by adding distinct XPCOM error codes for
+ // each of these, but there are a lot of places in Gecko that check
+ // specifically for NS_ERROR_CONNECTION_REFUSED, all of which would need to
+ // be checked.
+ case PR_NETWORK_UNREACHABLE_ERROR:
+ case PR_HOST_UNREACHABLE_ERROR:
+ case PR_ADDRESS_NOT_AVAILABLE_ERROR:
+ // Treat EACCES as a soft error since (at least on Linux) connect() returns
+ // EACCES when an IPv6 connection is blocked by a firewall. See bug 270784.
+ case PR_NO_ACCESS_RIGHTS_ERROR:
+ rv = NS_ERROR_CONNECTION_REFUSED;
+ break;
+ case PR_ADDRESS_NOT_SUPPORTED_ERROR:
+ rv = NS_ERROR_SOCKET_ADDRESS_NOT_SUPPORTED;
+ break;
+ case PR_IO_TIMEOUT_ERROR:
+ case PR_CONNECT_TIMEOUT_ERROR:
+ rv = NS_ERROR_NET_TIMEOUT;
+ break;
+ case PR_OUT_OF_MEMORY_ERROR:
+ // These really indicate that the descriptor table filled up, or that the
+ // kernel ran out of network buffers - but nobody really cares which part of
+ // the system ran out of memory.
+ case PR_PROC_DESC_TABLE_FULL_ERROR:
+ case PR_SYS_DESC_TABLE_FULL_ERROR:
+ case PR_INSUFFICIENT_RESOURCES_ERROR:
+ rv = NS_ERROR_OUT_OF_MEMORY;
+ break;
+ case PR_ADDRESS_IN_USE_ERROR:
+ rv = NS_ERROR_SOCKET_ADDRESS_IN_USE;
+ break;
+ // These filename-related errors can arise when using Unix-domain sockets.
+ case PR_FILE_NOT_FOUND_ERROR:
+ rv = NS_ERROR_FILE_NOT_FOUND;
+ break;
+ case PR_IS_DIRECTORY_ERROR:
+ rv = NS_ERROR_FILE_IS_DIRECTORY;
+ break;
+ case PR_LOOP_ERROR:
+ rv = NS_ERROR_FILE_UNRESOLVABLE_SYMLINK;
+ break;
+ case PR_NAME_TOO_LONG_ERROR:
+ rv = NS_ERROR_FILE_NAME_TOO_LONG;
+ break;
+ case PR_NO_DEVICE_SPACE_ERROR:
+ rv = NS_ERROR_FILE_NO_DEVICE_SPACE;
+ break;
+ case PR_NOT_DIRECTORY_ERROR:
+ rv = NS_ERROR_FILE_NOT_DIRECTORY;
+ break;
+ case PR_READ_ONLY_FILESYSTEM_ERROR:
+ rv = NS_ERROR_FILE_READ_ONLY;
+ break;
+ case PR_BAD_ADDRESS_ERROR:
+ rv = NS_ERROR_UNKNOWN_HOST;
+ break;
+ default:
+ if (psm::IsNSSErrorCode(errorCode)) {
+ rv = psm::GetXPCOMFromNSSError(errorCode);
+ }
+ break;
+
+ // NSPR's socket code can return these, but they're not worth breaking out
+ // into their own error codes, distinct from NS_ERROR_FAILURE:
+ //
+ // PR_BAD_DESCRIPTOR_ERROR
+ // PR_INVALID_ARGUMENT_ERROR
+ // PR_NOT_SOCKET_ERROR
+ // PR_NOT_TCP_SOCKET_ERROR
+ // These would indicate a bug internal to the component.
+ //
+ // PR_PROTOCOL_NOT_SUPPORTED_ERROR
+ // This means that we can't use the given "protocol" (like
+ // IPPROTO_TCP or IPPROTO_UDP) with a socket of the given type. As
+ // above, this indicates an internal bug.
+ //
+ // PR_IS_CONNECTED_ERROR
+ // This indicates that we've applied a system call like 'bind' or
+ // 'connect' to a socket that is already connected. The socket
+ // components manage each file descriptor's state, and in some cases
+ // handle this error result internally. We shouldn't be returning
+ // this to our callers.
+ //
+ // PR_IO_ERROR
+ // This is so vague that NS_ERROR_FAILURE is just as good.
+ }
+ SOCKET_LOG(("ErrorAccordingToNSPR [in=%d out=%" PRIx32 "]\n", errorCode,
+ static_cast<uint32_t>(rv)));
+ return rv;
+}
+
+//-----------------------------------------------------------------------------
+// socket input stream impl
+//-----------------------------------------------------------------------------
+
+nsSocketInputStream::nsSocketInputStream(nsSocketTransport* trans)
+ : mTransport(trans) {}
+
+// called on the socket transport thread...
+//
+// condition : failure code if socket has been closed
+//
+void nsSocketInputStream::OnSocketReady(nsresult condition) {
+ SOCKET_LOG(("nsSocketInputStream::OnSocketReady [this=%p cond=%" PRIx32 "]\n",
+ this, static_cast<uint32_t>(condition)));
+
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ nsCOMPtr<nsIInputStreamCallback> callback;
+ {
+ MutexAutoLock lock(mTransport->mLock);
+
+ // update condition, but be careful not to erase an already
+ // existing error condition.
+ if (NS_SUCCEEDED(mCondition)) mCondition = condition;
+
+ // ignore event if only waiting for closure and not closed.
+ if (NS_FAILED(mCondition) || !(mCallbackFlags & WAIT_CLOSURE_ONLY)) {
+ callback = std::move(mCallback);
+ mCallbackFlags = 0;
+ }
+ }
+
+ if (callback) callback->OnInputStreamReady(this);
+}
+
+NS_IMPL_QUERY_INTERFACE(nsSocketInputStream, nsIInputStream,
+ nsIAsyncInputStream)
+
+NS_IMETHODIMP_(MozExternalRefCountType)
+nsSocketInputStream::AddRef() {
+ ++mReaderRefCnt;
+ return mTransport->AddRef();
+}
+
+NS_IMETHODIMP_(MozExternalRefCountType)
+nsSocketInputStream::Release() {
+ if (--mReaderRefCnt == 0) Close();
+ return mTransport->Release();
+}
+
+NS_IMETHODIMP
+nsSocketInputStream::Close() { return CloseWithStatus(NS_BASE_STREAM_CLOSED); }
+
+NS_IMETHODIMP
+nsSocketInputStream::Available(uint64_t* avail) {
+ SOCKET_LOG(("nsSocketInputStream::Available [this=%p]\n", this));
+
+ *avail = 0;
+
+ PRFileDesc* fd;
+ {
+ MutexAutoLock lock(mTransport->mLock);
+
+ if (NS_FAILED(mCondition)) return mCondition;
+
+ fd = mTransport->GetFD_Locked();
+ if (!fd) return NS_OK;
+ }
+
+ // cannot hold lock while calling NSPR. (worried about the fact that PSM
+ // synchronously proxies notifications over to the UI thread, which could
+ // mistakenly try to re-enter this code.)
+ int32_t n = PR_Available(fd);
+
+ // PSM does not implement PR_Available() so do a best approximation of it
+ // with MSG_PEEK
+ if ((n == -1) && (PR_GetError() == PR_NOT_IMPLEMENTED_ERROR)) {
+ char c;
+
+ n = PR_Recv(fd, &c, 1, PR_MSG_PEEK, 0);
+ SOCKET_LOG(
+ ("nsSocketInputStream::Available [this=%p] "
+ "using PEEK backup n=%d]\n",
+ this, n));
+ }
+
+ nsresult rv;
+ {
+ MutexAutoLock lock(mTransport->mLock);
+
+ mTransport->ReleaseFD_Locked(fd);
+
+ if (n >= 0) {
+ *avail = n;
+ } else {
+ PRErrorCode code = PR_GetError();
+ if (code == PR_WOULD_BLOCK_ERROR) return NS_OK;
+ mCondition = ErrorAccordingToNSPR(code);
+ }
+ rv = mCondition;
+ }
+ if (NS_FAILED(rv)) mTransport->OnInputClosed(rv);
+ return rv;
+}
+
+NS_IMETHODIMP
+nsSocketInputStream::StreamStatus() {
+ SOCKET_LOG(("nsSocketInputStream::StreamStatus [this=%p]\n", this));
+
+ MutexAutoLock lock(mTransport->mLock);
+ return mCondition;
+}
+
+NS_IMETHODIMP
+nsSocketInputStream::Read(char* buf, uint32_t count, uint32_t* countRead) {
+ SOCKET_LOG(("nsSocketInputStream::Read [this=%p count=%u]\n", this, count));
+
+ *countRead = 0;
+
+ PRFileDesc* fd = nullptr;
+ {
+ MutexAutoLock lock(mTransport->mLock);
+
+ if (NS_FAILED(mCondition)) {
+ return (mCondition == NS_BASE_STREAM_CLOSED) ? NS_OK : mCondition;
+ }
+
+ fd = mTransport->GetFD_Locked();
+ if (!fd) return NS_BASE_STREAM_WOULD_BLOCK;
+ }
+
+ SOCKET_LOG((" calling PR_Read [count=%u]\n", count));
+
+ // cannot hold lock while calling NSPR. (worried about the fact that PSM
+ // synchronously proxies notifications over to the UI thread, which could
+ // mistakenly try to re-enter this code.)
+ int32_t n = PR_Read(fd, buf, count);
+
+ SOCKET_LOG((" PR_Read returned [n=%d]\n", n));
+
+ nsresult rv = NS_OK;
+ {
+ MutexAutoLock lock(mTransport->mLock);
+
+#ifdef ENABLE_SOCKET_TRACING
+ if (n > 0) mTransport->TraceInBuf(buf, n);
+#endif
+
+ mTransport->ReleaseFD_Locked(fd);
+
+ if (n > 0) {
+ mByteCount += (*countRead = n);
+ } else if (n < 0) {
+ PRErrorCode code = PR_GetError();
+ if (code == PR_WOULD_BLOCK_ERROR) return NS_BASE_STREAM_WOULD_BLOCK;
+ mCondition = ErrorAccordingToNSPR(code);
+ }
+ rv = mCondition;
+ }
+ if (NS_FAILED(rv)) mTransport->OnInputClosed(rv);
+
+ // only send this notification if we have indeed read some data.
+ // see bug 196827 for an example of why this is important.
+ if (n > 0) mTransport->SendStatus(NS_NET_STATUS_RECEIVING_FROM);
+ return rv;
+}
+
+NS_IMETHODIMP
+nsSocketInputStream::ReadSegments(nsWriteSegmentFun writer, void* closure,
+ uint32_t count, uint32_t* countRead) {
+ // socket stream is unbuffered
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsSocketInputStream::IsNonBlocking(bool* nonblocking) {
+ *nonblocking = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketInputStream::CloseWithStatus(nsresult reason) {
+ SOCKET_LOG(("nsSocketInputStream::CloseWithStatus [this=%p reason=%" PRIx32
+ "]\n",
+ this, static_cast<uint32_t>(reason)));
+
+ // may be called from any thread
+
+ nsresult rv;
+ {
+ MutexAutoLock lock(mTransport->mLock);
+
+ if (NS_SUCCEEDED(mCondition)) {
+ rv = mCondition = reason;
+ } else {
+ rv = NS_OK;
+ }
+ }
+ if (NS_FAILED(rv)) mTransport->OnInputClosed(rv);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketInputStream::AsyncWait(nsIInputStreamCallback* callback, uint32_t flags,
+ uint32_t amount, nsIEventTarget* target) {
+ SOCKET_LOG(("nsSocketInputStream::AsyncWait [this=%p]\n", this));
+
+ bool hasError = false;
+ {
+ MutexAutoLock lock(mTransport->mLock);
+
+ if (callback && target) {
+ //
+ // build event proxy
+ //
+ mCallback = NS_NewInputStreamReadyEvent("nsSocketInputStream::AsyncWait",
+ callback, target);
+ } else {
+ mCallback = callback;
+ }
+ mCallbackFlags = flags;
+
+ hasError = NS_FAILED(mCondition);
+ } // unlock mTransport->mLock
+
+ if (hasError) {
+ // OnSocketEvent will call OnInputStreamReady with an error code after
+ // going through the event loop. We do this because most socket callers
+ // do not expect AsyncWait() to synchronously execute the OnInputStreamReady
+ // callback.
+ mTransport->PostEvent(nsSocketTransport::MSG_INPUT_PENDING);
+ } else {
+ mTransport->OnInputPending();
+ }
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// socket output stream impl
+//-----------------------------------------------------------------------------
+
+nsSocketOutputStream::nsSocketOutputStream(nsSocketTransport* trans)
+ : mTransport(trans) {}
+
+// called on the socket transport thread...
+//
+// condition : failure code if socket has been closed
+//
+void nsSocketOutputStream::OnSocketReady(nsresult condition) {
+ SOCKET_LOG(("nsSocketOutputStream::OnSocketReady [this=%p cond=%" PRIx32
+ "]\n",
+ this, static_cast<uint32_t>(condition)));
+
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ nsCOMPtr<nsIOutputStreamCallback> callback;
+ {
+ MutexAutoLock lock(mTransport->mLock);
+
+ // update condition, but be careful not to erase an already
+ // existing error condition.
+ if (NS_SUCCEEDED(mCondition)) mCondition = condition;
+
+ // ignore event if only waiting for closure and not closed.
+ if (NS_FAILED(mCondition) || !(mCallbackFlags & WAIT_CLOSURE_ONLY)) {
+ callback = std::move(mCallback);
+ mCallbackFlags = 0;
+ }
+ }
+
+ if (callback) callback->OnOutputStreamReady(this);
+}
+
+NS_IMPL_QUERY_INTERFACE(nsSocketOutputStream, nsIOutputStream,
+ nsIAsyncOutputStream)
+
+NS_IMETHODIMP_(MozExternalRefCountType)
+nsSocketOutputStream::AddRef() {
+ ++mWriterRefCnt;
+ return mTransport->AddRef();
+}
+
+NS_IMETHODIMP_(MozExternalRefCountType)
+nsSocketOutputStream::Release() {
+ if (--mWriterRefCnt == 0) Close();
+ return mTransport->Release();
+}
+
+NS_IMETHODIMP
+nsSocketOutputStream::Close() { return CloseWithStatus(NS_BASE_STREAM_CLOSED); }
+
+NS_IMETHODIMP
+nsSocketOutputStream::Flush() { return NS_OK; }
+
+NS_IMETHODIMP
+nsSocketOutputStream::StreamStatus() {
+ MutexAutoLock lock(mTransport->mLock);
+ return mCondition;
+}
+
+NS_IMETHODIMP
+nsSocketOutputStream::Write(const char* buf, uint32_t count,
+ uint32_t* countWritten) {
+ SOCKET_LOG(("nsSocketOutputStream::Write [this=%p count=%u]\n", this, count));
+
+ *countWritten = 0;
+
+ // A write of 0 bytes can be used to force the initial SSL handshake, so do
+ // not reject that.
+
+ PRFileDesc* fd = nullptr;
+ {
+ MutexAutoLock lock(mTransport->mLock);
+
+ if (NS_FAILED(mCondition)) return mCondition;
+
+ fd = mTransport->GetFD_Locked();
+ if (!fd) return NS_BASE_STREAM_WOULD_BLOCK;
+ }
+
+ SOCKET_LOG((" calling PR_Write [count=%u]\n", count));
+
+ // cannot hold lock while calling NSPR. (worried about the fact that PSM
+ // synchronously proxies notifications over to the UI thread, which could
+ // mistakenly try to re-enter this code.)
+ int32_t n = PR_Write(fd, buf, count);
+
+ SOCKET_LOG((" PR_Write returned [n=%d]\n", n));
+
+ nsresult rv = NS_OK;
+ {
+ MutexAutoLock lock(mTransport->mLock);
+
+#ifdef ENABLE_SOCKET_TRACING
+ if (n > 0) mTransport->TraceOutBuf(buf, n);
+#endif
+
+ mTransport->ReleaseFD_Locked(fd);
+
+ if (n > 0) {
+ mByteCount += (*countWritten = n);
+ } 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() {
+ MOZ_RELEASE_ASSERT(!mAttached);
+ SOCKET_LOG(("destroying nsSocketTransport @%p\n", this));
+}
+
+nsresult nsSocketTransport::Init(const nsTArray<nsCString>& types,
+ const nsACString& host, uint16_t port,
+ const nsACString& hostRoute,
+ uint16_t portRoute,
+ nsIProxyInfo* givenProxyInfo,
+ nsIDNSRecord* dnsRecord) {
+ nsCOMPtr<nsProxyInfo> proxyInfo;
+ if (givenProxyInfo) {
+ proxyInfo = do_QueryInterface(givenProxyInfo);
+ NS_ENSURE_ARG(proxyInfo);
+ }
+
+ if (dnsRecord) {
+ mExternalDNSResolution = true;
+ mDNSRecord = do_QueryInterface(dnsRecord);
+ mDNSRecord->IsTRR(&mResolvedByTRR);
+ mDNSRecord->GetEffectiveTRRMode(&mEffectiveTRRMode);
+ mDNSRecord->GetTrrSkipReason(&mTRRSkipReason);
+ }
+
+ // init socket type info
+
+ mOriginHost = host;
+ mOriginPort = port;
+ if (!hostRoute.IsEmpty()) {
+ mHost = hostRoute;
+ mPort = portRoute;
+ } else {
+ mHost = host;
+ mPort = port;
+ }
+
+ // A subtle check we don't enter this method more than once for the socket
+ // transport lifetime. Disable on TSan builds to prevent race checking, we
+ // don't want an atomic here for perf reasons!
+#ifndef MOZ_TSAN
+ MOZ_ASSERT(!mPortRemappingApplied);
+#endif // !MOZ_TSAN
+
+ if (proxyInfo) {
+ mHttpsProxy = proxyInfo->IsHTTPS();
+ }
+
+ const char* proxyType = nullptr;
+ mProxyInfo = proxyInfo;
+ if (proxyInfo) {
+ mProxyPort = proxyInfo->Port();
+ mProxyHost = proxyInfo->Host();
+ // grab proxy type (looking for "socks" for example)
+ proxyType = proxyInfo->Type();
+ if (proxyType && (proxyInfo->IsHTTP() || proxyInfo->IsHTTPS() ||
+ proxyInfo->IsDirect() || !strcmp(proxyType, "unknown"))) {
+ proxyType = nullptr;
+ }
+ }
+
+ SOCKET_LOG1(
+ ("nsSocketTransport::Init [this=%p host=%s:%hu origin=%s:%d "
+ "proxy=%s:%hu]\n",
+ this, mHost.get(), mPort, mOriginHost.get(), mOriginPort,
+ mProxyHost.get(), mProxyPort));
+
+ // include proxy type as a socket type if proxy type is not "http"
+ uint32_t typeCount = types.Length() + (proxyType != nullptr);
+ if (!typeCount) return NS_OK;
+
+ // if we have socket types, then the socket provider service had
+ // better exist!
+ nsresult rv;
+ nsCOMPtr<nsISocketProviderService> spserv =
+ nsSocketProviderService::GetOrCreate();
+
+ if (!mTypes.SetCapacity(typeCount, fallible)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ // now verify that each socket type has a registered socket provider.
+ for (uint32_t i = 0, type = 0; i < typeCount; ++i) {
+ // store socket types
+ if (i == 0 && proxyType) {
+ mTypes.AppendElement(proxyType);
+ } else {
+ mTypes.AppendElement(types[type++]);
+ }
+
+ nsCOMPtr<nsISocketProvider> provider;
+ rv = spserv->GetSocketProvider(mTypes[i].get(), getter_AddRefs(provider));
+ if (NS_FAILED(rv)) {
+ NS_WARNING("no registered socket provider");
+ return rv;
+ }
+
+ // note if socket type corresponds to a transparent proxy
+ // XXX don't hardcode SOCKS here (use proxy info's flags instead).
+ if (mTypes[i].EqualsLiteral("socks") || mTypes[i].EqualsLiteral("socks4")) {
+ mProxyTransparent = true;
+
+ if (proxyInfo->Flags() & nsIProxyInfo::TRANSPARENT_PROXY_RESOLVES_HOST) {
+ // we want the SOCKS layer to send the hostname
+ // and port to the proxy and let it do the DNS.
+ mProxyTransparentResolvesHost = true;
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+#if defined(XP_UNIX)
+nsresult nsSocketTransport::InitWithFilename(const char* filename) {
+ return InitWithName(filename, strlen(filename));
+}
+
+nsresult nsSocketTransport::InitWithName(const char* name, size_t length) {
+ if (length > sizeof(mNetAddr.local.path) - 1) {
+ return NS_ERROR_FILE_NAME_TOO_LONG;
+ }
+
+ if (!name[0] && length > 1) {
+ // name is abstract address name that is supported on Linux only
+# if defined(XP_LINUX)
+ mHost.Assign(name + 1, length - 1);
+# else
+ return NS_ERROR_SOCKET_ADDRESS_NOT_SUPPORTED;
+# endif
+ } else {
+ // The name isn't abstract socket address. So this is Unix domain
+ // socket that has file path.
+ mHost.Assign(name, length);
+ }
+ mPort = 0;
+
+ mNetAddr.local.family = AF_LOCAL;
+ memcpy(mNetAddr.local.path, name, length);
+ mNetAddr.local.path[length] = '\0';
+ mNetAddrIsSet = true;
+
+ return NS_OK;
+}
+#endif
+
+nsresult nsSocketTransport::InitWithConnectedSocket(PRFileDesc* fd,
+ const NetAddr* addr) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ char buf[kNetAddrMaxCStrBufSize];
+ addr->ToStringBuffer(buf, sizeof(buf));
+ mHost.Assign(buf);
+
+ uint16_t port;
+ if (addr->raw.family == AF_INET) {
+ port = addr->inet.port;
+ } else if (addr->raw.family == AF_INET6) {
+ port = addr->inet6.port;
+ } else {
+ port = 0;
+ }
+ mPort = ntohs(port);
+
+ memcpy(&mNetAddr, addr, sizeof(NetAddr));
+
+ mPollFlags = (PR_POLL_READ | PR_POLL_WRITE | PR_POLL_EXCEPT);
+ mState = STATE_TRANSFERRING;
+ SetSocketName(fd);
+ mNetAddrIsSet = true;
+
+ {
+ MutexAutoLock lock(mLock);
+ NS_ASSERTION(!mFD.IsInitialized(), "already initialized");
+ mFD = fd;
+ mFDref = 1;
+ mFDconnected = true;
+ mPollTimeout = mTimeouts[TIMEOUT_READ_WRITE];
+ }
+
+ // make sure new socket is non-blocking
+ PRSocketOptionData opt;
+ opt.option = PR_SockOpt_Nonblocking;
+ opt.value.non_blocking = true;
+ PR_SetSocketOption(fd, &opt);
+
+ SOCKET_LOG(
+ ("nsSocketTransport::InitWithConnectedSocket [this=%p addr=%s:%hu]\n",
+ this, mHost.get(), mPort));
+
+ // jump to InitiateSocket to get ourselves attached to the STS poll list.
+ return PostEvent(MSG_RETRY_INIT_SOCKET);
+}
+
+nsresult nsSocketTransport::InitWithConnectedSocket(
+ PRFileDesc* aFD, const NetAddr* aAddr, nsIInterfaceRequestor* aCallbacks) {
+ {
+ MutexAutoLock lock(mLock);
+ mCallbacks = aCallbacks;
+ }
+ return InitWithConnectedSocket(aFD, aAddr);
+}
+
+nsresult nsSocketTransport::PostEvent(uint32_t type, nsresult status,
+ nsISupports* param,
+ std::function<void()>&& task) {
+ SOCKET_LOG(("nsSocketTransport::PostEvent [this=%p type=%u status=%" PRIx32
+ " param=%p]\n",
+ this, type, static_cast<uint32_t>(status), param));
+
+ nsCOMPtr<nsIRunnable> event =
+ new nsSocketEvent(this, type, status, param, std::move(task));
+ if (!event) return NS_ERROR_OUT_OF_MEMORY;
+
+ return mSocketTransportService->Dispatch(event, NS_DISPATCH_NORMAL);
+}
+
+void nsSocketTransport::SendStatus(nsresult status) {
+ SOCKET_LOG1(("nsSocketTransport::SendStatus [this=%p status=%" PRIx32 "]\n",
+ this, static_cast<uint32_t>(status)));
+
+ nsCOMPtr<nsITransportEventSink> sink;
+ uint64_t progress;
+ {
+ MutexAutoLock lock(mLock);
+ sink = mEventSink;
+ switch (status) {
+ case NS_NET_STATUS_SENDING_TO:
+ progress = mOutput.ByteCount();
+ 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));
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ nsresult rv;
+
+ if (!mProxyHost.IsEmpty()) {
+ if (!mProxyTransparent || mProxyTransparentResolvesHost) {
+#if defined(XP_UNIX)
+ MOZ_ASSERT(!mNetAddrIsSet || mNetAddr.raw.family != AF_LOCAL,
+ "Unix domain sockets can't be used with proxies");
+#endif
+ // When not resolving mHost locally, we still want to ensure that
+ // it only contains valid characters. See bug 304904 for details.
+ // Sometimes the end host is not yet known and mHost is *
+ if (!net_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,
+ NS_NewRunnableFunction("nsSocketTransport::ResolveHost->GetDNSService",
+ initTask));
+ } else {
+ initTask();
+ }
+ if (!dns) {
+ return NS_ERROR_FAILURE;
+ }
+
+ mResolving = true;
+
+ nsIDNSService::DNSFlags dnsFlags = nsIDNSService::RESOLVE_DEFAULT_FLAGS;
+ if (mConnectionFlags & nsSocketTransport::BYPASS_CACHE) {
+ dnsFlags = nsIDNSService::RESOLVE_BYPASS_CACHE;
+ }
+ if (mConnectionFlags & nsSocketTransport::REFRESH_CACHE) {
+ dnsFlags = nsIDNSService::RESOLVE_REFRESH_CACHE;
+ }
+ if (mConnectionFlags & nsSocketTransport::DISABLE_IPV6) {
+ dnsFlags |= nsIDNSService::RESOLVE_DISABLE_IPV6;
+ }
+ if (mConnectionFlags & nsSocketTransport::DISABLE_IPV4) {
+ dnsFlags |= nsIDNSService::RESOLVE_DISABLE_IPV4;
+ }
+ if (mConnectionFlags & nsSocketTransport::DISABLE_TRR) {
+ dnsFlags |= nsIDNSService::RESOLVE_DISABLE_TRR;
+ }
+
+ if (mConnectionFlags & nsSocketTransport::USE_IP_HINT_ADDRESS) {
+ dnsFlags |= nsIDNSService::RESOLVE_IP_HINT;
+ }
+
+ dnsFlags |= nsIDNSService::GetFlagsFromTRRMode(
+ nsISocketTransport::GetTRRModeFromFlags(mConnectionFlags));
+
+ // When we get here, we are not resolving using any configured proxy likely
+ // because of individual proxy setting on the request or because the host is
+ // excluded from proxying. Hence, force resolution despite global proxy-DNS
+ // configuration.
+ dnsFlags |= nsIDNSService::RESOLVE_IGNORE_SOCKS_DNS;
+
+ NS_ASSERTION(!(dnsFlags & nsIDNSService::RESOLVE_DISABLE_IPV6) ||
+ !(dnsFlags & nsIDNSService::RESOLVE_DISABLE_IPV4),
+ "Setting both RESOLVE_DISABLE_IPV6 and RESOLVE_DISABLE_IPV4");
+
+ SendStatus(NS_NET_STATUS_RESOLVING_HOST);
+
+ if (!SocketHost().Equals(mOriginHost)) {
+ SOCKET_LOG(("nsSocketTransport %p origin %s doing dns for %s\n", this,
+ mOriginHost.get(), SocketHost().get()));
+ }
+ rv =
+ dns->AsyncResolveNative(SocketHost(), nsIDNSService::RESOLVE_TYPE_DEFAULT,
+ dnsFlags, nullptr, this, mSocketTransportService,
+ mOriginAttributes, getter_AddRefs(mDNSRequest));
+
+ if (NS_SUCCEEDED(rv)) {
+ SOCKET_LOG((" advancing to STATE_RESOLVING\n"));
+ mState = STATE_RESOLVING;
+ }
+ return rv;
+}
+
+nsresult nsSocketTransport::BuildSocket(PRFileDesc*& fd, bool& proxyTransparent,
+ bool& usingSSL) {
+ SOCKET_LOG(("nsSocketTransport::BuildSocket [this=%p]\n", this));
+
+ nsresult rv = NS_OK;
+
+ proxyTransparent = false;
+ usingSSL = false;
+
+ if (mTypes.IsEmpty()) {
+ fd = PR_OpenTCPSocket(mNetAddr.raw.family);
+ if (!fd) {
+ SOCKET_LOG((" error creating TCP nspr socket [rv=%" PRIx32 "]\n",
+ static_cast<uint32_t>(rv)));
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ return NS_OK;
+ }
+
+#if defined(XP_UNIX)
+ MOZ_ASSERT(!mNetAddrIsSet || mNetAddr.raw.family != AF_LOCAL,
+ "Unix domain sockets can't be used with socket types");
+#endif
+
+ fd = nullptr;
+
+ uint32_t controlFlags = 0;
+ if (mProxyTransparentResolvesHost) {
+ controlFlags |= nsISocketProvider::PROXY_RESOLVES_HOST;
+ }
+
+ if (mConnectionFlags & nsISocketTransport::ANONYMOUS_CONNECT) {
+ controlFlags |= nsISocketProvider::ANONYMOUS_CONNECT;
+ }
+
+ if (mConnectionFlags & nsISocketTransport::NO_PERMANENT_STORAGE) {
+ controlFlags |= nsISocketProvider::NO_PERMANENT_STORAGE;
+ }
+
+ if (mConnectionFlags & nsISocketTransport::BE_CONSERVATIVE) {
+ controlFlags |= nsISocketProvider::BE_CONSERVATIVE;
+ }
+
+ if (mConnectionFlags & nsISocketTransport::DONT_TRY_ECH) {
+ controlFlags |= nsISocketProvider::DONT_TRY_ECH;
+ }
+
+ if (mConnectionFlags & nsISocketTransport::IS_RETRY) {
+ controlFlags |= nsISocketProvider::IS_RETRY;
+ }
+
+ if (mConnectionFlags &
+ nsISocketTransport::ANONYMOUS_CONNECT_ALLOW_CLIENT_CERT) {
+ controlFlags |= nsISocketProvider::ANONYMOUS_CONNECT_ALLOW_CLIENT_CERT;
+ }
+
+ if (mConnectionFlags & nsISocketTransport::IS_SPECULATIVE_CONNECTION) {
+ controlFlags |= nsISocketProvider::IS_SPECULATIVE_CONNECTION;
+ }
+
+ if (mResolvedByTRR) {
+ controlFlags |= nsISocketProvider::USED_PRIVATE_DNS;
+ }
+
+ // by setting host to mOriginHost, instead of mHost we send the
+ // SocketProvider (e.g. PSM) the origin hostname but can still do DNS
+ // on an explicit alternate service host name
+ const char* host = mOriginHost.get();
+ int32_t port = (int32_t)mOriginPort;
+
+ nsCOMPtr<nsISocketProviderService> spserv =
+ nsSocketProviderService::GetOrCreate();
+ nsCOMPtr<nsIProxyInfo> proxyInfo = mProxyInfo;
+
+ uint32_t i;
+ for (i = 0; i < mTypes.Length(); ++i) {
+ nsCOMPtr<nsISocketProvider> provider;
+
+ SOCKET_LOG((" pushing io layer [%u:%s]\n", i, mTypes[i].get()));
+
+ rv = spserv->GetSocketProvider(mTypes[i].get(), getter_AddRefs(provider));
+ if (NS_FAILED(rv)) break;
+
+ nsCOMPtr<nsITLSSocketControl> tlsSocketControl;
+ if (i == 0) {
+ // if this is the first type, we'll want the
+ // service to allocate a new socket
+
+ // Most layers _ESPECIALLY_ PSM want the origin name here as they
+ // will use it for secure checks, etc.. and any connection management
+ // differences between the origin name and the routed name can be
+ // taken care of via DNS. However, SOCKS is a special case as there is
+ // no DNS. in the case of SOCKS and PSM the PSM is a separate layer
+ // and receives the origin name.
+ const char* socketProviderHost = host;
+ int32_t socketProviderPort = port;
+ if (mProxyTransparentResolvesHost &&
+ (mTypes[0].EqualsLiteral("socks") ||
+ mTypes[0].EqualsLiteral("socks4"))) {
+ SOCKET_LOG(("SOCKS %d Host/Route override: %s:%d -> %s:%d\n",
+ mHttpsProxy, socketProviderHost, socketProviderPort,
+ mHost.get(), mPort));
+ socketProviderHost = mHost.get();
+ socketProviderPort = mPort;
+ }
+
+ // when https proxying we want to just connect to the proxy as if
+ // it were the end host (i.e. expect the proxy's cert)
+
+ rv = provider->NewSocket(
+ mNetAddr.raw.family,
+ mHttpsProxy ? mProxyHost.get() : socketProviderHost,
+ mHttpsProxy ? mProxyPort : socketProviderPort, proxyInfo,
+ mOriginAttributes, controlFlags, mTlsFlags, &fd,
+ getter_AddRefs(tlsSocketControl));
+
+ if (NS_SUCCEEDED(rv) && !fd) {
+ MOZ_ASSERT_UNREACHABLE(
+ "NewSocket succeeded but failed to "
+ "create a PRFileDesc");
+ rv = NS_ERROR_UNEXPECTED;
+ }
+ } else {
+ // the socket has already been allocated,
+ // so we just want the service to add itself
+ // to the stack (such as pushing an io layer)
+ rv = provider->AddToSocket(mNetAddr.raw.family, host, port, proxyInfo,
+ mOriginAttributes, controlFlags, mTlsFlags, fd,
+ getter_AddRefs(tlsSocketControl));
+ }
+
+ // controlFlags = 0; not used below this point...
+ if (NS_FAILED(rv)) break;
+
+ // if the service was ssl or starttls, we want to hold onto the socket
+ // info
+ bool isSSL = mTypes[i].EqualsLiteral("ssl");
+ if (isSSL || mTypes[i].EqualsLiteral("starttls")) {
+ // remember security info
+ {
+ MutexAutoLock lock(mLock);
+ mTLSSocketControl = tlsSocketControl;
+ SOCKET_LOG((" [tlsSocketControl=%p callbacks=%p]\n",
+ mTLSSocketControl.get(), mCallbacks.get()));
+ }
+ // remember if socket type is SSL so we can ProxyStartSSL if need be.
+ usingSSL = isSSL;
+ } else if (mTypes[i].EqualsLiteral("socks") ||
+ mTypes[i].EqualsLiteral("socks4")) {
+ // since socks is transparent, any layers above
+ // it do not have to worry about proxy stuff
+ proxyInfo = nullptr;
+ proxyTransparent = true;
+ }
+ }
+
+ if (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));
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ nsresult rv;
+ bool isLocal;
+ IsLocal(&isLocal);
+
+ if (gIOService->IsNetTearingDown()) {
+ return NS_ERROR_ABORT;
+ }
+ if (gIOService->IsOffline()) {
+ if (StaticPrefs::network_disable_localhost_when_offline() || !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());
+ return NS_ERROR_NON_LOCAL_CONNECTION_REFUSED;
+ }
+ }
+
+ // 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.
+ //
+ {
+ MutexAutoLock lock(mLock);
+ 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) {
+ mTLSSocketControl = new FuzzySocketControl();
+ }
+ }
+#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
+
+ // up to here, mFD will only be accessed by us
+
+ // assign mFD so that we can properly handle OnSocketDetached before we've
+ // established a connection.
+ {
+ MutexAutoLock lock(mLock);
+ // 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;
+
+ 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
+
+ if (mTLSSocketControl) {
+ if (!mEchConfig.IsEmpty() &&
+ !(mConnectionFlags & (DONT_TRY_ECH | BE_CONSERVATIVE))) {
+ SOCKET_LOG(("nsSocketTransport::InitiateSocket set echconfig."));
+ rv = mTLSSocketControl->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 (mTLSSocketControl && !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.
+ SOCKET_LOG((" calling ProxyStartSSL()\n"));
+ mTLSSocketControl->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;
+
+#ifdef DEBUG
+ {
+ MutexAutoLock lock(mLock);
+ NS_ASSERTION(!mFDconnected, "socket should not be connected");
+ }
+#endif
+
+ // all connection failures need to be reported to DNS so that the next
+ // time we will use a different address if available.
+ // NS_BASE_STREAM_CLOSED is not an actual connection failure, so don't report
+ // to DNS.
+ if (mState == STATE_CONNECTING && mDNSRecord &&
+ mCondition != NS_BASE_STREAM_CLOSED) {
+ 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);
+ mDNSRecord->GetEffectiveTRRMode(&mEffectiveTRRMode);
+ mDNSRecord->GetTrrSkipReason(&mTRRSkipReason);
+ 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) {
+ nsIRequest::TRRMode trrMode = nsIRequest::TRR_DEFAULT_MODE;
+ mDNSRecord->GetEffectiveTRRMode(&trrMode);
+ // If current trr mode is trr only, we should not retry.
+ if (trrMode != nsIRequest::TRR_ONLY_MODE) {
+ // 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,
+ std::function<void()>&& task) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ 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"));
+ if (task) {
+ task();
+ }
+
+ // 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);
+ mDNSRecord->GetEffectiveTRRMode(&mEffectiveTRRMode);
+ mDNSRecord->GetTrrSkipReason(&mTRRSkipReason);
+ }
+ // 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) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ 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);
+ if (gIOService->IsNetTearingDown()) {
+ if (mInputCopyContext) {
+ NS_CancelAsyncCopy(mInputCopyContext, mCondition);
+ }
+ if (mOutputCopyContext) {
+ NS_CancelAsyncCopy(mOutputCopyContext, mCondition);
+ }
+ }
+ }
+
+ if (mCondition == NS_ERROR_NET_RESET && mDNSRecord &&
+ mOutput.ByteCount() == 0) {
+ // If we are here, it's likely that we are retrying a transaction. Blocking
+ // the already used address could increase the successful rate of the retry.
+ mDNSRecord->ReportUnusable(SocketPort());
+ }
+
+ // 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** aResult) {
+ SOCKET_LOG(
+ ("nsSocketTransport::OpenInputStream [this=%p flags=%x]\n", this, flags));
+
+ NS_ENSURE_TRUE(!mInput.IsReferenced(), NS_ERROR_UNEXPECTED);
+
+ nsresult rv;
+ nsCOMPtr<nsIAsyncInputStream> pipeIn;
+ nsCOMPtr<nsIInputStream> result;
+ nsCOMPtr<nsISupports> inputCopyContext;
+
+ 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;
+ NS_NewPipe2(getter_AddRefs(pipeIn), getter_AddRefs(pipeOut), !openBlocking,
+ true, segsize, segcount);
+
+ // async copy from socket to pipe
+ rv = NS_AsyncCopy(&mInput, pipeOut, mSocketTransportService,
+ NS_ASYNCCOPY_VIA_WRITESEGMENTS, segsize, nullptr, nullptr,
+ true, true, getter_AddRefs(inputCopyContext));
+ if (NS_FAILED(rv)) return rv;
+
+ result = pipeIn;
+ } else {
+ result = &mInput;
+ }
+
+ // flag input stream as open
+ mInputClosed = false;
+ // mInputCopyContext can be only touched on socket thread
+ auto task = [self = RefPtr{this}, inputCopyContext(inputCopyContext)]() {
+ MOZ_ASSERT(OnSocketThread());
+ self->mInputCopyContext = inputCopyContext;
+ };
+ rv = PostEvent(MSG_ENSURE_CONNECT, NS_OK, nullptr, std::move(task));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ result.forget(aResult);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransport::OpenOutputStream(uint32_t flags, uint32_t segsize,
+ uint32_t segcount,
+ nsIOutputStream** aResult) {
+ SOCKET_LOG(("nsSocketTransport::OpenOutputStream [this=%p flags=%x]\n", this,
+ flags));
+
+ NS_ENSURE_TRUE(!mOutput.IsReferenced(), NS_ERROR_UNEXPECTED);
+
+ nsresult rv;
+ nsCOMPtr<nsIAsyncOutputStream> pipeOut;
+ nsCOMPtr<nsIOutputStream> result;
+ nsCOMPtr<nsISupports> outputCopyContext;
+ 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;
+ NS_NewPipe2(getter_AddRefs(pipeIn), getter_AddRefs(pipeOut), true,
+ !openBlocking, segsize, segcount);
+
+ // async copy from socket to pipe
+ rv = NS_AsyncCopy(pipeIn, &mOutput, mSocketTransportService,
+ NS_ASYNCCOPY_VIA_READSEGMENTS, segsize, nullptr, nullptr,
+ true, true, getter_AddRefs(outputCopyContext));
+ if (NS_FAILED(rv)) return rv;
+
+ result = pipeOut;
+ } else {
+ result = &mOutput;
+ }
+
+ // flag output stream as open
+ mOutputClosed = false;
+
+ // mOutputCopyContext can be only touched on socket thread
+ auto task = [self = RefPtr{this}, outputCopyContext(outputCopyContext)]() {
+ MOZ_ASSERT(OnSocketThread());
+ self->mOutputCopyContext = outputCopyContext;
+ };
+ rv = PostEvent(MSG_ENSURE_CONNECT, NS_OK, nullptr, std::move(task));
+ if (NS_FAILED(rv)) return rv;
+
+ result.forget(aResult);
+ 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::GetTlsSocketControl(nsITLSSocketControl** tlsSocketControl) {
+ MutexAutoLock lock(mLock);
+ *tlsSocketControl = do_AddRef(mTLSSocketControl).take();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransport::GetSecurityCallbacks(nsIInterfaceRequestor** callbacks) {
+ MutexAutoLock lock(mLock);
+ *callbacks = do_AddRef(mCallbacks).take();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransport::SetSecurityCallbacks(nsIInterfaceRequestor* callbacks) {
+ nsCOMPtr<nsIInterfaceRequestor> threadsafeCallbacks;
+ NS_NewNotificationCallbacksAggregation(callbacks, nullptr,
+ GetCurrentSerialEventTarget(),
+ getter_AddRefs(threadsafeCallbacks));
+ MutexAutoLock lock(mLock);
+ mCallbacks = threadsafeCallbacks;
+ SOCKET_LOG(("Reset callbacks for tlsSocketInfo=%p callbacks=%p\n",
+ mTLSSocketControl.get(), mCallbacks.get()));
+ 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);
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ 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);
+ }
+
+ if (nsCOMPtr<nsIDNSAddrRecord> addrRecord = do_QueryInterface(rec)) {
+ addrRecord->IsTRR(&mResolvedByTRR);
+ addrRecord->GetEffectiveTRRMode(&mEffectiveTRRMode);
+ addrRecord->GetTrrSkipReason(&mTRRSkipReason);
+ }
+
+ // 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();
+ char* errMessage;
+ FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM |
+ FORMAT_MESSAGE_IGNORE_INSERTS,
+ NULL, errCode, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
+ (LPSTR)&errMessage, 0, NULL);
+ NS_WARNING(nsPrintfCString("%s [%p] OS error[0x%lx] %s",
+ aPrefix ? aPrefix : "nsSocketTransport", aObjPtr,
+ errCode,
+ errMessage ? errMessage : "<no error text>")
+ .get());
+ LocalFree(errMessage);
+# else
+ int errCode = errno;
+ char* errMessage = strerror(errno);
+ NS_WARNING(nsPrintfCString("%s [%p] OS error[0x%x] %s",
+ aPrefix ? aPrefix : "nsSocketTransport", aObjPtr,
+ errCode,
+ errMessage ? errMessage : "<no error text>")
+ .get());
+# 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::GetEffectiveTRRMode(
+ nsIRequest::TRRMode* aEffectiveTRRMode) {
+ *aEffectiveTRRMode = mEffectiveTRRMode;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsSocketTransport::GetTrrSkipReason(
+ nsITRRSkipReason::value* aSkipReason) {
+ *aSkipReason = mTRRSkipReason;
+ 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