Quellcodebibliothek Statistik Leitseite products/sources/formale Sprachen/C/Firefox/netwerk/base/   (Browser von der Mozilla Stiftung Version 136.0.1©)  Datei vom 10.2.2025 mit Größe 107 kB image not shown  

Quelle  nsSocketTransport2.cpp   Sprache: C

 
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:set ts=4 sw=2 et cindent: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */


#include <algorithm>

#include "nsSocketTransport2.h"

#include "MockNetworkLayer.h"
#include "MockNetworkLayerController.h"
#include "NSSErrorsService.h"
#include "NetworkDataCountLayer.h"
#include "QuicSocketControl.h"
#include "mozilla/Attributes.h"
#include "mozilla/glean/NetwerkMetrics.h"
#include "mozilla/StaticPrefs_network.h"
#include "mozilla/SyncRunnable.h"
#include "mozilla/Telemetry.h"
#include "mozilla/dom/ToJSValue.h"
#include "mozilla/net/NeckoChild.h"
#include "mozilla/net/SSLTokensCache.h"
#include "mozilla/ProfilerBandwidthCounter.h"
#include "nsCOMPtr.h"
#include "nsICancelable.h"
#include "nsIClassInfoImpl.h"
#include "nsIDNSByTypeRecord.h"
#include "nsIDNSRecord.h"
#include "nsIDNSService.h"
#include "nsIOService.h"
#include "nsIPipe.h"
#include "nsISocketProvider.h"
#include "nsITLSSocketControl.h"
#include "nsNetAddr.h"
#include "nsNetCID.h"
#include "nsNetSegmentUtils.h"
#include "nsNetUtil.h"
#include "nsPrintfCString.h"
#include "nsProxyInfo.h"
#include "nsSocketProviderService.h"
#include "nsStreamUtils.h"
#include "nsThreadUtils.h"
#include "nsTransportUtils.h"
#include "nsURLHelper.h"
#include "prerr.h"
#include "sslexp.h"
#include "xpcpublic.h"

#if defined(FUZZING)
#  include "FuzzyLayer.h"
#  include "FuzzySocketControl.h"
#  include "mozilla/StaticPrefs_fuzzing.h"
#endif

#if defined(XP_WIN)
#  include "ShutdownLayer.h"
#endif

/* Following inclusions required for keepalive config not supported by NSPR. */
#include "private/pprio.h"
#if defined(XP_WIN)
#  include <winsock2.h>
#  include <mstcpip.h>
#elif defined(XP_UNIX)
#  include <errno.h>
#  include <netinet/tcp.h>
#endif
/* End keepalive config inclusions. */

#define SUCCESSFUL_CONNECTING_TO_IPV4_ADDRESS 0
#define UNSUCCESSFUL_CONNECTING_TO_IPV4_ADDRESS 1
#define SUCCESSFUL_CONNECTING_TO_IPV6_ADDRESS 2
#define UNSUCCESSFUL_CONNECTING_TO_IPV6_ADDRESS 3

//-----------------------------------------------------------------------------

static NS_DEFINE_CID(kDNSServiceCID, NS_DNSSERVICE_CID);

//-----------------------------------------------------------------------------

namespace mozilla {
namespace net {

class nsSocketEvent : public Runnable {
 public:
  nsSocketEvent(nsSocketTransport* transport, uint32_t type,
                nsresult status = NS_OK, nsISupports* param = nullptr,
                std::function<void()>&& task = nullptr)
      : Runnable("net::nsSocketEvent"),
        mTransport(transport),
        mType(type),
        mStatus(status),
        mParam(param),
        mTask(std::move(task)) {}

  NS_IMETHOD Run() override {
    mTransport->OnSocketEvent(mType, mStatus, mParam, std::move(mTask));
    return NS_OK;
  }

 private:
  RefPtr<nsSocketTransport> mTransport;

  uint32_t mType;
  nsresult mStatus;
  nsCOMPtr<nsISupports> mParam;
  std::function<void()> mTask;
};

//-----------------------------------------------------------------------------

// #define TEST_CONNECT_ERRORS
#ifdef TEST_CONNECT_ERRORS
#  include <stdlib.h>
static PRErrorCode RandomizeConnectError(PRErrorCode code) {
  //
  // To test out these errors, load http://www.yahoo.com/.  It should load
  // correctly despite the random occurrence of these errors.
  //
  int n = rand();
  if (n > RAND_MAX / 2) {
    struct {
      PRErrorCode err_code;
      const char* err_name;
    } errors[] = {
        //
        // These errors should be recoverable provided there is another
        // IP address in mDNSRecord.
        //
        {PR_CONNECT_REFUSED_ERROR, "PR_CONNECT_REFUSED_ERROR"},
        {PR_CONNECT_TIMEOUT_ERROR, "PR_CONNECT_TIMEOUT_ERROR"},
        //
        // This error will cause this socket transport to error out;
        // however, if the consumer is HTTP, then the HTTP transaction
        // should be restarted when this error occurs.
        //
        {PR_CONNECT_RESET_ERROR, "PR_CONNECT_RESET_ERROR"},
    };
    n = n % (sizeof(errors) / sizeof(errors[0]));
    code = errors[n].err_code;
    SOCKET_LOG(("simulating NSPR error %d [%s]\n", code, errors[n].err_name));
  }
  return code;
}
#endif

//-----------------------------------------------------------------------------

nsresult ErrorAccordingToNSPR(PRErrorCode errorCode) {
  nsresult rv = NS_ERROR_FAILURE;
  switch (errorCode) {
    case PR_WOULD_BLOCK_ERROR:
      rv = NS_BASE_STREAM_WOULD_BLOCK;
      break;
    case PR_CONNECT_ABORTED_ERROR:
    case PR_CONNECT_RESET_ERROR:
      rv = NS_ERROR_NET_RESET;
      break;
    case PR_END_OF_FILE_ERROR:  // XXX document this correlation
      rv = NS_ERROR_NET_INTERRUPT;
      break;
    case PR_CONNECT_REFUSED_ERROR:
    // We lump the following NSPR codes in with PR_CONNECT_REFUSED_ERROR. We
    // could get better diagnostics by adding distinct XPCOM error codes for
    // each of these, but there are a lot of places in Gecko that check
    // specifically for NS_ERROR_CONNECTION_REFUSED, all of which would need to
    // be checked.
    case PR_NETWORK_UNREACHABLE_ERROR:
    case PR_HOST_UNREACHABLE_ERROR:
    case PR_ADDRESS_NOT_AVAILABLE_ERROR:
    // Treat EACCES as a soft error since (at least on Linux) connect() returns
    // EACCES when an IPv6 connection is blocked by a firewall. See bug 270784.
    case PR_NO_ACCESS_RIGHTS_ERROR:
      rv = NS_ERROR_CONNECTION_REFUSED;
      break;
    case PR_ADDRESS_NOT_SUPPORTED_ERROR:
      rv = NS_ERROR_SOCKET_ADDRESS_NOT_SUPPORTED;
      break;
    case PR_IO_TIMEOUT_ERROR:
    case PR_CONNECT_TIMEOUT_ERROR:
      rv = NS_ERROR_NET_TIMEOUT;
      break;
    case PR_OUT_OF_MEMORY_ERROR:
    // These really indicate that the descriptor table filled up, or that the
    // kernel ran out of network buffers - but nobody really cares which part of
    // the system ran out of memory.
    case PR_PROC_DESC_TABLE_FULL_ERROR:
    case PR_SYS_DESC_TABLE_FULL_ERROR:
    case PR_INSUFFICIENT_RESOURCES_ERROR:
      rv = NS_ERROR_OUT_OF_MEMORY;
      break;
    case PR_ADDRESS_IN_USE_ERROR:
      rv = NS_ERROR_SOCKET_ADDRESS_IN_USE;
      break;
    // These filename-related errors can arise when using Unix-domain sockets.
    case PR_FILE_NOT_FOUND_ERROR:
      rv = NS_ERROR_FILE_NOT_FOUND;
      break;
    case PR_IS_DIRECTORY_ERROR:
      rv = NS_ERROR_FILE_IS_DIRECTORY;
      break;
    case PR_LOOP_ERROR:
      rv = NS_ERROR_FILE_UNRESOLVABLE_SYMLINK;
      break;
    case PR_NAME_TOO_LONG_ERROR:
      rv = NS_ERROR_FILE_NAME_TOO_LONG;
      break;
    case PR_NO_DEVICE_SPACE_ERROR:
      rv = NS_ERROR_FILE_NO_DEVICE_SPACE;
      break;
    case PR_NOT_DIRECTORY_ERROR:
      rv = NS_ERROR_FILE_NOT_DIRECTORY;
      break;
    case PR_READ_ONLY_FILESYSTEM_ERROR:
      rv = NS_ERROR_FILE_READ_ONLY;
      break;
    case PR_BAD_ADDRESS_ERROR:
      rv = NS_ERROR_UNKNOWN_HOST;
      break;
    default:
      if (psm::IsNSSErrorCode(errorCode)) {
        rv = psm::GetXPCOMFromNSSError(errorCode);
      }
      break;

      // NSPR's socket code can return these, but they're not worth breaking out
      // into their own error codes, distinct from NS_ERROR_FAILURE:
      //
      // PR_BAD_DESCRIPTOR_ERROR
      // PR_INVALID_ARGUMENT_ERROR
      // PR_NOT_SOCKET_ERROR
      // PR_NOT_TCP_SOCKET_ERROR
      //   These would indicate a bug internal to the component.
      //
      // PR_PROTOCOL_NOT_SUPPORTED_ERROR
      //   This means that we can't use the given "protocol" (like
      //   IPPROTO_TCP or IPPROTO_UDP) with a socket of the given type. As
      //   above, this indicates an internal bug.
      //
      // PR_IS_CONNECTED_ERROR
      //   This indicates that we've applied a system call like 'bind' or
      //   'connect' to a socket that is already connected. The socket
      //   components manage each file descriptor's state, and in some cases
      //   handle this error result internally. We shouldn't be returning
      //   this to our callers.
      //
      // PR_IO_ERROR
      //   This is so vague that NS_ERROR_FAILURE is just as good.
  }
  SOCKET_LOG(("ErrorAccordingToNSPR [in=%d out=%" PRIx32 "]\n", errorCode,
              static_cast<uint32_t>(rv)));
  return rv;
}

//-----------------------------------------------------------------------------
// socket input stream impl
//-----------------------------------------------------------------------------

nsSocketInputStream::nsSocketInputStream(nsSocketTransport* trans)
    : mTransport(trans) {}

// called on the socket transport thread...
//
//   condition : failure code if socket has been closed
//
void nsSocketInputStream::OnSocketReady(nsresult condition) {
  SOCKET_LOG(("nsSocketInputStream::OnSocketReady [this=%p cond=%" PRIx32 "]\n",
              thisstatic_cast<uint32_t>(condition)));

  MOZ_ASSERT(OnSocketThread(), "not on socket thread");

  nsCOMPtr<nsIInputStreamCallback> callback;
  {
    MutexAutoLock lock(mTransport->mLock);

    // update condition, but be careful not to erase an already
    // existing error condition.
    if (NS_SUCCEEDED(mCondition)) mCondition = condition;

    // ignore event if only waiting for closure and not closed.
    if (NS_FAILED(mCondition) || !(mCallbackFlags & WAIT_CLOSURE_ONLY)) {
      callback = std::move(mCallback);
      mCallbackFlags = 0;
    }
  }

  if (callback) callback->OnInputStreamReady(this);
}

NS_IMPL_QUERY_INTERFACE(nsSocketInputStream, nsIInputStream,
                        nsIAsyncInputStream)

NS_IMETHODIMP_(MozExternalRefCountType)
nsSocketInputStream::AddRef() {
  ++mReaderRefCnt;
  return mTransport->AddRef();
}

NS_IMETHODIMP_(MozExternalRefCountType)
nsSocketInputStream::Release() {
  if (--mReaderRefCnt == 0) Close();
  return mTransport->Release();
}

NS_IMETHODIMP
nsSocketInputStream::Close() { return CloseWithStatus(NS_BASE_STREAM_CLOSED); }

NS_IMETHODIMP
nsSocketInputStream::Available(uint64_t* avail) {
  SOCKET_LOG(("nsSocketInputStream::Available [this=%p]\n"this));

  *avail = 0;

  PRFileDesc* fd;
  {
    MutexAutoLock lock(mTransport->mLock);

    if (NS_FAILED(mCondition)) return mCondition;

    fd = mTransport->GetFD_Locked();
    if (!fd) return NS_OK;
  }

  // cannot hold lock while calling NSPR.  (worried about the fact that PSM
  // synchronously proxies notifications over to the UI thread, which could
  // mistakenly try to re-enter this code.)
  int32_t n = PR_Available(fd);

  // PSM does not implement PR_Available() so do a best approximation of it
  // with MSG_PEEK
  if ((n == -1) && (PR_GetError() == PR_NOT_IMPLEMENTED_ERROR)) {
    char c;

    n = PR_Recv(fd, &c, 1, PR_MSG_PEEK, 0);
    SOCKET_LOG(
        ("nsSocketInputStream::Available [this=%p] "
         "using PEEK backup n=%d]\n",
         this, n));
  }

  nsresult rv;
  {
    MutexAutoLock lock(mTransport->mLock);

    mTransport->ReleaseFD_Locked(fd);

    if (n >= 0) {
      *avail = n;
    } else {
      PRErrorCode code = PR_GetError();
      if (code == PR_WOULD_BLOCK_ERROR) return NS_OK;
      mCondition = ErrorAccordingToNSPR(code);
    }
    rv = mCondition;
  }
  if (NS_FAILED(rv)) mTransport->OnInputClosed(rv);
  return rv;
}

NS_IMETHODIMP
nsSocketInputStream::StreamStatus() {
  SOCKET_LOG(("nsSocketInputStream::StreamStatus [this=%p]\n"this));

  MutexAutoLock lock(mTransport->mLock);
  return mCondition;
}

NS_IMETHODIMP
nsSocketInputStream::Read(char* buf, uint32_t count, uint32_t* countRead) {
  SOCKET_LOG(("nsSocketInputStream::Read [this=%p count=%u]\n"this, count));

  *countRead = 0;

  PRFileDesc* fd = nullptr;
  {
    MutexAutoLock lock(mTransport->mLock);

    if (NS_FAILED(mCondition)) {
      return (mCondition == NS_BASE_STREAM_CLOSED) ? NS_OK : mCondition;
    }

    fd = mTransport->GetFD_Locked();
    if (!fd) return NS_BASE_STREAM_WOULD_BLOCK;
  }

  SOCKET_LOG((" calling PR_Read [count=%u]\n", count));

  // cannot hold lock while calling NSPR.  (worried about the fact that PSM
  // synchronously proxies notifications over to the UI thread, which could
  // mistakenly try to re-enter this code.)
  int32_t n = PR_Read(fd, buf, count);

  SOCKET_LOG((" PR_Read returned [n=%d]\n", n));

  nsresult rv = NS_OK;
  {
    MutexAutoLock lock(mTransport->mLock);

#ifdef ENABLE_SOCKET_TRACING
    if (n > 0) mTransport->TraceInBuf(buf, n);
#endif

    mTransport->ReleaseFD_Locked(fd);

    if (n > 0) {
      mByteCount += (*countRead = n);
      profiler_count_bandwidth_read_bytes(n);
    } else if (n < 0) {
      PRErrorCode code = PR_GetError();
      if (code == PR_WOULD_BLOCK_ERROR) return NS_BASE_STREAM_WOULD_BLOCK;
      mCondition = ErrorAccordingToNSPR(code);
    }
    rv = mCondition;
  }
  if (NS_FAILED(rv)) mTransport->OnInputClosed(rv);

  // only send this notification if we have indeed read some data.
  // see bug 196827 for an example of why this is important.
  if (n > 0) mTransport->SendStatus(NS_NET_STATUS_RECEIVING_FROM);
  return rv;
}

NS_IMETHODIMP
nsSocketInputStream::ReadSegments(nsWriteSegmentFun writer, void* closure,
                                  uint32_t count, uint32_t* countRead) {
  // socket stream is unbuffered
  return NS_ERROR_NOT_IMPLEMENTED;
}

NS_IMETHODIMP
nsSocketInputStream::IsNonBlocking(bool* nonblocking) {
  *nonblocking = true;
  return NS_OK;
}

NS_IMETHODIMP
nsSocketInputStream::CloseWithStatus(nsresult reason) {
  SOCKET_LOG(("nsSocketInputStream::CloseWithStatus [this=%p reason=%" PRIx32
              "]\n",
              thisstatic_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",
              thisstatic_cast<uint32_t>(condition)));

  MOZ_ASSERT(OnSocketThread(), "not on socket thread");

  nsCOMPtr<nsIOutputStreamCallback> callback;
  {
    MutexAutoLock lock(mTransport->mLock);

    // update condition, but be careful not to erase an already
    // existing error condition.
    if (NS_SUCCEEDED(mCondition)) mCondition = condition;

    // ignore event if only waiting for closure and not closed.
    if (NS_FAILED(mCondition) || !(mCallbackFlags & WAIT_CLOSURE_ONLY)) {
      callback = std::move(mCallback);
      mCallbackFlags = 0;
    }
  }

  if (callback) callback->OnOutputStreamReady(this);
}

NS_IMPL_QUERY_INTERFACE(nsSocketOutputStream, nsIOutputStream,
                        nsIAsyncOutputStream)

NS_IMETHODIMP_(MozExternalRefCountType)
nsSocketOutputStream::AddRef() {
  ++mWriterRefCnt;
  return mTransport->AddRef();
}

NS_IMETHODIMP_(MozExternalRefCountType)
nsSocketOutputStream::Release() {
  if (--mWriterRefCnt == 0) Close();
  return mTransport->Release();
}

NS_IMETHODIMP
nsSocketOutputStream::Close() { return CloseWithStatus(NS_BASE_STREAM_CLOSED); }

NS_IMETHODIMP
nsSocketOutputStream::Flush() { return NS_OK; }

NS_IMETHODIMP
nsSocketOutputStream::StreamStatus() {
  MutexAutoLock lock(mTransport->mLock);
  return mCondition;
}

NS_IMETHODIMP
nsSocketOutputStream::Write(const char* buf, uint32_t count,
                            uint32_t* countWritten) {
  SOCKET_LOG(("nsSocketOutputStream::Write [this=%p count=%u]\n"this, count));

  *countWritten = 0;

  // A write of 0 bytes can be used to force the initial SSL handshake, so do
  // not reject that.

  PRFileDesc* fd = nullptr;
  {
    MutexAutoLock lock(mTransport->mLock);

    if (NS_FAILED(mCondition)) return mCondition;

    fd = mTransport->GetFD_Locked();
    if (!fd) return NS_BASE_STREAM_WOULD_BLOCK;
  }

  SOCKET_LOG((" calling PR_Write [count=%u]\n", count));

  // cannot hold lock while calling NSPR.  (worried about the fact that PSM
  // synchronously proxies notifications over to the UI thread, which could
  // mistakenly try to re-enter this code.)
  int32_t n = PR_Write(fd, buf, count);

  SOCKET_LOG((" PR_Write returned [n=%d]\n", n));

  nsresult rv = NS_OK;
  {
    MutexAutoLock lock(mTransport->mLock);

#ifdef ENABLE_SOCKET_TRACING
    if (n > 0) mTransport->TraceOutBuf(buf, n);
#endif

    mTransport->ReleaseFD_Locked(fd);

    if (n > 0) {
      mByteCount += (*countWritten = n);
      profiler_count_bandwidth_written_bytes(n);
    } else if (n < 0) {
      PRErrorCode code = PR_GetError();
      if (code == PR_WOULD_BLOCK_ERROR) return NS_BASE_STREAM_WOULD_BLOCK;
      mCondition = ErrorAccordingToNSPR(code);
    }
    rv = mCondition;
  }
  if (NS_FAILED(rv)) mTransport->OnOutputClosed(rv);

  // only send this notification if we have indeed written some data.
  // see bug 196827 for an example of why this is important.
  if ((n > 0)) {
    mTransport->SendStatus(NS_NET_STATUS_SENDING_TO);
  }

  return rv;
}

NS_IMETHODIMP
nsSocketOutputStream::WriteSegments(nsReadSegmentFun reader, void* closure,
                                    uint32_t count, uint32_t* countRead) {
  // socket stream is unbuffered
  return NS_ERROR_NOT_IMPLEMENTED;
}

nsresult nsSocketOutputStream::WriteFromSegments(
    nsIInputStream* input, void* closure, const char* fromSegment,
    uint32_t offset, uint32_t count, uint32_t* countRead) {
  nsSocketOutputStream* self = (nsSocketOutputStream*)closure;
  return self->Write(fromSegment, count, countRead);
}

NS_IMETHODIMP
nsSocketOutputStream::WriteFrom(nsIInputStream* stream, uint32_t count,
                                uint32_t* countRead) {
  return stream->ReadSegments(WriteFromSegments, this, count, countRead);
}

NS_IMETHODIMP
nsSocketOutputStream::IsNonBlocking(bool* nonblocking) {
  *nonblocking = true;
  return NS_OK;
}

NS_IMETHODIMP
nsSocketOutputStream::CloseWithStatus(nsresult reason) {
  SOCKET_LOG(("nsSocketOutputStream::CloseWithStatus [this=%p reason=%" PRIx32
              "]\n",
              thisstatic_cast<uint32_t>(reason)));

  // may be called from any thread

  nsresult rv;
  {
    MutexAutoLock lock(mTransport->mLock);

    if (NS_SUCCEEDED(mCondition)) {
      rv = mCondition = reason;
    } else {
      rv = NS_OK;
    }
  }
  if (NS_FAILED(rv)) mTransport->OnOutputClosed(rv);
  return NS_OK;
}

NS_IMETHODIMP
nsSocketOutputStream::AsyncWait(nsIOutputStreamCallback* callback,
                                uint32_t flags, uint32_t amount,
                                nsIEventTarget* target) {
  SOCKET_LOG(("nsSocketOutputStream::AsyncWait [this=%p]\n"this));

  {
    MutexAutoLock lock(mTransport->mLock);

    if (callback && target) {
      //
      // build event proxy
      //
      mCallback = NS_NewOutputStreamReadyEvent(callback, target);
    } else {
      mCallback = callback;
    }

    mCallbackFlags = flags;
  }
  mTransport->OnOutputPending();
  return NS_OK;
}

//-----------------------------------------------------------------------------
// socket transport impl
//-----------------------------------------------------------------------------

nsSocketTransport::nsSocketTransport()
    : mFD(this),
      mSocketTransportService(gSocketTransportService),
      mInput(new nsSocketInputStream(this)),
      mOutput(new nsSocketOutputStream(this)) {
  SOCKET_LOG(("creating nsSocketTransport @%p\n"this));

  mTimeouts[TIMEOUT_CONNECT] = UINT16_MAX;     // no timeout
  mTimeouts[TIMEOUT_READ_WRITE] = UINT16_MAX;  // no timeout
}

nsSocketTransport::~nsSocketTransport() {
  MOZ_RELEASE_ASSERT(!mAttached);
  SOCKET_LOG(("destroying nsSocketTransport @%p\n"this));
}

nsresult nsSocketTransport::Init(const nsTArray<nsCString>& types,
                                 const nsACString& host, uint16_t port,
                                 const nsACString& hostRoute,
                                 uint16_t portRoute,
                                 nsIProxyInfo* givenProxyInfo,
                                 nsIDNSRecord* dnsRecord) {
  nsCOMPtr<nsProxyInfo> proxyInfo;
  if (givenProxyInfo) {
    proxyInfo = do_QueryInterface(givenProxyInfo);
    NS_ENSURE_ARG(proxyInfo);
  }

  if (dnsRecord) {
    mExternalDNSResolution = true;
    mDNSRecord = do_QueryInterface(dnsRecord);
    mDNSRecord->IsTRR(&mResolvedByTRR);
    mDNSRecord->GetEffectiveTRRMode(&mEffectiveTRRMode);
    mDNSRecord->GetTrrSkipReason(&mTRRSkipReason);
  }

  // init socket type info

  mOriginHost = host;
  mOriginPort = port;
  if (!hostRoute.IsEmpty()) {
    mHost = hostRoute;
    mPort = portRoute;
  } else {
    mHost = host;
    mPort = port;
  }

  // A subtle check we don't enter this method more than once for the socket
  // transport lifetime.  Disable on TSan builds to prevent race checking, we
  // don't want an atomic here for perf reasons!
#ifndef MOZ_TSAN
  MOZ_ASSERT(!mPortRemappingApplied);
#endif  // !MOZ_TSAN

  if (proxyInfo) {
    mHttpsProxy = proxyInfo->IsHTTPS();
  }

  const char* proxyType = nullptr;
  mProxyInfo = proxyInfo;
  if (proxyInfo) {
    mProxyPort = proxyInfo->Port();
    mProxyHost = proxyInfo->Host();
    // grab proxy type (looking for "socks" for example)
    proxyType = proxyInfo->Type();
    if (proxyType && (proxyInfo->IsHTTP() || proxyInfo->IsHTTPS() ||
                      proxyInfo->IsDirect() || !strcmp(proxyType, "unknown"))) {
      proxyType = nullptr;
    }
  }

  SOCKET_LOG1(
      ("nsSocketTransport::Init [this=%p host=%s:%hu origin=%s:%d "
       "proxy=%s:%hu]\n",
       this, mHost.get(), mPort, mOriginHost.get(), mOriginPort,
       mProxyHost.get(), mProxyPort));

  // include proxy type as a socket type if proxy type is not "http"
  uint32_t typeCount = types.Length() + (proxyType != nullptr);
  if (!typeCount) return NS_OK;

  // if we have socket types, then the socket provider service had
  // better exist!
  nsresult rv;
  nsCOMPtr<nsISocketProviderService> spserv =
      nsSocketProviderService::GetOrCreate();

  if (!mTypes.SetCapacity(typeCount, fallible)) {
    return NS_ERROR_OUT_OF_MEMORY;
  }

  // now verify that each socket type has a registered socket provider.
  for (uint32_t i = 0, type = 0; i < typeCount; ++i) {
    // store socket types
    if (i == 0 && proxyType) {
      mTypes.AppendElement(proxyType);
    } else {
      mTypes.AppendElement(types[type++]);
    }

    nsCOMPtr<nsISocketProvider> provider;
    rv = spserv->GetSocketProvider(mTypes[i].get(), getter_AddRefs(provider));
    if (NS_FAILED(rv)) {
      NS_WARNING("no registered socket provider");
      return rv;
    }

    // note if socket type corresponds to a transparent proxy
    // XXX don't hardcode SOCKS here (use proxy info's flags instead).
    if (mTypes[i].EqualsLiteral("socks") || mTypes[i].EqualsLiteral("socks4")) {
      mProxyTransparent = true;

      if (proxyInfo->Flags() & nsIProxyInfo::TRANSPARENT_PROXY_RESOLVES_HOST) {
        // we want the SOCKS layer to send the hostname
        // and port to the proxy and let it do the DNS.
        mProxyTransparentResolvesHost = true;
      }
    }
  }

  return NS_OK;
}

#if defined(XP_UNIX)
nsresult nsSocketTransport::InitWithFilename(const char* filename) {
  return InitWithName(filename, strlen(filename));
}

nsresult nsSocketTransport::InitWithName(const char* name, size_t length) {
  if (length > sizeof(mNetAddr.local.path) - 1) {
    return NS_ERROR_FILE_NAME_TOO_LONG;
  }

  if (!name[0] && length > 1) {
    // name is abstract address name that is supported on Linux only
#  if defined(XP_LINUX)
    mHost.Assign(name + 1, length - 1);
#  else
    return NS_ERROR_SOCKET_ADDRESS_NOT_SUPPORTED;
#  endif
  } else {
    // The name isn't abstract socket address.  So this is Unix domain
    // socket that has file path.
    mHost.Assign(name, length);
  }
  mPort = 0;

  mNetAddr.local.family = AF_LOCAL;
  memcpy(mNetAddr.local.path, name, length);
  mNetAddr.local.path[length] = '\0';
  mNetAddrIsSet = true;

  return NS_OK;
}
#endif

nsresult nsSocketTransport::InitWithConnectedSocket(PRFileDesc* fd,
                                                    const NetAddr* addr) {
  MOZ_ASSERT(OnSocketThread(), "not on socket thread");

  char buf[kNetAddrMaxCStrBufSize];
  addr->ToStringBuffer(buf, sizeof(buf));
  mHost.Assign(buf);

  uint16_t port;
  if (addr->raw.family == AF_INET) {
    port = addr->inet.port;
  } else if (addr->raw.family == AF_INET6) {
    port = addr->inet6.port;
  } else {
    port = 0;
  }
  mPort = ntohs(port);

  mNetAddr = *addr;

  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",
               thisstatic_cast<uint32_t>(status)));

  nsCOMPtr<nsITransportEventSink> sink;
  uint64_t progress;
  {
    MutexAutoLock lock(mLock);
    sink = mEventSink;
    switch (status) {
      case NS_NET_STATUS_SENDING_TO:
        progress = mOutput->ByteCount(lock);
        break;
      case NS_NET_STATUS_RECEIVING_FROM:
        progress = mInput->ByteCount(lock);
        break;
      default:
        progress = 0;
        break;
    }
  }
  if (sink) {
    sink->OnTransportStatus(this, status, progress, -1);
  }
}

nsresult nsSocketTransport::ResolveHost() {
  SOCKET_LOG((
      "nsSocketTransport::ResolveHost [this=%p %s:%d%s] "
      "mProxyTransparentResolvesHost=%d\n",
      this, SocketHost().get(), SocketPort(),
      mConnectionFlags & nsSocketTransport::BYPASS_CACHE ? " bypass cache" : "",
      mProxyTransparentResolvesHost));
  MOZ_ASSERT(OnSocketThread(), "not on socket thread");

  nsresult rv;

  if (!mProxyHost.IsEmpty()) {
    if (!mProxyTransparent || mProxyTransparentResolvesHost) {
#if defined(XP_UNIX)
      MOZ_ASSERT(!mNetAddrIsSet || mNetAddr.raw.family != AF_LOCAL,
                 "Unix domain sockets can't be used with proxies");
#endif
      // When not resolving mHost locally, we still want to ensure that
      // it only contains valid characters.  See bug 304904 for details.
      // Sometimes the end host is not yet known and mHost is *
      if (!net_IsValidDNSHost(mHost) && !mHost.EqualsLiteral("*")) {
        SOCKET_LOG((" invalid hostname %s\n", mHost.get()));
        return NS_ERROR_UNKNOWN_HOST;
      }
    }
    if (mProxyTransparentResolvesHost) {
      // Name resolution is done on the server side.  Just pretend
      // client resolution is complete, this will get picked up later.
      // since we don't need to do DNS now, we bypass the resolving
      // step by initializing mNetAddr to an empty address, but we
      // must keep the port. The SOCKS IO layer will use the hostname
      // we send it when it's created, rather than the empty address
      // we send with the connect call.
      mState = STATE_RESOLVING;
      mNetAddr.raw.family = AF_INET;
      mNetAddr.inet.port = htons(SocketPort());
      mNetAddr.inet.ip = htonl(INADDR_ANY);
      return PostEvent(MSG_DNS_LOOKUP_COMPLETE, NS_OK, nullptr);
    }
  }

  if (mExternalDNSResolution) {
    MOZ_ASSERT(mDNSRecord);
    mState = STATE_RESOLVING;
    return PostEvent(MSG_DNS_LOOKUP_COMPLETE, NS_OK, nullptr);
  }

  nsCOMPtr<nsIDNSService> dns = nullptr;
  auto initTask = [&dns]() { dns = do_GetService(kDNSServiceCID); };
  if (!NS_IsMainThread()) {
    // Forward to the main thread synchronously.
    RefPtr<nsIThread> mainThread = do_GetMainThread();
    if (!mainThread) {
      return NS_ERROR_FAILURE;
    }

    SyncRunnable::DispatchToThread(
        mainThread,
        NS_NewRunnableFunction("nsSocketTransport::ResolveHost->GetDNSService",
                               initTask));
  } else {
    initTask();
  }
  if (!dns) {
    return NS_ERROR_FAILURE;
  }

  mResolving = true;

  nsIDNSService::DNSFlags dnsFlags = nsIDNSService::RESOLVE_DEFAULT_FLAGS;
  if (mConnectionFlags & nsSocketTransport::BYPASS_CACHE) {
    dnsFlags = nsIDNSService::RESOLVE_BYPASS_CACHE;
  }
  if (mConnectionFlags & nsSocketTransport::REFRESH_CACHE) {
    dnsFlags = nsIDNSService::RESOLVE_REFRESH_CACHE;
  }
  if (mConnectionFlags & nsSocketTransport::DISABLE_IPV6) {
    dnsFlags |= nsIDNSService::RESOLVE_DISABLE_IPV6;
  }
  if (mConnectionFlags & nsSocketTransport::DISABLE_IPV4) {
    dnsFlags |= nsIDNSService::RESOLVE_DISABLE_IPV4;
  }
  if (mConnectionFlags & nsSocketTransport::DISABLE_TRR) {
    dnsFlags |= nsIDNSService::RESOLVE_DISABLE_TRR;
  }

  if (mConnectionFlags & nsSocketTransport::USE_IP_HINT_ADDRESS) {
    dnsFlags |= nsIDNSService::RESOLVE_IP_HINT;
  }

  dnsFlags |= nsIDNSService::GetFlagsFromTRRMode(
      nsISocketTransport::GetTRRModeFromFlags(mConnectionFlags));

  // When we get here, we are not resolving using any configured proxy likely
  // because of individual proxy setting on the request or because the host is
  // excluded from proxying.  Hence, force resolution despite global proxy-DNS
  // configuration.
  dnsFlags |= nsIDNSService::RESOLVE_IGNORE_SOCKS_DNS;

  NS_ASSERTION(!(dnsFlags & nsIDNSService::RESOLVE_DISABLE_IPV6) ||
                   !(dnsFlags & nsIDNSService::RESOLVE_DISABLE_IPV4),
               "Setting both RESOLVE_DISABLE_IPV6 and RESOLVE_DISABLE_IPV4");

  SendStatus(NS_NET_STATUS_RESOLVING_HOST);

  if (!SocketHost().Equals(mOriginHost)) {
    SOCKET_LOG(("nsSocketTransport %p origin %s doing dns for %s\n"this,
                mOriginHost.get(), SocketHost().get()));
  }
  rv =
      dns->AsyncResolveNative(SocketHost(), nsIDNSService::RESOLVE_TYPE_DEFAULT,
                              dnsFlags, nullptr, this, mSocketTransportService,
                              mOriginAttributes, getter_AddRefs(mDNSRequest));

  if (NS_SUCCEEDED(rv)) {
    SOCKET_LOG((" advancing to STATE_RESOLVING\n"));
    mState = STATE_RESOLVING;
  }
  return rv;
}

nsresult nsSocketTransport::BuildSocket(PRFileDesc*& fd, bool& proxyTransparent,
                                        bool& usingSSL) {
  SOCKET_LOG(("nsSocketTransport::BuildSocket [this=%p]\n"this));

  nsresult rv = NS_OK;

  proxyTransparent = false;
  usingSSL = false;

  if (mTypes.IsEmpty()) {
    fd = PR_OpenTCPSocket(mNetAddr.raw.family);
    if (!fd) {
      SOCKET_LOG((" error creating TCP nspr socket [rv=%" PRIx32 "]\n",
                  static_cast<uint32_t>(rv)));
      return NS_ERROR_OUT_OF_MEMORY;
    }
    return NS_OK;
  }

#if defined(XP_UNIX)
  MOZ_ASSERT(!mNetAddrIsSet || mNetAddr.raw.family != AF_LOCAL,
             "Unix domain sockets can't be used with socket types");
#endif

  fd = nullptr;

  uint32_t controlFlags = 0;
  if (mProxyTransparentResolvesHost) {
    controlFlags |= nsISocketProvider::PROXY_RESOLVES_HOST;
  }

  if (mConnectionFlags & nsISocketTransport::ANONYMOUS_CONNECT) {
    controlFlags |= nsISocketProvider::ANONYMOUS_CONNECT;
  }

  if (mConnectionFlags & nsISocketTransport::NO_PERMANENT_STORAGE) {
    controlFlags |= nsISocketProvider::NO_PERMANENT_STORAGE;
  }

  if (mConnectionFlags & nsISocketTransport::BE_CONSERVATIVE) {
    controlFlags |= nsISocketProvider::BE_CONSERVATIVE;
  }

  if (mConnectionFlags & nsISocketTransport::DONT_TRY_ECH) {
    controlFlags |= nsISocketProvider::DONT_TRY_ECH;
  }

  if (mConnectionFlags & nsISocketTransport::IS_RETRY) {
    controlFlags |= nsISocketProvider::IS_RETRY;
  }

  if (mConnectionFlags &
      nsISocketTransport::ANONYMOUS_CONNECT_ALLOW_CLIENT_CERT) {
    controlFlags |= nsISocketProvider::ANONYMOUS_CONNECT_ALLOW_CLIENT_CERT;
  }

  if (mConnectionFlags & nsISocketTransport::IS_SPECULATIVE_CONNECTION) {
    controlFlags |= nsISocketProvider::IS_SPECULATIVE_CONNECTION;
  }

  if (mResolvedByTRR) {
    controlFlags |= nsISocketProvider::USED_PRIVATE_DNS;
  }

  // by setting host to mOriginHost, instead of mHost we send the
  // SocketProvider (e.g. PSM) the origin hostname but can still do DNS
  // on an explicit alternate service host name
  const char* host = mOriginHost.get();
  int32_t port = (int32_t)mOriginPort;

  nsCOMPtr<nsISocketProviderService> spserv =
      nsSocketProviderService::GetOrCreate();
  nsCOMPtr<nsIProxyInfo> proxyInfo = mProxyInfo;

  uint32_t i;
  for (i = 0; i < mTypes.Length(); ++i) {
    nsCOMPtr<nsISocketProvider> provider;

    SOCKET_LOG((" pushing io layer [%u:%s]\n", i, mTypes[i].get()));

    rv = spserv->GetSocketProvider(mTypes[i].get(), getter_AddRefs(provider));
    if (NS_FAILED(rv)) break;

    nsCOMPtr<nsITLSSocketControl> tlsSocketControl;
    if (i == 0) {
      // if this is the first type, we'll want the
      // service to allocate a new socket

      // Most layers _ESPECIALLY_ PSM want the origin name here as they
      // will use it for secure checks, etc.. and any connection management
      // differences between the origin name and the routed name can be
      // taken care of via DNS. However, SOCKS is a special case as there is
      // no DNS. in the case of SOCKS and PSM the PSM is a separate layer
      // and receives the origin name.
      const char* socketProviderHost = host;
      int32_t socketProviderPort = port;
      if (mProxyTransparentResolvesHost &&
          (mTypes[0].EqualsLiteral("socks") ||
           mTypes[0].EqualsLiteral("socks4"))) {
        SOCKET_LOG(("SOCKS %d Host/Route override: %s:%d -> %s:%d\n",
                    mHttpsProxy, socketProviderHost, socketProviderPort,
                    mHost.get(), mPort));
        socketProviderHost = mHost.get();
        socketProviderPort = mPort;
      }

      // when https proxying we want to just connect to the proxy as if
      // it were the end host (i.e. expect the proxy's cert)

      rv = provider->NewSocket(
          mNetAddr.raw.family,
          mHttpsProxy ? mProxyHost.get() : socketProviderHost,
          mHttpsProxy ? mProxyPort : socketProviderPort, proxyInfo,
          mOriginAttributes, controlFlags, mTlsFlags, &fd,
          getter_AddRefs(tlsSocketControl));

      if (NS_SUCCEEDED(rv) && !fd) {
        MOZ_ASSERT_UNREACHABLE(
            "NewSocket succeeded but failed to "
            "create a PRFileDesc");
        rv = NS_ERROR_UNEXPECTED;
      }
    } else {
      // the socket has already been allocated,
      // so we just want the service to add itself
      // to the stack (such as pushing an io layer)
      rv = provider->AddToSocket(mNetAddr.raw.family, host, port, proxyInfo,
                                 mOriginAttributes, controlFlags, mTlsFlags, fd,
                                 getter_AddRefs(tlsSocketControl));
    }

    // controlFlags = 0; not used below this point...
    if (NS_FAILED(rv)) break;

    // if the service was ssl or starttls, we want to hold onto the socket
    // info
    bool isSSL = mTypes[i].EqualsLiteral("ssl");
    if (isSSL || mTypes[i].EqualsLiteral("starttls")) {
      // remember security info
      {
        MutexAutoLock lock(mLock);
        mTLSSocketControl = tlsSocketControl;
        SOCKET_LOG((" [tlsSocketControl=%p callbacks=%p]\n",
                    mTLSSocketControl.get(), mCallbacks.get()));
      }
      // remember if socket type is SSL so we can ProxyStartSSL if need be.
      usingSSL = isSSL;
    } else if (mTypes[i].EqualsLiteral("socks") ||
               mTypes[i].EqualsLiteral("socks4")) {
      // since socks is transparent, any layers above
      // it do not have to worry about proxy stuff
      proxyInfo = nullptr;
      proxyTransparent = true;
    }
  }

  if (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;
}

static bool ShouldBlockAddress(const NetAddr& aAddr) {
  if (!xpc::AreNonLocalConnectionsDisabled()) {
    return false;
  }

  NetAddr overrideAddr;
  bool hasOverride = FindNetAddrOverride(aAddr, overrideAddr);
  const NetAddr& addrToCheck = hasOverride ? overrideAddr : aAddr;

  return !(addrToCheck.IsIPAddrAny() || addrToCheck.IsIPAddrLocal() ||
           addrToCheck.IsIPAddrShared() || addrToCheck.IsLoopbackAddr());
}

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;
  }

  // Since https://github.com/whatwg/fetch/pull/1763,
  // we need to disable access to 0.0.0.0 for non-test purposes
  if (mNetAddr.IsIPAddrAny() && !mProxyTransparentResolvesHost) {
    if (StaticPrefs::network_socket_ip_addr_any_disabled()) {
      mozilla::glean::networking::http_ip_addr_any_count
          .Get("blocked_requests"_ns)
          .Add(1);
      SOCKET_LOG(("connection refused NS_ERROR_CONNECTION_REFUSED\n"));
      return NS_ERROR_CONNECTION_REFUSED;
    }

    mozilla::glean::networking::http_ip_addr_any_count
        .Get("not_blocked_requests"_ns)
        .Add(1);
  }

  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) && ShouldBlockAddress(mNetAddr)) {
      nsAutoCString ipaddr;
      RefPtr<nsNetAddr> netaddr = new nsNetAddr(&mNetAddr);
      netaddr->GetAddress(ipaddr);
      fprintf_stderr(
          stderr,
          "FATAL ERROR: Non-local network connections are disabled and a "
          "connection "
          "attempt to %s (%s) was made.\nYou should only access hostnames "
          "available via the test networking proxy (if running mochitests) "
          "or from a test-specific httpd.js server (if running xpcshell "
          "tests). "
          "Browser services should be disabled or redirected to a local "
          "server.\n",
          mHost.get(), ipaddr.get());
      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 = ""_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;
  }

#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));
    }
  }
  if (StaticPrefs::network_socket_attach_mock_network_layer() &&
      xpc::AreNonLocalConnectionsDisabled()) {
    if (NS_FAILED(AttachMockNetworkLayer(fd))) {
      SOCKET_LOG(
          ("nsSocketTransport::InitiateSocket "
           "AttachMockNetworkLayer 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",
              thisstatic_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",
              thisstatic_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.
--> --------------------

--> maximum size reached

--> --------------------

100%


¤ Dauer der Verarbeitung: 0.39 Sekunden  (vorverarbeitet)  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung ist noch experimentell.